首页 文章 精选 留言 我的

精选列表

搜索[数据库知识开放麦],共10027篇文章
优秀的个人博客,低调大师

网络编程基础知识

一、 cs架构和bs架构二、 互联网三、 osi七层 1.应用层 2.表示层 3.会话层 4.传输层 5.网络层 6.数据链路层 7.物理层 tcp/ip5层(5层名字记住:重点) -1.物理层 -网线,光纤 -2.数据链路层 -网卡 -3.网络层 -路由器 -4.传输层(运输层) -四层路由器 -5.应用层(会话层,表示层,应用层) -http协议,ftp协议,websocket协议... 3.1每一层的功能 1 物理层 -网线----》01010101电信号---》从网线中传输电信号 -8个比特位是一个字节 2 数据链路层 -数据帧:一组电信号是一个数据帧,有头和数据部分 -网卡---》mac地址---》全球唯一---》网卡出厂--》烧在了网卡上 -48位二进制---》看到的都是16进制表示 -广播: -局域网内通信,所有人都会接受到,通过mac地址确定给谁的数据报,如果不是自己的就不处理 -广播风暴 3 网络层 -跨局域网通信,需要经过网络层 -IP地址:0.0.0.0-255.255.255.255 -子网掩码: -192.168.1.1 255.255.255.0 -192.168.2.1 255.255.255.0 -ARP:ip和mac的对照表 4 传输层: -端口:0--65535,一个应用程序可以监听多个端口,但是一个端口只能属于一个应用程序 -TCP协议:可靠传输(可靠传输如何保证?三次握手,四次挥手) -UDP协议:不可靠传输 5 应用层 -htpp,ftp,websocket.... 3.2传输层常用接口 应用程序 FTP TFTP TELNET SMTP DNS HTTP SSH MYSQL 熟知端口 21,20 69 23 25 53 80 22 3306 传输层协议 TCP UDP TCP TCP UDP TCP TCP TCP DNS:把网址转换成ip地址 从浏览器发送出去的数据,都是http协议,默认是80端口 3.3传输层TCP的三次握手和四次挥手 1 tcp可靠传输:三次握手,四次挥手保证数据可靠 2 三次握手 -客户端向服务端发送链接请求 1 -服务端回复可以建立,并且带着跟客户端建立链接的数据报 2 -客户端收到后,链接建好了 3 3 这个过程后就可以可靠传输数据 4 断开链接(4四挥手) -客户端告诉服务端,要断开 1 -服务端收到,回复ok 2 -服务端可能还有数据在传递,暂时还没断开---等数据传完 -服务端告诉客户端,我要断了 3 -客户端收到,回复ok 4 四、Socket层 1 Socket抽象层,从osi七层抽象出来的,抽象了网络层和传输层,跟语言无关 2 专门给开发人员用的 五、基于Tcp的socket套接字(重点)服务端 #导入socket模块 import socket import time #第一个socket是模块名,第二个socket是类名 #类实例化得到对象,得到一个socket对象 #server=socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM) #不写默认就是他 server=socket.socket() #绑定地址跟端口 ,传一个元组,监听的地址,监听的端口 server.bind(('192.168.11.28',8008)) #监听 #半连接池是5,可以缓冲5个 server.listen(5) #等待用户连接(看源码,有两个返回结果) #sock是连接对象,以后这个服务端和客户端交互,使用这个对象交互 #addr是客户端地址(ip和端口) sock,addr=server.accept() #接收客户端发给我的消息 data=sock.recv(1024) #打印 print(data) #服务端给客户端发送消息(必须是byte格式) sock.send(b'helloworld') time.sleep(1) #关闭连接对象 sock.close() #关闭服务 server.close() 客户端 import socket #创建socket对象 client=socket.socket() #连接服务端 client.connect(('127.0.0.1',8008)) #给服务端发了一个sb client.send(b'sb') #收到了服务端给我的 data=client.recv(1024) print(data) #关闭 client.close()

优秀的个人博客,低调大师

JS基础知识整合

1、执行环境:有时也叫环境,是JavaScript中最为重要的一个概念,执行环境定义了变量或函数有权访问的其他数据。 2、变量对象:每一个执行环境都有一个与之关联的变量对象,环境中定义的所有的变量和函数都保存在这个的对象中。 3、作用域链:代码在环境中执行时,会创建变量对象的作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终是当前执行代码所在环境的变量对象。 4、活动对象:如果当前执行环境是函数,则将活动对象作为变量对象。 5、可执行代码: 1)全局代码:例如加载外部的JS文件或者本地标签内的代码,全局代码不包括function体内的代码; 2)函数代码:function体内的代码; 3) eval代码:eval()函数计算某个字符串,并执行其中的JS代码,比如 eval("alert('helloworld')")。 6、执行上下文栈:在一个JS程序中,必定会产生多个执行上下文,JS引擎会以栈的方式来处理它们,也就是执行上下文栈。 7、作用域:JS是一种没有块级作用域的语言(包括if、for等语句的花括号代码块或者单独的花括号代码块都不能形成一个局部作用域),所以js的局部作用域的形成有且只有函数的花括号内定义的代码块形成的,既函数作用域。 8、作用域链:是作用域规则的实现,通过作用域链的实现,变量在它的作用域内可被访问,函数在它的作用域内可被调用。 作用域链是一个只能单向访问的链表,这个链表上的每个节点就是执行上下文的变量对象,单向链表的头部(可被第一个访问的节点)始终都是当前正在被调用执行的函数的变量对象(活动对象),尾部始终是全局活动对象。 9、闭包:函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。 从技术角度来说,JS的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函数作用域内。

优秀的个人博客,低调大师

线程最最基础的知识

Java 多线程系列文章第 5 篇。 什么是线程 试想一下没有线程的程序是怎么样的?百度网盘在上传文件时就无法下载文件了,得等文件上传完成后才能下载文件。这个我们现在看起来很反人性,因为我们习惯了一个程序同时可以进行运行多个功能,而这些都是线程的功劳。 之前的文章 进程知多少 中讲到,为了实现多个程序并行执行,引入了进程概念。现在引入线程是为了让一个程序能够并发执行。 线程的组成 线程ID:线程标识符。 当前指令指针(PC):指向要执行的指令。 寄存器集合:存储单元寄存器的集合。 堆栈:暂时存放数据和地址,一般用来保护断点和现场。 线程与进程区别 线程和进程之间的区别,我觉得可以用这个例子来看出两者的不同,进程就是一栋房子,房子住着 3 个人,线程就是住在房子里的人。进程是一个独立的个体,有自己的资源,线程是在进程里的,多个线程共享着进程的资源。 线程状态 我们看到 Java 源代码里面,线程状态的枚举有如下 6 个。 public enum State { //新建状态 NEW, //运行状态 RUNNABLE, //阻塞状态 BLOCKED, //等待状态 WAITING, //等待状态(区别在于这个有等待的时间) TIMED_WAITING, //终止状态 TERMINATED; } 下面给这 6 个状态一一做下解释。 NEW:新建状态。在创建完 Thread ,还没执行 start() 之前,线程的状态一直是 NEW。可以说这个时候还没有真正的一个线程映射着,只是一个对象。 RUNNABLE:运行状态。线程对象调用 start() 之后,就进入 RUNNABLE 状态,该状态说明在 JVM 中有一个真实的线程存在。 BLOCKED:阻塞状态。线程在等待锁的释放,也就是等待获取 monitor 锁。 WAITING:等待状态。线程在这个状态的时候,不会被分配 CPU,而且需要被显示地唤醒,否则会一直等待下去。 TIMED_WAITING:超时等待状态。这个状态的线程也一样不会被分配 CPU,但是它不会无限等待下去,有时间限制,时间一到就停止等待。 TERMINATED:终止状态。线程执行完成结束,但不代表这个对象已经没有了,对象可能还是存在的,只是线程不存在了。 线程既然有这么多个状态,那肯定就有状态机,也就是在什么情况下 A 状态会变成 B 状态。下面就来简单描述一下。 结合下图,我们 new 出线程类的时候,就是 NEW 状态,调用 start() 方法,就进入了 RUNNABLE 状态,这时如果触发等待,则进入了 WAITING 状态,如果触发超时等待,则进入 TIMED_WAITING 状态,当访问需要同步的资源时,则只有一个线程能访问,其他线程就进入 BLOCKED 状态,当线程执行完后,进入 TERMINATED 状态。 其实在 JVM 中,线程是有 9 个状态,如下所示,有兴趣的同学可以深入了解一下。 javaClasses.hpp enum ThreadStatus { NEW = 0, RUNNABLE = JVMTI_THREAD_STATE_ALIVE + // runnable / running JVMTI_THREAD_STATE_RUNNABLE, SLEEPING = JVMTI_THREAD_STATE_ALIVE + // Thread.sleep() JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT + JVMTI_THREAD_STATE_SLEEPING, IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE + // Object.wait() JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_INDEFINITELY + JVMTI_THREAD_STATE_IN_OBJECT_WAIT, IN_OBJECT_WAIT_TIMED = JVMTI_THREAD_STATE_ALIVE + // Object.wait(long) JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT + JVMTI_THREAD_STATE_IN_OBJECT_WAIT, PARKED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park() JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_INDEFINITELY + JVMTI_THREAD_STATE_PARKED, PARKED_TIMED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park(long) JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT + JVMTI_THREAD_STATE_PARKED, BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE + // (re-)entering a synchronization block JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER, TERMINATED = JVMTI_THREAD_STATE_TERMINATED }; Java 线程实现 下面讲一讲在 Java 中如何创建一个线程。众所周知,实现 Java 线程有 2 种方式:继承 Thread 类和实现 Runnable 接口。 继承 Thread 类 继承 Thread 类,重写 run() 方法。 class MyThread extends Thread { @Override public void run() { System.out.println("MyThread"); } } 实现 Runnable 接口 实现 Runnable 接口,实现 run() 方法。 class MyRunnable implements Runnable { public void run() { System.out.println("MyRunnable"); } } 这 2 种线程的启动方式也不一样。MyThread 是一个线程类,所以可以直接 new 出一个对象出来,接着调用 start() 方法来启动线程;而 MyRunnable 只是一个普通的类,需要 new 出线程基类 Thread 对象,将 MyRunnable 对象传进去。 下面是启动线程的方式。 public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread myRunnable = new Thread(new MyRunnable()); System.out.println("main Thread begin"); myThread.start(); myRunnable.start(); System.out.println("main Thread end"); } } 打印结果如下: main Thread begin main Thread end MyThread MyRunnable 看这结果,不像咱们之前的串行执行依次打印,主线程不会等待子线程执行完。 敲重点:不能直接调用 run(),直接调用 run() 不会创建线程,而是主线程直接执行 run() 的内容,相当于执行普通函数。这时就是串行执行的。看下面代码。 public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread myRunnable = new Thread(new MyRunnable()); System.out.println("main Thread begin"); myThread.run(); myRunnable.run(); System.out.println("main Thread end"); } } 打印结果: main Thread begin MyThread MyRunnable main Thread end 从结果看出只是串行的,但看不出没有线程,我们看下面例子来验证直接调用 run() 方法没有创建新的线程,使用 VisualVM 工具来观察线程情况。 我们对代码做一下修改,加上 Thread.sleep(1000000) 让它睡眠一段时间,这样方便用工具查看线程情况。 调用 run() 的代码: public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("MyThread"); Thread myRunnable = new Thread(new MyRunnable()); myRunnable.setName("MyRunnable"); System.out.println("main Thread begin"); myThread.run(); myRunnable.run(); System.out.println("main Thread end"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread { @Override public void run() { System.out.println("MyThread"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyRunnable implements Runnable { public void run() { System.out.println("MyRunnable"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } 运行结果: main Thread begin MyThread 只打印出 2 句日志,观察线程时也只看到 main 线程,没有看到 MyThread 和 MyRunnable 线程,印证了上面咱们说的:直接调用 run() 方法,没有创建线程。 下面我们来看看有 调用 start() 的代码: public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("MyThread"); Thread myRunnable = new Thread(new MyRunnable()); myRunnable.setName("MyRunnable"); System.out.println("main Thread begin"); myThread.start(); myRunnable.start(); System.out.println("main Thread end"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } 运行结果: main Thread begin main Thread end MyThread MyRunnable 所有日志都打印出来了,并且通过 VisualVM 工具可以看到 MyThread 和 MyRunnable 线程。看到了这个结果,切记创建线程要调用 start() 方法。 今天就先讲到这,继续关注后面的内容。 推荐阅读 老板叫你别阻塞了 吃个快餐都能学到串行、并行、并发 泡一杯茶,学一学同异步 进程知多少? 设计模式看了又忘,忘了又看? 后台回复『设计模式』可以获取《一故事一设计模式》电子书 觉得文章有用帮忙转发&点赞,多谢朋友们!

优秀的个人博客,低调大师

几个集合工具知识回顾

以前看过一些JDK源码,最近回顾了一下笔记,所以在这里对几个常见的集合工具做个记录。 一、跳表 跳表SkipList就是有序链表+二分搜索的组合。它的效率可以做到和二分相同,时间复杂度是O(logn),空间复杂度是O(n)。 它的基本特征是: 它是一个多层结构,每一层都是一个有序链表,且至少包含两个链表节点(头head节点和尾tail节点); 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集),最底层的链表包含了所有的元素; 链表中的每个节点都包含两个指针,一个指向下一个兄弟链表节点,一个指向下一层的子链表节点; 理想情况下,第二层只有第一层一半的节点数,而且均匀间隔,第三层则是1/4的节点数,以此类推,这样理想的层数就是logN。 工作过程: 搜索过程很简单,就是从最高层链表开始,如果比当前节点大、比下一个节点小,那么去下一层查找,以此类推,一直找到最底层的最后一个节点; 插入过程需要确定插入的层数,通常给定一个统计概率p,产生一个0到1之间的随机数,如果这个随机数小于p,则将高度加1,直到产生的随机数大于概率p才停止。当概率为1/2或者是1/4的时候,整体的性能会比较好(当p为1/2的时候,也就是抛硬币的方法了)。而后,就是将元素插入到从最底层到第k层; 1.ConcurrentSkipListMap ConcurrentSkipListMap内部的数据结构就是Index,它是跳表中的索引,本身是一个bucket(KV链表),同时包含右指针和下指针。它通过HeadIndex(包含level层级)作为跳表每层的头结点,将整张表拎出来。 先说删除操作,对于跳表而言主要是定位待删结点+删除该结点的一个复合操作,而在并发跳表中则是这样: ⑴ 先通过key和HeadIndex定位到待删除节点(二分搜索定位,比较器比较key是否相等); ⑵ 将待删结点value值置为null(即标记待删除),失败则自旋; ⑶ 向待删结点的next后继位置新增一个空的marker标记结点(用于分散并发冲突,即key值是否对应、value值是否为null、marker节点为空,如果不是则重试); ⑷ 断链操作,让待删结点的pre前驱节点越过本身指向后继结点(非marker节点),那么待删结点和marker标记结点将游离结点,最终被GC掉; 插入操作类似,简单来说就是:先通过概率随机得到level值,然后新增一个结点到最底层的链表上,并新增一个纵向的引用关联,最后链接各个索引层次上的新节点。 二、哈希表 HashMap的底层是基于“数组+链表”组成的,不过在JDK1.7和1.8中具体实现稍有不同。 默认的负载因子0.75(4/3),默认容量为16,也就意味着当容量达到了16*0.75=12就需要进行2的n次幂扩容,rehash、数据拷贝而耗性能,所以对大数据、性能敏感的应用通常提前预估好HashMap大小,减少rehash次数。 工作过程:对传入key值做hash运算,而后与数组长度取模得到数组下标index。因为性能问题,HashMap规定数组的长度为2次幂,这样用2^n-1做位运算与取模效果一致,并且效率还要高的多。而判定key值是否相等的条件,是hashcode+key。 谈hashmap就不得不说它存在的这两个问题: 链表过深: 当hash冲突严重时,每个桶的链表越来越深,遍历效率越来越低。而在Java7之后,通过红黑树来对大链表做优化,提升查询效率。 比如在put时,如果当前bucket(链表)的大小大于阈值(默认是8)时,将其转为红黑树;而get时,判断当前Node如果是TreeNode就按照树的方式查找,不是则按照链表的方式遍历查找。 并发时环形链表: 在rehash时,链表拷贝是逐个进行的(头插法),这意味着顺序会颠倒。如果这个时候刚好有另外一个线程在操作,就可能会出现A->B->A的情况,即环形链表。 这个问题在Java8得到了解决,通过尾插法,使用2次幂扩展长度的方式,即用key的高位运算结果,如果扩容后判断原来hash值新增的1个bit是0还是1,0的话索引没变,1的话变成“原索引+oldCap”。这样就省去了重新hash值的操作和时间,避免了环形链表的问题。 一般在JDK集合类里都会有“Fail-Fast机制”,这在hashmap里也有: 快速失败策略在源码中的实现是通过modCount域,就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map。 注意:经管modCount被声明为volatile,保证线程之间修改的可见性。但迭代器的快速失败行为并不能得到保证,比如,存在非同步的并发修改的时候。 1.ConcurrentHashMap ConcurrentHashMap由Segment[]数组+里面的Entry组成,通过锁分段的方式来降低并发度(16等分,每个Segment一个ReentrantLock)。即:并发读不加锁,并发写对操作的segment加独占锁。ConcurrentHashMap也有全量锁的时候,比如扩容、rehash的时候。主要目标还是最小化更新竞争的同时保持并发可读性,使得空间消耗与HashMap大致相同。 工作过程: 1)put时,第一次hash确定key在哪个segment中; 2)tryLock尝试获取锁,失败则阻塞(Java7是会自旋一阵而后升级为阻塞锁,默认是64次); 3)而后第二次hash在segment中确定key在链表的哪个Entry链表中; 4)遍历判断hashcode和key值是否相等,相等就覆盖,不相等就new一个; 5)最后判断要不要扩容,在返回前解锁分段锁。 在Java8之后,ConcurrentHashMap的数据结构发生了很大变化,首先抛弃了分段锁的方案,使用Unsafe的CAS+阻塞锁。虽然仍是Node数组+链表的形式,但将锁分段更细化了。 工作过程:如果根据key定位是一个空的话,尝试利用CAS new一个新的bucket,失败则自旋保证;如果不是空的话,则对链表头加阻塞锁,使用同步块来细化。实际上,在每一个桶中,头node都作为这个bucket的锁而锁住整个bucket。这样的策略,使得对HashMap的并发粒度呈线性减低,因为一个桶中node节点不会太多,不会总是冲突,扩容时更会均匀打散,当链表过深时(默认为8)则用红黑树保证查询效率。 跨段操作: 某些操作需要涉及到多个segment,比如size()。HashMap统计各个bucket基本的思路是“提前做”,结构性更新时对计数器加1减1,而在ConcurrentHashMap中因为存在并发,情况就不同了。 在JDK1.7中,先不加锁计算三次,比较前后计算结果,结果一致就认为是准确的,不一致就对Segment加锁再计算; 在JDK1.8之后,先CAS的对baseCount加减,如果CAS失败,则自旋对counterCells加减(每个bucket一个counterCells)。然后在调用size()或mappingCount()的时候,累加baseCount和counterCells数组。 三、延时队列 这里,我们不妨先谈谈优先级队列。 1.PriorityQueue PriorityQueue是一个优先级的无界队列,意味着每次从队列取出的都是最小的元素。内部使用小顶堆来实现。 工作过程: PriorityQueue的建堆过程和大顶堆的建堆过程基本一样,从上往下做siftDown操作。siftDown的过程就是将一个节点和它的左右子节点进行比较,然后和最小的子节点交换位置,一直向下到叶子节点,保证它比它所有的子节点都要小。 而每次添加新元素实际上是在数组的后面追加。先判断数组长度是否需要扩容,而后做siftUp操作。和siftDown类似,一个节点和它的父节点比较,如果果它比父节点小则交换位置,一直向上到根节点这样,就变成符合条件的小顶堆了。 默认情况下元素采用自然顺序升序排序。默认的长度为11,扩容时若容量小于64,则新容量是oldCap * 2 + 2,否则是oldCap * 1.5。即容量越小增长得越快。 PriorityQueue在JDK中对应的实现类是PriorityBlockingQueue,优先级的无界阻塞队列。与ConcurrentHashMap不同,PriorityBlockingQueue在扩容时没有全量锁的时候,事实上为了获得更好的并发性,在扩容开始时PriorityBlockingQueue就释放了全局锁,后面的扩容操作通过乐观锁来进行并发控制。 2.DelayQueue DelayQueue等同于BlockingQueue + PriorityQueue + Delayed接口。 DelayQueue是一个延时的无界阻塞队列。其实就是使用PriorityQueue进行元素的排序,以元素过期时间为排序条件,以ReentrantLock作为主锁,即头元素是最先过期的元素。它会根据time的先后时间排序,若time相同则根据sequenceNumber序号排序。 DelayQueue核心字段是:全局独占锁,优先级队列(用于存储元素),leader即当前消费者线程,和available条件变量。DelayQueue的内部实现,其实就是你在offer一个过期时间为N的元素时,先入优先级队列,之后你take一个任务的时候,判断列表头的到期时间,到期就去消费,没到期则通过available条件变量进行await阻塞等待,过N个时间单位到期唤醒后再去消费。 具体的工作过程: 当你offer一个元素时: 1)先加锁,入优先级队列; 2)然后peek一下,如果队首元素是刚插入的元素,则设置leader为null,并唤醒阻塞在available上的线程进行消费; 3)释放锁; 当你要take一个元素时: 1)先加锁; 2)然后peek一下,如果队首元素为空则阻塞,不为空则获取队首元素的超时时间,已超时则poll出队; 3)如果leader不为null,说明消费者线程已拿到锁,阻塞当前线程; 4)如果leader为null则将leader指向当前线程,阻塞等待剩余的到期时间,唤醒后将leader置为null。这时再判断一次,如果leader为null并且队列不为空,说明没有其他线程在等待,则唤醒available上的线程; 5)释放锁; 3.TimingWheel JDK的Timer和DelayQueue插入和删除操作的平均时间复杂度为O(nlogn),不能满足一般分布式系统的性能要求,而基于时间轮(TimingWheel)算法则可以将插入和删除操作的时间复杂度降为O(1)。 Kafka中的时间轮(TimingWheel)是一个存储定时任务的环形队列,队列中的每个元素是一个用于存储定时任务的双端链表,链表中的每一项就是定时任务项,里面是真正的定时任务。 环形队列中的每个元素就是一个个时间格,每个时间格代表当前时间轮的基本时间跨度(tickMs)。时间格的个数是固定的(wheelSize),整个时间轮的总体时间跨度(interval)为 tickMs × wheelSize。时间轮有一个表盘指针(currentTime),表示时间轮当前所处的时间,它是tickMs的整数倍。currentTime指向的当前时间格,就需要处理此时间格所对应的TimerTaskList的所有任务。 时间轮 = 时间格(环形队列) » 定时任务列表(双端链表) » 定时任务项 初始情况下表盘指针currentTime指向时间格0。当对某个时间格做相应的到期操作时,同时有定时任务插入,则复用时间轮,这就是环形队列的好处了。整个时间轮的总体跨度是不变的,随着指针currentTime的推进,当前时间轮所能处理的时间段也在不断后移。 对于每个使用到的TimerTaskList都会加入到DelayQueue中,根据TimerTaskList对应的到期时间来排序,这意味着最短到期时间的会在列表头,优先执行。 超长任务问题:现在看来情况良好,没有什么问题,但倘若有个定时时长大于或者远大于interval的任务进来,如果直接扩充wheelSize是没有底限的,而且对于超长定时任务,过多的内存占用,性能效率都不会太好。因此引入了层级时间轮的概念,通过时间轮升/降级来解决大于当前时间周期的定时任务。 当任务的到期时间超过了当前时间轮的时间跨度时,尝试添加到上层时间轮中。每一层时间轮的时间格个数wheelSize是差不多固定的,但上一层的基本时间跨度tickMs为下一层的总体时间跨度interval,以此类推。 你应该注意到,越上层的时间轮时间跨度越大,即便currentTime当前指向某个时间格,任务的到期操作也很难执行。这时又会有一个时间轮降级的动作,会将这个定时任务重新提交到下一层的时间轮中,依次类推。可想而知,底层的时间轮,基本时间跨度很小,比如在Kafka默认实现中,tickMs=1ms,wheelSize=20,而在Quartz中,tickMs=1ms,wheelSize=60,秒、分、时。 性能调优: 在插入任务之前先检查任务是否完成,因为存在有些任务超时直接就完成超时操作的场景; 每个双向链表TimerTaskList都会有一个哨兵节点,来简化边界条件。它是一个附加的链表节点,作为第一个节点,值域中并不存储任何东西; 每一层的currentTime都必须是tickMs的整数倍,这是用一个单线程来维护的,也叫“到期操作收割机”,通过“拨动”执行指针来触发定时任务; “空推进”问题: DelayQueue中的列表头到期时间是确定的,这里获取DelayQueue的队头只需要O(1)的时间复杂度,但如果是每秒定时推进的话,比如到期时间200s,那么获取到这个定时任务需要执行200次推进且有199次属于“空推进”,这样无疑会无故空耗机器的性能资源。 所以,为了避免“空推进”,引入DelayQueue来做时间“拨动”,以少量空间换时间,优化性能。简单来说就是,用时间轮TimingWheel来做任务的插入和删除操作,用延迟队列DelayQueue来做时间推进的工作。 4.RingBuffer 这里说的是Disruptor的RingBuffer,它不像传统的队列和其它RingBuffer,具有head、tail、count这三个指针,也是三个冲突点。每次操作元素时,入队列、出队列还有区分是否空队列都会有竞争,而且这三个变量,通常也在一个缓冲行里,又会有“伪共享”问题,无论操作哪一个都会导致其它缓存不命中。而而Disruptor的RingBuffer的优点是:通过减少竞争点,比如不删除数据而是直接覆盖,所以不需要tail尾指针;通过RingBuffer重复利用数组,来减少GC;利用数组对CPU Cache友好的特性,缓存行填充,来避免伪共享,提高效率。 伪共享问题: 缓存是由缓存行组成的,通常是64字节(比较旧的处理器缓存行是32字节),引用主内存中的一块地址。若数据结构中的项在内存中不是彼此相邻的(比如链表),就得不到这种缓存加载所带来的性能优势。 如果有一个单独的变量head,然后又有另一个变量紧挨着它,tail。那么当加载head到缓存的时候,也免费加载了tail。那么,如果你更新了head的值,缓存中的值和内存中的值都被更新了,再想要读tail值时,整个缓存行都要重新从内存中读取,也就意味着应用程序被缓存未命中给拖慢了。如果两个独立的线程同时写这两个不同的值会更糟,因为遇到两个线程之间的写冲突了,尽管它们写入的是不同的变量。这就叫作“伪共享”,无声的性能杀手。 一般的解决方案是,增大数组元素的间隔使得由不同线程存取的元素位于不同的缓存行上,以空间换时间。俗称“缓存行填充”,将32字节、64字节或者128字节填满,不填满一样有伪共享问题,但这肯定是会浪费空间的。在JDK1.7中,Sun官方提供了一个优化方案,通过添加@Contended注解,JVM会自动的进行缓存行填充,但应用程序运行时必须加上虚拟机参数-XX:-RestrictContended,这个注解才会生效。 Disruptor的RingBuffer,只需要一个序号指向下一个可用的空间,同时做缓存行填充,通过补全来确保RingBuffer的序号不会和其他东西同时存在于一个缓存行中,所以没有伪共享问题和非预期的竞争。 Disruptor的RingBuffer的工作原理,简单来说,就是把争抢多个窗口买票的行为,通过Barrier规范为:所有人先到Barrier处取号,然后凭序号去对应窗口买票。取号消耗的时间很短,一个Barrier就足够,而买票消耗时间很长,各个窗口独立服务。 具体的工作过程: 在定位元素时,与HashMap类似的,按size进行位运算来定位出数组的下标(所以数组长度也是2^n)。RingBuffer中每格都有序号,并用一个变量来指向可用的位置。 在插入元素时,为2阶段提交,首先申请sequence序号,如果发现所有数据都没被被消费,会等待(为了避免RingBuffer重叠)直到有可用的sequence;获取到可用的sequence后,再进行publish操作。 那么,Disruptor如何防止多个线程重复写同一个元素呢?在多个生产者的情况下,Disruptor让每个线程获取不同的一段数组空间进行操作,就是在分配元素的时候,通过CAS判断一下这段空间是否已经分配出去即可。但这样做,意味着很有可能出现“脏读”,读到还未写的旧数据。“如何防止读取的时候读到还未写的元素”,Disruptor是这样做的,在多个生产者的情况下,引入了一个与RingBuffer大小相同的buffer:available Buffer。当某个位置写入成功的时候,便把availble Buffer相应的位置标记为写入成功。读取的时候,会遍历available Buffer,来判断元素是否已经就绪。 Disruptor的等待策略: 名称 措施 备注 BlockingWaitStrategy 加锁 CPU资源占用少,延迟大、吞吐低 BusySpinWaitStrategy 自旋 通过自旋,来减少线程切换导致的系统调用。CPU资源占用高,延迟大 PhasedBackoffWaitStrategy 自旋+yield+自定义策略 混合上面多种策略,根据时间参数和传入的等待策略来决定使用哪种等待策略。 SleepingWaitStrategy 自旋+yield+sleep 多次自旋尝试不成功后,选择让出CPU,等待下次调度,多次调度后仍不成功,尝试睡眠一个纳秒级别的时间后再尝试。这种策略平衡了延迟和CPU资源占用,但延迟不均匀。 TimeoutBlockingWaitStrategy 加锁,有超时限制 相对于BlockingWaitStrategy来说,设置了等待时间,超时后抛异常 YieldingWaitStrategy 自旋+yield+自旋 多次自旋尝试不成功后,选择让出CPU,等待下次调度。这种策略平衡了延迟和CPU资源占用,延迟比较均匀。 四、COW列表 在更新时申请写锁,复制对象并修改,修改完毕后切换对象引用,而读取则不加锁。这种方式被称为“写时复制(copy-on-write)”,即COW机制。CopyOnWriteArrayList就是COW的典型实现。 CopyOnWriteArrayList的内部实现,拿add()方法来说,就是当对列表进行修改操作时,先lock一把(使用ReentrantLock),copy一个数组(使用Arrays.copyOf()方法,其内部使用System.arraycopy函数拷贝数据,因为是native方法,效率比普通数组拷贝效率要高),数组长度在当前基础上加1,然后在新数组追加新数据,再将该新数组的引用赋予当前数组,最后解锁。因为对更新操作是在副本中进行的,所以无锁,适合于读远多于写的情况。 与Java的COW不同的是,Linux下,不是直接将父进程的数据拷贝到子进程中,而是子进程共享父进程的物理空间,当父子进程有内存写入操作时触发异常中断,再将内存页复制一份。当我们通过fork()函数创建出的子进程,它会与父进程共享物理内存空间(其实是直接引用父进程的物理空间)。当父子进程中有更改数据段的行为发生时,再为子进程相应的分配物理空间。 具体来说,就是fork()之后,kernel把父进程中的内存页的权限都设为read-only,子进程的地址空间指向父进程。当父子进程都只读共享内存时,相安无事。当其中某个进程写内存时,CPU检测到内存页是read-only,触发页异常中断(page-fault),开始走kernel的一个中断例程。在中断例程中,kernel就会把触发异常的页复制一份,父子进程各自持有独立的一份。 这样做是为了减少不必要的资源分配,如果没有修改数据就不会有副本。

优秀的个人博客,低调大师

Kotlin 模糊知识点

1,as 关键字的作用,表示强转类型,example 打开软键盘的代码 fun openKeyboard(context: Context, editText: EditText) { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(editText, InputMethodManager.RESULT_SHOWN) imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) } 解释:注意一点即可以观察出来,如果不进行强转,本身默认是 Object 类型,那么怎么会有 showSoftInput 方法和 toggleSoftInput 的方法呢。 2,object 关键字和 companion object 关键字都可以用来实现单例模式。不但如此,object 还常用来实现匿名内部类。 object 可以定义在全局也可以在类的内部使用 object 就是单例模式的化身 object 可以实现 Java 的匿名内部类 companion object 就是 Java 中的 static 变量 companion object 只能定义在对应的类中 tips:除此之外,除此之外,除此之外 object 可以作为变量的定义也可以是表达式 object 匿名类可以继承并超越 Java 中匿名类而实现多个接口 object 表达式当场实例化,但定义的 object 变量是延迟实例化的 object 匿名内部类还可以引用并更改局部变量 companion object 甚至还可以被扩展 object 可以实现 kotlin 的单例模式,可以定义在全局中,也可以定义在类的内部。但是需要注意: 1.object 定义后即刻实例化 2.因此 object 不能有定义构造函数 3.定义在类内部的 object 并不能访问类的成员 companion object 的定义完全属于类的本身,所以 companion object 肯定不能脱离类而定义在全局中。有些像 Java 中的 static 变量。也可以实现接口,因为 companion object 寄生于类,甚至类还可以直接作为相对应的接口的参数形式传入。 在 Java 中,内部类是不可以访问外部的非 final 成员变量的,也就是说:它不允许更改变量值!但是,kotlin 的 object 可以。 3,var 和 val 的唯一区别是 var 既有 getter 又有 setter,val 只有 getter 没有 setter。其中 val 与 Java 中的 final 不完全相同。只有一种情况他们完全相同,就是像 private val foo = 3 这样,以 private 修饰,赋予初始值,并且不提供自定义的 getter,不被委托的属性,这个属性完全等同于 Java 中的 private final int foo = 3。因为 kotlin 默认是 public 属性,如果是权限修饰符是 private 的话那么就不能外部访问,再加上是以 val 修饰的,所以没有 getter 和 setter 方法。 4,kotlin 中 open 关键字什么时候使用,init 关键字什么时候使用。 Kotlin 类默认是 final,这点可以自己尝试反编译下 Kotlin 代码就可以发现。所以如果你想扩展一个类的话,就必须使用 open 修饰符才行。 Kotlin 中的类可以有一个主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后 class Person constructor(firstName: String) {} 一般情况下 constructor 关键字可以省略。tips:主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,使用 init 关键字作为前缀。 class Person constructor(firstName: String) { init { System.out.print("FirstName is $firstName") } } 5,kotlin 代码转换成 Java 代码,选择 tools -> kotlin -> show kotlin bytecode (展示字节码文件) -> Decompile 即可以显示出来相对应的 Java 代码。 6,注意在 kotlin 中之所以可以直接访问属性名来获取值就是因为 kotlin 自动生成了 getter 和 setter 方法。

优秀的个人博客,低调大师

QoS的基础知识

跨IP网络提供足够的服务质量(QoS)正成为当今企业IT基础架构中越来越重要的一个方面。通过网络进行语音和视频流传输不仅需要QoS,它还是支持不断增长的物联网(IoT)的重要因素。在本文中,我将解释为什么QoS很重要,它如何工作,并描述一些用例场景,以展示它如何有益于最终用户的体验。为什么QoS很重要?网络上运行的某些应用程序对延迟很敏感。这些应用程序通常使用UDP协议而不是TCP协议。TCP和UDP之间的关键区别在于它与时间敏感性有关,TCP将重新传输在传输过程中丢失的数据包,而UDP则不会。对于从一台PC到下一台PC的文件传输,应使用TCP,因为如果任何数据包丢失,格式错误或无序到达,TCP协议可以重新传输和重新排序数据包以在目标PC上重新创建文件。但是对于IP电话等UDP应用,任何丢失的数据包都不能重传,因为语音数据包是按顺序流进行的; 重传数据包是没用的。因此,运行UDP协议的应用程序的任何丢失或延迟数据包都是一个真正的问题。在我们的语音通话示例中,即使丢失一些数据包也会导致语音质量变得不稳定且难以理解。此外,数据包对所谓的抖动很敏感。抖动是流应用程序延迟的变化。如果你的网络具有足够的带宽并且没有超出其可以处理的流量,那么你将不会遇到丢包,延迟或抖动问题。但在许多企业网络中,有时链路变得过于拥挤,导致路由器和交换机开始丢弃数据包,因为它们进出的速度比可以处理的速度快。如果是这种情况,你的流媒体应用程序将受到影响。这就是QoS的用武之地。QoS如何运作?QoS有助于管理网络基础架构上的数据包丢失,延迟和抖动。由于我们正在使用有限的带宽,我们的第一项业务是确定哪些应用程序将从管理这三项内容中受益。一旦网络和应用程序管理员识别出需要优先于网络带宽的应用程序,下一步就是识别该流量。有几种方法可以识别或标记流量。服务等级(CoS)和差异化服务代码点(DSCP)是两个例子。CoS将在第2层帧头中标记数据流,而DSCP将在第3层包头中标记数据流。可以不同地标记各种应用,这允许网络设备能够将数据分类到不同的组中。现在我们可以将数据流分类到不同的组中,我们可以使用该信息将策略放在这些组上,以便优先处理某些数据流而不是其他数据流。这被称为排队。例如,如果标记了语音流量并且创建了策略以使其能够访问链路上的大部分网络带宽,则路由或交换设备将这些分组/帧移动到队列的前面并立即发送它们。但是,如果标准TCP数据传输流标记为较低优先级,则它将等待(排队),直到有足够的带宽进行传输。如果队列填满太多,这些优先级较低的数据包/帧将首先被丢弃。QoS用例场景如前所述,QoS的最常见用例是语音和视频流。但是还有更多的例子,特别是现在IoT开始起飞了。一个例子是制造业,机器开始利用网络提供可能发生的任何问题的实时状态信息。识别问题的任何延迟都可能导致制造错误,每秒花费数万美元。利用QoS,制造状态数据流可以在网络中占优先地位,以确保信息及时流动。另一个用例可能是用于大型物联网项目的各种智能传感器,如智能建筑或智能城市。收集和分析的大部分数据,如温度,湿度和位置感知,都是高度时间敏感的。由于这种时间敏感性,应该相应地正确识别,标记和排队这些数据。可以肯定地说,随着我们的连接需求不断扩展到我们个人和商业生活的各个方面,QoS将在确保某些数据流优先于其他数据流以便有效运行方面发挥越来越重要的作用。

优秀的个人博客,低调大师

python 关键知识点

学习资源:笨方法学习 python3 将变量传递给脚本——argv 脚本:你编写的 .py 文件。 argv 参数变量(argument variable)保存着你运行 python 脚本的参数。 input() 脚本运行过程需要输入参数 from sys import argv script, user_name = argv # 解包 prompt = '> ' # 提示符 print(f"Hi {user_name}, I'm the {script} script.") print("I'd like to ask you a few question.") print(f"Do you like me {user_name}?") likes = input(prompt) print(f"Where do you live {user_name}?") lives = input(prompt) print("What kind of computer do you have?") computer = input(prompt) print(f''' Alright, so you said {likes} about liking me. You live in {lives}. Not sure where that is. And you have a {computer} computer. Nice. ''') x = input('sd ffg') sd ffgdf type(x) str x 'df' echo 'This is a text file.' > a.txt # 创建文件 cat a.txt # 查看文件 man cat # 查看 cat 的帮助文档 函数中的 *args 告诉 python 把函数的所有参数都接收进来,然后放到名叫 args 的列表中去。 def print_two(*args): arg1, arg2 = args print(f'arg1: {arg1}, arg2: {arg2}') print_two('Zed', 'Shaw') arg1: Zed, arg2: Shaw 字符串 & 字节串 & 字符编码 import sys script, encoding, error = sys.argv def main(language_file, encoding, errors): line = language_file.readline() if line: print_line(line, encoding, errors) return main(language_file, encoding, errors) def print_line(line, encoding, errors): next_lang = line.strip() raw_bytes = next_lang.encode(encoding, errors = errors) cooked_string = raw_bytes.decode(encoding, errors = errors) print(raw_bytes, "<===>", cooked_string) languages = open('E:/Data/languages.txt', encoding = 'utf-8') main(languages, encoding, error) PowerShell: python .\str_bytes_test.py utf-8 strict def main(language_file, encoding, errors): line = language_file.readline() if line: print_line(line, encoding, errors) return main(language_file, encoding, errors) def print_line(line, encoding, errors): next_lang = line.strip() # 删除每行结尾的 "\n" raw_bytes = next_lang.encode(encoding, errors = errors) cooked_string = raw_bytes.decode(encoding, errors = errors) print(raw_bytes, "<===>", cooked_string) with open('E:/Data/languages.txt', encoding = 'utf-8') as fp: main(fp, 'utf-8', 'strict') b'Afrikaans' <===> Afrikaans b'\xe1\x8a\xa0\xe1\x88\x9b\xe1\x88\xad\xe1\x8a\x9b' <===> አማርኛ b'\xd0\x90\xd2\xa7\xd1\x81\xd1\x88\xd3\x99\xd0\xb0' <===> Аҧсшәа b'\xd8\xa7\xd9\x84\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a\xd8\xa9' <===> العربية b'Aragon\xc3\xa9s' <===> Aragonés b'Arpetan' <===> Arpetan b'Az\xc9\x99rbaycanca' <===> Azərbaycanca b'Bamanankan' <===> Bamanankan b'\xe0\xa6\xac\xe0\xa6\xbe\xe0\xa6\x82\xe0\xa6\xb2\xe0\xa6\xbe' <===> বাংলা b'B\xc3\xa2n-l\xc3\xa2m-g\xc3\xba' <===> Bân-lâm-gú b'\xd0\x91\xd0\xb5\xd0\xbb\xd0\xb0\xd1\x80\xd1\x83\xd1\x81\xd0\xba\xd0\xb0\xd1\x8f' <===> Беларуская b'\xd0\x91\xd1\x8a\xd0\xbb\xd0\xb3\xd0\xb0\xd1\x80\xd1\x81\xd0\xba\xd0\xb8' <===> Български b'Boarisch' <===> Boarisch b'Bosanski' <===> Bosanski b'\xd0\x91\xd1\x83\xd1\x80\xd1\x8f\xd0\xb0\xd0\xb4' <===> Буряад b'Catal\xc3\xa0' <===> Català b'\xd0\xa7\xd3\x91\xd0\xb2\xd0\xb0\xd1\x88\xd0\xbb\xd0\xb0' <===> Чӑвашла b'\xc4\x8ce\xc5\xa1tina' <===> Čeština b'Cymraeg' <===> Cymraeg b'Dansk' <===> Dansk b'Deutsch' <===> Deutsch b'Eesti' <===> Eesti b'\xce\x95\xce\xbb\xce\xbb\xce\xb7\xce\xbd\xce\xb9\xce\xba\xce\xac' <===> Ελληνικά b'Espa\xc3\xb1ol' <===> Español b'Esperanto' <===> Esperanto b'\xd9\x81\xd8\xa7\xd8\xb1\xd8\xb3\xdb\x8c' <===> فارسی b'Fran\xc3\xa7ais' <===> Français b'Frysk' <===> Frysk b'Gaelg' <===> Gaelg b'G\xc3\xa0idhlig' <===> Gàidhlig b'Galego' <===> Galego b'\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4' <===> 한국어 b'\xd5\x80\xd5\xa1\xd5\xb5\xd5\xa5\xd6\x80\xd5\xa5\xd5\xb6' <===> Հայերեն b'\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80' <===> हिन्दी b'Hrvatski' <===> Hrvatski b'Ido' <===> Ido b'Interlingua' <===> Interlingua b'Italiano' <===> Italiano b'\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa' <===> עברית b'\xe0\xb2\x95\xe0\xb2\xa8\xe0\xb3\x8d\xe0\xb2\xa8\xe0\xb2\xa1' <===> ಕನ್ನಡ b'Kapampangan' <===> Kapampangan b'\xe1\x83\xa5\xe1\x83\x90\xe1\x83\xa0\xe1\x83\x97\xe1\x83\xa3\xe1\x83\x9a\xe1\x83\x98' <===> ქართული b'\xd2\x9a\xd0\xb0\xd0\xb7\xd0\xb0\xd2\x9b\xd1\x88\xd0\xb0' <===> Қазақша b'Krey\xc3\xb2l ayisyen' <===> Kreyòl ayisyen b'Latga\xc4\xbcu' <===> Latgaļu b'Latina' <===> Latina b'Latvie\xc5\xa1u' <===> Latviešu b'L\xc3\xabtzebuergesch' <===> Lëtzebuergesch b'Lietuvi\xc5\xb3' <===> Lietuvių b'Magyar' <===> Magyar b'\xd0\x9c\xd0\xb0\xd0\xba\xd0\xb5\xd0\xb4\xd0\xbe\xd0\xbd\xd1\x81\xd0\xba\xd0\xb8' <===> Македонски b'Malti' <===> Malti b'\xe0\xa4\xae\xe0\xa4\xb0\xe0\xa4\xbe\xe0\xa4\xa0\xe0\xa5\x80' <===> मराठी b'\xe1\x83\x9b\xe1\x83\x90\xe1\x83\xa0\xe1\x83\x92\xe1\x83\x90\xe1\x83\x9a\xe1\x83\xa3\xe1\x83\xa0\xe1\x83\x98' <===> მარგალური b'\xd9\x85\xd8\xa7\xd8\xb2\xd9\x90\xd8\xb1\xd9\x88\xd9\x86\xdb\x8c' <===> مازِرونی b'Bahasa Melayu' <===> Bahasa Melayu b'\xd0\x9c\xd0\xbe\xd0\xbd\xd0\xb3\xd0\xbe\xd0\xbb' <===> Монгол b'Nederlands' <===> Nederlands b'\xe0\xa4\xa8\xe0\xa5\x87\xe0\xa4\xaa\xe0\xa4\xbe\xe0\xa4\xb2 \xe0\xa4\xad\xe0\xa4\xbe\xe0\xa4\xb7\xe0\xa4\xbe' <===> नेपाल भाषा b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e' <===> 日本語 b'Norsk bokm\xc3\xa5l' <===> Norsk bokmål b'Nouormand' <===> Nouormand b'Occitan' <===> Occitan b'O\xca\xbbzbekcha/\xd1\x9e\xd0\xb7\xd0\xb1\xd0\xb5\xd0\xba\xd1\x87\xd0\xb0' <===> Oʻzbekcha/ўзбекча b'\xe0\xa8\xaa\xe0\xa9\xb0\xe0\xa8\x9c\xe0\xa8\xbe\xe0\xa8\xac\xe0\xa9\x80' <===> ਪੰਜਾਬੀ b'\xd9\xbe\xd9\x86\xd8\xac\xd8\xa7\xd8\xa8\xdb\x8c' <===> پنجابی b'\xd9\xbe\xda\x9a\xd8\xaa\xd9\x88' <===> پښتو b'Plattd\xc3\xbc\xc3\xbctsch' <===> Plattdüütsch b'Polski' <===> Polski b'Portugu\xc3\xaas' <===> Português b'Rom\xc3\xa2n\xc4\x83' <===> Română b'Romani' <===> Romani b'\xd0\xa0\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9' <===> Русский b'Seeltersk' <===> Seeltersk b'Shqip' <===> Shqip b'Simple English' <===> Simple English b'Sloven\xc4\x8dina' <===> Slovenčina b'\xda\xa9\xd9\x88\xd8\xb1\xd8\xaf\xdb\x8c\xdb\x8c \xd9\x86\xd8\xa7\xd9\x88\xdb\x95\xd9\x86\xd8\xaf\xdb\x8c' <===> کوردیی ناوەندی b'\xd0\xa1\xd1\x80\xd0\xbf\xd1\x81\xd0\xba\xd0\xb8 / srpski' <===> Српски / srpski b'Suomi' <===> Suomi b'Svenska' <===> Svenska b'Tagalog' <===> Tagalog b'\xe0\xae\xa4\xe0\xae\xae\xe0\xae\xbf\xe0\xae\xb4\xe0\xaf\x8d' <===> தமிழ் b'\xe0\xb8\xa0\xe0\xb8\xb2\xe0\xb8\xa9\xe0\xb8\xb2\xe0\xb9\x84\xe0\xb8\x97\xe0\xb8\xa2' <===> ภาษาไทย b'Taqbaylit' <===> Taqbaylit b'\xd0\xa2\xd0\xb0\xd1\x82\xd0\xb0\xd1\x80\xd1\x87\xd0\xb0/tatar\xc3\xa7a' <===> Татарча/tatarça b'\xe0\xb0\xa4\xe0\xb1\x86\xe0\xb0\xb2\xe0\xb1\x81\xe0\xb0\x97\xe0\xb1\x81' <===> తెలుగు b'\xd0\xa2\xd0\xbe\xd2\xb7\xd0\xb8\xd0\xba\xd3\xa3' <===> Тоҷикӣ b'T\xc3\xbcrk\xc3\xa7e' <===> Türkçe b'\xd0\xa3\xd0\xba\xd1\x80\xd0\xb0\xd1\x97\xd0\xbd\xd1\x81\xd1\x8c\xd0\xba\xd0\xb0' <===> Українська b'\xd8\xa7\xd8\xb1\xd8\xaf\xd9\x88' <===> اردو b'Ti\xe1\xba\xbfng Vi\xe1\xbb\x87t' <===> Tiếng Việt b'V\xc3\xb5ro' <===> Võro b'\xe6\x96\x87\xe8\xa8\x80' <===> 文言 b'\xe5\x90\xb4\xe8\xaf\xad' <===> 吴语 b'\xd7\x99\xd7\x99\xd6\xb4\xd7\x93\xd7\x99\xd7\xa9' <===> ייִדיש b'\xe4\xb8\xad\xe6\x96\x87' <===> 中文 现代的计算机本质上是一个巨大的开关阵列。计算机用电来触发这些开关的开启或关闭。这些开关用 1 表示开启、有电、接通,用 0 表示关闭、没电、切断。我们称这些 1 和 0 为『位』(bit)。 我们用「字节」(byte)表示一个 \(8\) 位(0 和 1)的序列。 美国信息交换标准代码(ASCII)把数字与字母相对应。 0b1011010 # 二进制的 90 90 ord('Z') # 字母 Z 的对应数字 90 chr(90) 'Z' 有了 ASCII 这个约定,我们就可以用 \(8\) 位(也就是 \(1\) 字节)编码一个字符,然后就可以把字符串联在一起,合成一个单词。例如:'Zed A. Shaw' 我们可以用一组字节序列来表示:[90, 101, 100, 32, 65, 46, 32, 83, 104, 97, 119],代码如下: [ord(name) for name in 'Zed A. Shaw'] [90, 101, 100, 32, 65, 46, 32, 83, 104, 97, 119] 再说一遍,类似上面的约定的序列最终还是会转换为开关的通断。 一个字节可以存放 \(256\)(即 \(2 ^ 8\))个数字:\[ 0 \sim 255 \; \text{或} \; 00000000 \sim 11111111 \] 为了统一编码方式,Unicode(universal encoding)便出现了。 UTF-8(Unicode Transformation Format 8 Bits):把大部分常用的字符用 \(8\) 位编码,如果需要编码更多的字符,就「逃」去使用更大的数;这种编码方式很大的程度节省了存储空间。 在 python 中,string 是 UTF-8 编码的字符序列,是显示和处理文本的基础。bytes 则是 python 用来存储 UTF-8 字符串的原始字节序列,使用b"" 表示字节串。 因而,如果要处理的是原始字节串(bytes),那么你就需要通过 .decode() 来获取字符串(string)。原始字节串不包含编码方式,它们就是字节序列,一堆数字而已,所以你必须告诉 python 把它解码成 UTF 字符串。即 解码字节串,编码字符串 (decode bytes, encode strings) 短路逻辑 任何以 False 开头的 and 语句都会直接处理成 False,不会继续检查后面的语句。 任何包含 True 的 or 语句,只要处理到 True,就不会继续向下推算,而是直接返回 True 了。 Python 只会运行它遇到的是 True 的第一个块,所以如果有多个 elif 块,只有第一个为 True 的块才会运行。 from sys import exit def glod_room(): print("This room is full of gold. How much do you take?") choice = input('> ') if '0' in choice or '1' in choice: how_much = int(choice) else: dead("Man, learn to type a number.") if how_much < 50: print("Nice, you're not greedy, you win!") exit(0) else: dead("You greedy bastard!") def dead(why): print(why, "Good Job!") exit(0) glod_room() This room is full of gold. How much do you take? > 7 Man, learn to type a number. Good Job! An exception has occurred, use %tb to see the full traceback. SystemExit: 0 C:\ProgramData\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py:2971: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D. warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1) exit(0) 可以中止某个程序,而其中的数字参数用来表示程序是否是遇到错误而中止的。exit(0) 表示程序正常退出。 if 语句的规则 每一条 if 语句必须包含一个 else。(为了防止出现未考虑到的条件出现) 若这个 else 永远都不应该被执行到,因为它本身没有任何意义,则必须在 else 语句后面使用一个叫 die 的函数,让它打印出出错消息并且「死」给你看。 if 语句的嵌套不要超过两层,最好尽量保持只有一层。 将 if 语句当作段落来对待,其中的每一个 if-elif-else 组合就跟一个段落的句子组合一样。在这种组合的最前面和最后面留一个空行以作区分。 你的布尔测试应该很简单,如果它们很复杂,你需要在函数里面将它们的运算顺序事先放到一个变量里,并且为变量取一个名字。 调试小技巧 最好的调试方法是使用 print 在各个要检查的关键点将变量打印出来,从而检查那里是否有错。 让程序一部分一部分地运行起来。不要等到写了一大堆代码文件后才去运行它们,写一点,运行一点,再修改一点。 .join() & .split() ten_things = 'Apples Oranges Crows Telephone Light Sugar' stuff = ten_things.split(' ') stuff ['Apples', 'Oranges', 'Crows', 'Telephone', 'Light', 'Sugar'] ' & '.join(stuff) 'Apples & Oranges & Crows & Telephone & Light & Sugar' 面向对象(OOP) 练习: import random from urllib.request import urlopen import sys WORD_URL = 'http://learncodethehardway.org/words.txt' WORDS = [] PHRASES = { 'class %%%(%%%):': "Make a class named %%% that is-a %%%.", 'class %%%(object):\n\tdef __init__(self, ***)': 'class %%% has-a __init__ that takes self and *** params.', 'class %%%(object):\n\tdef ***(self, @@@)': 'class %%% has-a function *** that takes self ang @@@ params.', '*** = %%%()': 'Set *** to an instance of class %%%', "***.***(@@@)": 'From *** get *** function, call it with params self, @@@.', "***.*** = '***'": "From *** get the attribute and set it to '***'." } # do they want to drill phrases(短语) first if len(sys.argv) == 2 and sys.argv[1] == 'english': PHRASES_FIRST = True else: PHRASES_FIRST = False # load up the words from website for word in urlopen(WORD_URL).readlines(): WORDS.append(str(word.strip(), encoding = 'utf-8')) def convert(snippet, phrase): class_names = [w.capitalize() for w in random.sample(WORDS, snippet.count("%%%"))] other_names = random.sample(WORDS, snippet.count("***")) results = [] param_names = [] for i in range(0, snippet.count('@@@')): param_count = random.randint(1, 3) param_names.append(', '.join(random.sample(WORDS, param_count))) for sentence in snippet, phrase: result = sentence[:] # fake class names for word in class_names: result = result.replace("%%%", word, 1) # fake other names for word in other_names: result = result.replace("***", word, 1) # fake parameter lists for word in param_names: result = result.replace("@@@", word, 1) results.append(result) return results # keep going until they hit CTRAL-D try: while True: snippets = list(PHRASES.keys()) random.shuffle(snippets) for snippet in snippets: phrase = PHRASES[snippet] question, answer = convert(snippet, phrase) if PHRASES_FIRST: question, answer = question, answer print(question) input("> ") print(f"ANSWER: {answer}\n\n") except EOFError: print('\nBye') 关于 class Name(object) 在 python 3 中,你不需要在类名后面添加 object,但是 python 圈子的人相信「显式优于隐式」,所以,一般还是需要写上。 关于 super() 在 python 3 中 super() 等价于 super(Child, self) class Parent(object): def altered(self): print("PARENT altered()") class Child(Parent): def altered(self): print("CHILD, BEFORE PARENT altered()") super(Child, self).altered() print("CHILD, AFTER PARENT altered()") dad = Parent() son = Child() dad.altered() print('=='* 20) son.altered() PARENT altered() ======================================== CHILD, BEFORE PARENT altered() PARENT altered() CHILD, AFTER PARENT altered() class Parent(object): def altered(self): print("PARENT altered()") class Child(Parent): def altered(self): print("CHILD, BEFORE PARENT altered()") super().altered() print("CHILD, AFTER PARENT altered()") dad = Parent() son = Child() dad.altered() print('=='* 20) son.altered() PARENT altered() ======================================== CHILD, BEFORE PARENT altered() PARENT altered() CHILD, AFTER PARENT altered() 探寻有趣之事!

优秀的个人博客,低调大师

Java基础知识总结

一:java概述(快速浏览): 1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒、PDA等的微处理器; 1994年将Oak语言更名为Java; Java的三种技术架构: JAVAEE:Java PlatformEnterpriseEdition,开发企业环境下的应用程序,主要针对web程序开发; JAVASE:Java Platform Standard Edition,完成桌面应用程序的开发,是其它两者的基础; JAVAME:Java PlatformMicro Edition,开发电子消费产品和嵌入式设备,如手机中的程序; 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具和jre。 2,JRE:Java Runtime Environment,java程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。 3,配置环境变量:让java jdk\bin目录下的工具,可以在任意目录下运行,原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。 环境变量的配置: 1):永久配置方式:JAVA_HOME=%安装路径%\Java\jdk path=%JAVA_HOME%\bin 2):临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin 特点:系统默认先去当前路径下找要执行的程序,如果没有,再去path中设置的路径下找。 classpath的配置: 1):永久配置方式:classpath=.;c:\;e:\ 2):临时配置方式:set classpath=.;c:\;e:\ 注意:在定义classpath环境变量时,需要注意的情况 如果没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件; 如果指定了classpath,那么会在指定的目录下查找要运行的类文件。 还会在当前目录找吗?两种情况: 1):如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。 2):如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。 一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序。 4,javac命令和java命令做什么事情呢? 要知道java是分两部分的:一个是编译,一个是运行。 javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。 java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行. 一个文件要被执行,必须要有一个执行的起始点,这个起始点就是main函数. ---------------------------------------------------------------------------------------------- 二:java语法基础(快速浏览): 1,关键字:其实就是某种语言赋予了特殊含义的单词。 保留字:其实就是还没有赋予特殊含义,但是准备日后要使用过的单词。 2,标示符:其实就是在程序中自定义的名词。比如类名,变量名,函数名。包含0-9、a-z、$、_; 注意: 1),数字不可以开头。 2),不可以使用关键字。 3,常量:是在程序中的不会变化的数据。 4,变量:其实就是内存中的一个存储空间,用于存储常量数据。 作用:方便于运算。因为有些数据不确定。所以确定该数据的名词和存储空间。 特点:变量空间可以重复使用。 什么时候定义变量?只要是数据不确定的时候,就定义变量。 变量空间的开辟需要什么要素呢? 1,这个空间要存储什么数据?数据类型。 2,这个空间叫什么名字啊?变量名称。 3,这个空间的第一次的数据是什么? 变量的初始化值。 变量的作用域和生存期: 变量的作用域: 作用域从变量定义的位置开始,到该变量所在的那对大括号结束; 生命周期: 变量从定义的位置开始就在内存中活了; 变量到达它所在的作用域的时候就在内存中消失了; 数据类型: 1):基本数据类型:byte、short、int、long、float、double、char、boolean 2):引用数据类型: 数组、类、接口。 级别从低到高为:byte,char,short(这三个平级)-->int-->float-->long-->double 自动类型转换:从低级别到高级别,系统自动转的; 强制类型转换:什么情况下使用?把一个高级别的数赋给一个别该数的级别低的变量; 运算符号: 1)、算术运算符。 + - * / %%:任何整数模2不是0就是1,所以只要改变被模数就可以实现开关运算。 +:连接符。 ++,-- 2)、赋值运算符。 = += -= *= /= %= 3)、比较运算符。 特点:该运算符的特点是:运算完的结果,要么是true,要么是false。 4)、逻辑运算符。 & | ^ ! && || 逻辑运算符除了 ! 外都是用于连接两个boolean类型表达式。 &: 只有两边都为true结果是true。否则就是false。 |:只要两边都为false结果是false,否则就是true ^:异或:和或有点不一样。 两边结果一样,就为false。 两边结果不一样,就为true. & 和 &&区别:& :无论左边结果是什么,右边都参与运算。 &&:短路与,如果左边为false,那么右边不参数与运算。 | 和|| 区别:|:两边都运算。 ||:短路或,如果左边为true,那么右边不参与运算。 5)、位运算符:用于操作二进制位的运算符。 & | ^ << >> >>>(无符号右移) 练习:对两个变量的数据进行互换。不需要第三方变量。 int a = 3,b = 5;-->b = 3,a = 5; a = a + b; a = 8; b = a - b; b = 3; a = a - b; a = 5; a = a ^ b;// b = a ^ b;//b = a ^ b ^ b = a a = a ^ b;//a = a ^ b ^ a = b; 练习:高效的算出 2*8 = 2<<3; 5,语句。 Ifswitchdowhilewhilefor 这些语句什么时候用? 1)、当判断固定个数的值的时候,可以使用if,也可以使用switch。 但是建议使用switch,效率相对较高。 switch(变量){ case 值:要执行的语句;break; … default:要执行的语句; } 工作原理:用小括号中的变量的值依次和case后面的值进行对比,和哪个case后面的值相同了 就执行哪个case后面的语句,如果没有相同的则执行default后面的语句; 细节:1):break是可以省略的,如果省略了就一直执行到遇到break为止; 2):switch 后面的小括号中的变量应该是byte,char,short,int四种类型中的一种; 3):default可以写在switch结构中的任意位置;如果将default语句放在了第一行,则不管expression与case中的value是否匹配,程序会从default开始执行直到第一个break出现。 2)、当判断数据范围,获取判断运算结果boolean类型时,需要使用if。 3)、当某些语句需要执行很多次时,就用循环结构。 while和for可以进行互换。 区别在于:如果需要定义变量控制循环次数。建议使用for。因为for循环完毕,变量在内存中释放。 break:作用于switch ,和循环语句,用于跳出,或者称为结束。 break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字即可,这个名字称之为标号。 continue:只作用于循环结构,继续循环用的。 作用:结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。 6,函 数:为了提高代码的复用性,可以将其定义成一个单独的功能,该功能的体现就是java中的函数。函数就是体现之一。 java中的函数的定义格式: 修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数1,…){ 执行语句; return 返回值; } 当函数没有具体的返回值时,返回的返回值类型用void关键字表示。 如果函数的返回值类型是void时,return语句可以省略不写的,系统会帮你自动加上。 return的作用:结束函数。结束功能。 如何定义一个函数? 函数其实就是一个功能,定义函数就是实现功能,通过两个明确来完成: 1)、明确该功能的运算完的结果,其实是在明确这个函数的返回值类型。 2)、在实现该功能的过程中是否有未知内容参与了运算,其实就是在明确这个函数的参数列表(参数类型&参数个数)。 函数的作用: 1)、用于定义功能。 2)、用于封装代码提高代码的复用性。 注意:函数中只能调用函数,不能定义函数。 主函数: 1)、保证该类的独立运行。 2)、因为它是程序的入口。 3)、因为它在被jvm调用。 函数定义名称是为什么呢? 答:1)、为了对该功能进行标示,方便于调用。 2)、为了通过名称就可以明确函数的功能,为了增加代码的阅读性。 重载的定义是:在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了。 如何区分重载:当函数同名时,只看参数列表。和返回值类型没关系。 7,数 组:用于存储同一类型数据的一个容器。好处:可以对该容器中的数据进行编号,从0开始。数组用于封装数据,就是一个具体的实体。 如何在java中表现一个数组呢?两种表现形式。 1)、元素类型[] 变量名 = new 元素类型[元素的个数]; 2)、元素类型[] 变量名 = {元素1,元素2...}; 元素类型[] 变量名 = new 元素类型[]{元素1,元素2...}; --------------------------------------------------------- //二分查找法。必须有前提:数组中的元素要有序。 public static int halfSeach_2(int[] arr,int key){ int min,max,mid; min = 0; max = arr.length-1; mid = (max+min)>>1;//(max+min)/2; while(arr[mid]!=key){ if(key>arr[mid]){ min = mid + 1; } else if(key<arr[mid]){ max = mid - 1; } if(max<min) return -1; mid = (max+min)>>1; } return mid; } --------------------------------------------------------- java分了5片内存。 1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。 栈:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 ); 只要数据运算完成所在的区域结束,该数据就会被释放。 堆:用于存储数组和对象,也就是实体。啥是实体啊?就是用于封装多个数据的。 1:每一个实体都有内存首地址值。 2:堆内存中的变量都有默认初始化值。因为数据类型不同,值也不一样。 3:垃圾回收机制。 ---------------------------------------------------------------------------------------------- 三:面向对象(理解) 以把大象装进冰箱为例子谈谈面向过程和面向对象的区别: 面向过程(代表语言c语言): 1、打开冰箱。 2、装进大象。 3、关上冰箱。 **开发时重点在于定义函数(过程;打开,装进,关上)。 面向对象(代表语言java语言): 1、冰箱打开。 2、冰箱储存。 3、冰箱关闭。 **开发时重点在于定义对象(对象;冰箱)。 特点: 1:将复杂的事情简单化。 2:面向对象将以前的过程中的执行者,变成了指挥者。 3:面向对象这种思想是符合现在人们思考习惯的一种思想。 过程和对象在我们的程序中是如何体现的呢?过程其实就是函数;对象是将函数等一些内容进行了封装。 匿名对象使用场景: 1:当对方法只进行一次调用的时候,可以使用匿名对象。 2:当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。 在类中定义其实都称之为成员。成员有两种: 1:成员变量:其实对应的就是事物的属性。 2:成员函数:其实对应的就是事物的行为。 所以,其实定义类,就是在定义成员变量和成员函数。但是在定义前,必须先要对事物进行属性和行为的分析,才可以用代码来体现。 privateint age;//私有的访问权限最低,只有在本类中的访问有效。 注意:私有仅仅是封装的一种体现形式而已。 私有的成员:其他类不能直接创建对象访问,所以只有通过本类对外提供具体的访问方式来完成对私有的访问,可以通过对外提供函数的形式对其进行访问。 好处:可以在函数中加入逻辑判断等操作,对数据进行判断等操作。 总结:开发时,记住,属性是用于存储数据的,直接被访问,容易出现安全隐患,所以,类中的属性通常被私有化,并对外提供公共的访问方法。 这个方法一般有两个,规范写法:对于属性 xxx,可以使用setXXX(),getXXX()对其进行操作。 类中怎么没有定义主函数呢? 注意:主函数的存在,仅为该类是否需要独立运行,如果不需要,主函数是不用定义的。 主函数的解释:保证所在类的独立运行,是程序的入口,被jvm调用。 成员变量和局部变量的区别: 1:成员变量直接定义在类中。 局部变量定义在方法中,参数上,语句中。 2:成员变量在这个类中有效。 局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。 3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。 局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。 构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。 特点: 1:该函数的名称和所在类的名称相同。 2:不需要定义返回值类型。 3:该函数没有具体的返回值。 记住:所有对象创建时,都需要初始化才可以使用。 注意事项:一个类在定义时,如果没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,为了方便该类创建对象,完成初始化。如果在类中自定义了构造函数,那么默认的构造函数就没有了。 一个类中,可以有多个构造函数,因为它们的函数名称都相同,所以只能通过参数列表来区分。所以,一个类中如果出现多个构造函数。它们的存在是以重载体现的。 构造函数和一般函数有什么区别呢? 1:两个函数定义格式不同。 2:构造函数是在对象创建时,就被调用,用于初始化,而且初始化动作只执行一次。 一般函数,是对象创建后,需要调用才执行,可以被调用多次。 什么时候使用构造函数呢? 分析事物时,发现具体事物一出现,就具备了一些特征,那就将这些特征定义到构造函数内。 构造代码块和构造函数有什么区别? 构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。 构造函数:是给与之对应的对象进行初始化。它具有针对性。 Person p = new Person(); 创建一个对象都在内存中做了什么事情? 1:先将硬盘上指定位置的Person.class文件加载进内存。 2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p。 3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new 4:在该实体空间中进行属性的空间分配,并进行了默认初始化。 5:对空间中的属性进行显示初始化。 6:进行实体的构造代码块初始化。 7:调用该实体对应的构造函数,进行构造函数初始化。() 8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象) -------------------------------------------------------------------------------------------- 封 装(面向对象特征之一): 是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。 好处:将变化隔离;便于使用;提高重用性;安全性。 封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。 this:代表对象。就是所在函数所属对象的引用。 this到底代表什么呢?哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。 开发时,什么时候使用this呢? 在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。 this 还可以用于构造函数间的调用。 调用格式:this(实际参数); this对象后面跟上. 调用的是成员属性和成员方法(一般方法); this对象后面跟上 () 调用的是本类中的对应参数的构造函数。 注意:用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。 static:关键字,是一个修饰符,用于修饰成员(成员变量和成员函数)。 特点: 1,想要实现对象中的共性数据的对象共享。可以将这个数据进行静态修饰。 2,被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。 3,静态随着类的加载而加载。而且优先于对象存在。 弊端: 1,有些数据是对象特有的数据,是不可以被静态修饰的。因为那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。所以,在定义静态时,必须要明确,这个数据是否是被对象所共享的。 2,静态方法只能访问静态成员,不可以访问非静态成员。 因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。 3,静态方法中不能使用this,super关键字。 因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。 4,主函数是静态的。 什么时候定义静态成员呢?或者说:定义成员时,到底需不需要被静态修饰呢? 成员分两种: 1,成员变量。(数据共享时静态化) 该成员变量的数据是否是所有对象都一样: 如果是,那么该变量需要被静态修饰,因为是共享的数据。 如果不是,那么就说这是对象的特有数据,要存储到对象中。 2,成员函数。(方法中没有调用特有数据时就定义成静态) 如果判断成员函数是否需要被静态修饰呢? 只要参考,该函数内是否访问了对象中的特有数据: 如果有访问特有数据,那方法不能被静态修饰。 如果没有访问过特有数据,那么这个方法需要被静态修饰。 成员变量和静态变量的区别: 1,成员变量所属于对象。所以也称为实例变量。 静态变量所属于类。所以也称为类变量。 2,成员变量存在于堆内存中。 静态变量存在于方法区中。 3,成员变量随着对象创建而存在。随着对象被回收而消失。 静态变量随着类的加载而存在。随着类的消失而消失。 4,成员变量只能被对象所调用 。 静态变量可以被对象调用,也可以被类名调用。 所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。 静态的注意:静态的生命周期很长。 静态代码块:就是一个有静态关键字标示的一个代码块区域。定义在类中。 作用:可以完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。 Public:访问权限最大。 static:不需要对象,直接类名即可。 void:主函数没有返回值。 Main:主函数特定的名称。 (String[] args):主函数的参数,是一个字符串数组类型的参数,jvm调用main方法时,传递的实际参数是 new String[0]。 jvm默认传递的是长度为0的字符串数组,我们在运行该类时,也可以指定具体的参数进行传递。可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。 静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块à构造代码块à构造函数; 生成Java帮助文档:命令格式:javadoc–d 文件夹名–auther–version *.java /** //格式 *类描述 *@author 作者名 *@version版本号 */ /** *方法描述 *@param 参数描述 *@return 返回值描述 */ --------------------------------------------------------------------------------------------- 设计模式: 解决问题最行之有效的思想。是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 java中有23种设计模式: 单例设计模式: 解决的问题:保证一个类在内存中的对象唯一性。 比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。 Runtime()方法就是单例设计模式进行设计的。 如何保证对象唯一性呢? 思想: 1,不让其他程序创建该类对象。 2,在本类中创建一个本类对象。 3,对外提供方法,让其他程序获取这个对象。 步骤: 1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象; 2,就在类中创建一个本类的对象; 3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控) 代码体现: 1,私有化构造函数; 2,创建私有并静态的本类对象; 3,定义公有并静态的方法,返回该对象。 --------------------------------------------- //饿汉式 class Single{ private Single(){} //私有化构造函数。 private static Single s = new Single(); //创建私有并静态的本类对象。 public static Single getInstance(){ //定义公有并静态的方法,返回该对象。 return s; } } --------------------------------------------- //懒汉式:延迟加载方式。 class Single2{ private Single2(){} private static Single2 s = null; public static Single2 getInstance(){ if(s==null) s = new Single2(); return s; } } ------------------------------------------------------------------------------------------------- 继 承(面向对象特征之一) 好处: 1:提高了代码的复用性。 2:让类与类之间产生了关系,提供了另一个特征多态的前提。 父类的由来:其实是由多个类不断向上抽取共性内容而来的。 java中对于继承,java只支持单继承。java虽然不直接支持多继承,但是保留了这种多继承机制,进行改良。 单继承:一个类只能有一个父类。 多继承:一个类可以有多个父类。 为什么不支持多继承呢? 因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?因为父类中的方法中存在方法体。 但是java支持多重继承。A继承B B继承C C继承D。 多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。 所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。 简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。 子父类出现后,类中的成员都有了哪些特点: 1:成员变量。 当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。 如果想要调用父类中的属性值,需要使用一个关键字:super This:代表是本类类型的对象引用。 Super:代表是子类所属的父类中的内存空间引用。 注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。 2:成员函数。 当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是函数的另一个特性:覆盖(复写,重写) 什么时候使用覆盖呢?当一个类的功能内容需要修改时,可以通过覆盖来实现。 3:构造函数。 发现子类构造函数运行时,先运行了父类的构造函数。为什么呢? 原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super(); super():表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。 为什么子类对象初始化时,都需要调用父类中的函数?(为什么要在子类构造函数的第一行加入这个super()?) 因为子类继承父类,会继承到父类中的数据,所以必须要看父类是如何对自己的数据进行初始化的。所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。 注意:子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super(); 如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。 如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。 问题:super()和this()是否可以同时出现的构造函数中。 两个语句只能有一个定义在第一行,所以只能出现其中一个。 super()或者this():为什么一定要定义在第一行? 因为super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。 继承的细节: 什么时候使用继承呢? 当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。 英文书中,所属关系:" is a " 注意:不要仅仅为了获取其他类中的已有成员进行继承。 所以判断所属关系,可以简单看,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。 细节二: 在方法覆盖时,注意两点: 1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限可以实现继承。否则,编译失败。 2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖) 继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。 这时如何解决问题呢?介绍一个关键字,final:最终。 final特点: 1:这个关键字是一个修饰符,可以修饰类,方法,变量。 2:被final修饰的类是一个最终类,不可以被继承。 3:被final修饰的方法是一个最终方法,不可以被覆盖。 4:被final修饰的变量是一个常量,只能赋值一次。 其实这样的原因的就是给一些固定的数据起个阅读性较强的名称。 不加final修饰不是也可以使用吗?那么这个值是一个变量,是可以更改的。加了final,程序更为严谨。常量名称定义时,有规范,所有字母都大写,如果由多个单词组成,中间用_连接。 抽象类:abstract 抽象:不具体,看不明白。抽象类表象体现。 在不断抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所标示,声明为抽象方法。 抽象方法所在类一定要标示为抽象类,也就是说该类需要被abstract关键字所修饰。 抽象类的特点: 1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。 2:抽象方法只定义方法声明,并不定义方法实现。 3:抽象类不可以被创建对象(实例化)。 4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。 抽象类的细节: 1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。 2:抽象类中是否可以定义非抽象方法? 可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。 3:抽象关键字abstract和哪些不可以共存?final ,private , static 4:抽象类中可不可以不定义抽象方法?可以。抽象方法目的仅仅为了不让该类创建对象。 ----------------------------------------------------------------------------------------------- 模板方法设计模式: 解决的问题:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。 abstract class GetTime{ publicfinalvoid getTime(){//此功能如果不需要复写,可加final限定 long start = System.currentTimeMillis(); code();//不确定的功能部分,提取出来,通过抽象方法实现 long end = System.currentTimeMillis(); System.out.println("毫秒是:"+(end-start)); } publicabstractvoid code();//抽象不确定的功能,让子类复写实现 } class SubDemo extends GetTime{ public void code(){//子类复写功能方法 for(int y=0; y<1000; y++){ System.out.println("y"); } } } --------------------------------------------------------------------------------------------- 接 口: 1:是用关键字interface定义的。 2:接口中包含的成员,最常见的有全局常量、抽象方法。 注意:接口中的成员都有固定的修饰符。 成员变量:public static final 成员方法:public abstract interfaceInter{ public static finalint x = 3; public abstractvoid show(); } 3:接口中有抽象方法,说明接口不可以实例化。接口的子类必须实现了接口中所有的抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。 4:类与类之间存在着继承关系,类与接口中间存在的是实现关系。 继承用extends ;实现用implements ; 5:接口和类不一样的地方,就是,接口可以被多实现,这就是多继承改良后的结果。java将多继承机制通过多现实来体现。 6:一个类在继承另一个类的同时,还可以实现多个接口。所以接口的出现避免了单继承的局限性。还可以将类进行功能的扩展。 7:其实java中是有多继承的。接口与接口之间存在着继承关系,接口可以多继承接口。 接口都用于设计上,设计上的特点:(可以理解主板上提供的接口) 1:接口是对外提供的规则。 2:接口是功能的扩展。 3:接口的出现降低了耦合性。 抽象类与接口: 抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。 接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。 抽象类和接口的共性:都是不断向上抽取的结果。 抽象类和接口的区别: 1:抽象类只能被继承,而且只能单继承。 接口需要被实现,而且可以多实现。 2:抽象类中可以定义非抽象方法,子类可以直接继承使用。 接口中都有抽象方法,需要子类去实现。 3:抽象类使用的是 is a 关系。 接口使用的 like a 关系。 4:抽象类的成员修饰符可以自定义。 接口中的成员修饰符是固定的。全都是public的。 在开发之前,先定义规则,A和B分别开发,A负责实现这个规则,B负责使用这个规则。至于A是如何对规则具体实现的,B是不需要知道的。这样这个接口的出现就降低了A和B直接耦合性。 ------------------------------------------------------------------------------------------------ 多 态(面向对象特征之一): 函数本身就具备多态性,某一种事物有不同的具体的体现。 体现:父类引用或者接口的引用指向了自己的子类对象。//Animal a = new Cat(); 多态的好处:提高了程序的扩展性。 多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性) 多态的前提: 1:必须要有关系,比如继承、或者实现。 2:通常会有覆盖操作。 多态的出现思想上也做着变化:以前是创建对象并指挥对象做事情。有了多态以后,我们可以找到对象的共性类型,直接操作共性类型做事情即可,这样可以指挥一批对象做事情,即通过操作父类或接口实现。 -------------------------------------------------------------- class 毕姥爷{ void 讲课(){ System.out.println("企业管理"); } void 钓鱼(){ System.out.println("钓鱼"); } } class 毕老师 extends 毕姥爷{ void 讲课(){ System.out.println("JAVA"); } void 看电影(){ System.out.println("看电影"); } } class { public static void main(String[] args) { 毕姥爷 x = new 毕老师();//毕老师对象被提升为了毕姥爷类型。 //x.讲课(); //x.看电影();//错误. 毕老师 y = (毕老师)x;//将毕姥爷类型强制转换成毕老师类型。 y.看电影();//在多态中,自始自终都是子类对象在做着类型的变化。 } } --------------------------------------------------------------- 如果想用子类对象的特有方法,如何判断对象是哪个具体的子类类型呢? 可以可以通过一个关键字instanceof ;//判断对象是否实现了指定的接口或继承了指定的类 格式:<对象 instanceof 类型> ,判断一个对象是否所属于指定的类型。 StudentinstanceofPerson = true;//student继承了person类 多态在子父类中的成员上的体现的特点: 1,成员变量:在多态中,子父类成员变量同名。 在编译时期:参考的是引用型变量所属的类中是否有调用的成员。(编译时不产生对象,只检查语法错误) 运行时期:也是参考引用型变量所属的类中是否有调用的成员。 简单一句话:无论编译和运行,成员变量参考的都是引用变量所属的类中的成员变量。 再说的更容易记忆一些:成员变量---编译运行都看 = 左边。 2,成员函数。 编译时期:参考引用型变量所属的类中是否有调用的方法。 运行事情:参考的是对象所属的类中是否有调用的方法。 为什么是这样的呢?因为在子父类中,对于一模一样的成员函数,有一个特性:覆盖。 简单一句:成员函数,编译看引用型变量所属的类,运行看对象所属的类。 更简单:成员函数---编译看 = 左边,运行看 = 右边。 3,静态函数。 编译时期:参考的是引用型变量所属的类中是否有调用的成员。 运行时期:也是参考引用型变量所属的类中是否有调用的成员。 为什么是这样的呢?因为静态方法,其实不所属于对象,而是所属于该方法所在的类。 调用静态的方法引用是哪个类的引用调用的就是哪个类中的静态方法。 简单说:静态函数---编译运行都看 = 左边。 ----------------------------------------------------------------------------------------------- ------java.lang.Object Object:所有类的直接或者间接父类,Java认为所有的对象都具备一些基本的共性内容,这些内容可以不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的就是所有对象都具备的功能。 具体方法: 1,booleanequals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。 而根据对象的属性不同,判断对象是否相同的具体内容也不一样。所以在定义类时,一般都会复写equals方法,建立本类特有的判断对象是否相同的依据。 public boolean equals(Object obj){ if(!(obj instanceof Person)) return false; Person p = (Person)obj; return this.age == p.age; } 2,StringtoString():将对象变成字符串;默认返回的格式:类名@哈希值 =getClass().getName() + '@' + Integer.toHexString(hashCode()) 为了对象对应的字符串内容有意义,可以通过复写,建立该类对象自己特有的字符串表现形式。 public String toString(){ return "person : "+age; } 3,ClassgetClass():获取任意对象运行时的所属字节码文件对象。 4,inthashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。 通常equals,toString,hashCode,在应用中都会被复写,建立具体对象的特有的内容。 ------------------------------------------------------------------------------------------------ 内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。 ----------------------------------------------------- class Outer{ int num = 4; class Inner{ void show(){ System.out.println("inner show run "+num); } } public void method(){ Inner in = new Inner();//创建内部类的对象。 in.show();//调用内部类的方法。 } } ------------------------------------------------------- 当内部类定义在外部类中的成员位置上,可以使用一些成员修饰符修饰 private、static。 1:默认修饰符。 直接访问内部类格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象; Outer.Inner in = new Outer.new Inner();//这种形式很少用。 但是这种应用不多见,因为内部类之所以定义在内部就是为了封装。想要获取内部类对象通常都通过外部类的方法来获取。这样可以对内部类对象进行控制。 2:私有修饰符。 通常内部类被封装,都会被私有化,因为封装性不让其他程序直接访问。 3:静态修饰符。 如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员。 注意;如果内部类中定义了静态成员,那么该内部类必须是静态的。 内部类编译后的文件名为:“外部类名$内部类名.java”; 为什么内部类可以直接访问外部类中的成员呢? 那是因为内部中都持有一个外部类的引用。这个是引用是外部类名.this 内部类可以定义在外部类中的成员位置上,也可以定义在外部类中的局部位置上。 当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。 匿名内部类:没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。 匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。 匿名内部类的使用场景: 当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。 其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。 -------------------------------------------------------- //面试 //1 new Object(){ void show(){ System.out.println("show run"); } }.show(); //2 Object obj = new Object(){ void show(){ System.out.println("show run"); } }; obj.show(); 1和2的写法正确吗?有区别吗?说出原因。 写法是正确,1和2都是在通过匿名内部类建立一个Object类的子类对象。 区别: 第一个可是编译通过,并运行。 第二个编译失败,因为匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提升为了 Object类型,而编译时检查Object类中是否有show方法,所以编译失败。 ------------------------------------------------------- class InnerClassDemo6 { +(static)class Inner{ void show(){} } public void method(){ this.new Inner().show();//可以 } public static void main(String[] args) {//static不允许this This.new Inner().show();//错误,Inner类需要定义成static } } ------------------------------------------------------ interface Inter{ void show(); } class Outer{//通过匿名内部类补足Outer类中的代码。 public static Inter method(){ return new Inter(){ public void show(){} }; } } class InnerClassDemo7 { public static void main(String[] args) { Outer.method().show(); /* Outer.method():意思是:Outer中有一个名称为method的方法,而且这个方法是静态的。 Outer.method().show():当Outer类调用静态的method方法运算结束后的结果又调用了show方法,意味着:method()方法运算完一个是对象,而且这个对象是Inter类型的。 */ function (new Inter(){ public void show(){} });//匿名内部类作为方法的参数进行传递。 } public static void function(Inter in){ in.show(); } } ------------------------------------------------------------------------------------------------ 异 常: 异常:就是不正常。程序在运行时出现的不正常情况。其实就是程序中出现的问题。这个问题按照面向对象思想进行描述,并封装成了对象。因为问题的产生有产生的原因、有问题的名称、有问题的描述等多个属性信息存在。当出现多属性信息最方便的方式就是将这些信息进行封装。异常就是java按照面向对象的思想将问题进行对象封装。这样就方便于操作问题以及处理问题。 出现的问题有很多种,比如角标越界,空指针等都是。就对这些问题进行分类。而且这些问题都有共性内容比如:每一个问题都有名称,同时还有问题描述的信息,问题出现的位置,所以可以不断的向上抽取。形成了异常体系。 --------java.lang.Throwable: Throwable:可抛出的。 |--Error:错误,一般情况下,不编写针对性的代码进行处理,通常是jvm发生的,需要对程序进行修正。 |--Exception:异常,可以有针对性的处理方式 无论是错误还是异常,它们都有具体的子类体现每一个问题,它们的子类都有一个共性,就是都以父类名才作为子类的后缀名。 这个体系中的所有类和对象都具备一个独有的特点;就是可抛性。 可抛性的体现:就是这个体系中的类和对象都可以被throws和throw两个关键字所操作。 ------------------------------------------------------ class ExceptionDemo{ public static void main(String[] args) { //byte[] buf = new byte[1024*1024*700];//java.lang.OutOfMemoryError内存溢出错误 } } ------------------------------------------------------ 在开发时,如果定义功能时,发现该功能会出现一些问题,应该将问题在定义功能时标示出来,这样调用者就可以在使用这个功能的时候,预先给出处理方式。 如何标示呢?通过throws关键字完成,格式:throws 异常类名,异常类名... 这样标示后,调用者,在使用该功能时,就必须要处理,否则编译失败。 处理方式有两种:1、捕捉;2、抛出。 对于捕捉:java有针对性的语句块进行处理。 try { 需要被检测的代码; } catch(异常类 变量名){ 异常处理代码; } fianlly{ 一定会执行的代码; } -------------------------------------------------------- catch (Exception e){//e用于接收try检测到的异常对象。 System.out.println("message:"+e.getMessage());//获取的是异常的信息。 System.out.println("toString:"+e.toString());//获取的是异常的名字+异常的信息。 e.printStackTrace();//打印异常在堆栈中信息;异常名称+异常信息+异常的位置。 } --------------------------------------------------------- 异常处理原则:功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。 特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。 throw 和throws关键字的区别: throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。 throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。 通常情况:函数内容如果有throw,抛出异常对象,并没有进行处理,那么函数上一定要声明,否则编译失败。但是也有特殊情况。 异常分两种: 1:编译时被检查的异常,只要是Exception及其子类都是编译时被检测的异常。 2:运行时异常,其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常。 编译时被检查的异常和运行时异常的区别: 编译被检查的异常在函数内被抛出,函数必须要声明,否编译失败。 声明的原因:是需要调用者对该异常进行处理。 运行时异常如果在函数内被抛出,在函数上不需要声明。 不声明的原因:不需要调用者处理,运行时异常发生,已经无法再让程序继续运行,所以,不让调用处理的,直接让程序停止,由调用者对代码进行修正。 定义异常处理时,什么时候定义try,什么时候定义throws呢? 功能内部如果出现异常,如果内部可以处理,就用try; 如果功能内部处理不了,就必须声明出来,让调用者处理。 自定义异常:当开发时,项目中出现了java中没有定义过的问题时,这时就需要我们按照java异常建立思想,将项目的中的特有问题也进行对象的封装。这个异常,称为自定义异常。 对于除法运算,0作为除数是不可以的。java中对这种问题用ArithmeticException类进行描述。对于这个功能,在我们项目中,除数除了不可以为0外,还不可以为负数。可是负数的部分java并没有针对描述。所以我们就需要自定义这个异常。 自定义异常的步骤: 1:定义一个子类继承Exception或RuntimeException,让该类具备可抛性。 2:通过throw 或者throws进行操作。 异常的转换思想:当出现的异常是调用者处理不了的,就需要将此异常转换为一个调用者可以处理的异常抛出。 try catch finally的几种结合方式: 1, try catch finally 这种情况,如果出现异常,并不处理,但是资源一定关闭,所以try finally集合只为关闭资源。 记住:finally很有用,主要用户关闭资源。无论是否发生异常,资源都必须进行关闭。 System.exit(0);//退出jvm,只有这种情况finally不执行。 当异常出现后,在子父类进行覆盖时,有了一些新的特点: 1:当子类覆盖父类的方法时,如果父类的方法抛出了异常,那么子类的方法要么不抛出异常要么抛出父类异常或者该异常的子类,不能抛出其他异常。 2:如果父类抛出了多个异常,那么子类在覆盖时只能抛出父类的异常的子集。 注意: 如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws。 如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。 常见异常: 1、脚标越界异常(IndexOutOfBoundsException)包括数组、字符串; 空指针异常(NullPointerException) 2、类型转换异常:ClassCastException 3、没有这个元素异常:NullPointerException 4、不支持操作异常; 异常要尽量避免,如果避免不了,需要预先给出处理方式。比如家庭备药,比如灭火器。 ----------------------------------------------------------------------------------------------- 包:定义包用package关键字。 1:对类文件进行分类管理。 2:给类文件提供多层名称空间。 如果生成的包不在当前目录下,需要最好执行classpath,将包所在父目录定义到classpath变量中即可。 一般在定义包名时,因为包的出现是为了区分重名的类。所以包名要尽量唯一。怎么保证唯一性呢?可以使用url域名来进行包名称的定义。 packagepack;//定义了一个包,名称为pack。注意:包名的写法规范:所有字母都小写。 //package cn.itcast.pack.demo; 类的全名称是包名.类名 编译命令:javac–d 位置(.当前路径) java源文件(就可以自动生成包) 包是一种封装形式,用于封装类,想要被包以外的程序访问,该类必须public; 类中的成员,如果被包以外访问,也必须public; 包与包之间访问可以使用的权限有两种: 1:public 2:protected:只能是不同包中的子类可以使用的权限。 总结java中的四种权限: 范围 publicprotecteddefaultprivate 同一个类中 okokokok 同一包中okokok 子类ok 不同包中 ok ----------------------------------------------------------------------------------------------- Import- 导入:类名称变长,写起来很麻烦。为了简化,使用了一个关键字:import,可以使用这个关键字导入指定包中的类。记住:实际开发时,到的哪个类就导入哪个类,不建议使用*. import packa.*;//这个仅仅是导入了packa当前目录下的所有的类。不包含子包。 import packa.abc.*;//导入了packa包中的子包abc下的当前的所有类。 如果导入的两个包中存在着相同名称的类。这时如果用到该类,必须在代码中指定包名。 常见的软件包: java.lang: language java的核心包,Object System String Throwable jdk1.2版本后,该包中的类自动被导入。 java.awt: 定义的都是用于java图形界面开发的对象。 javax.swing:提供所有的windows桌面应用程序包括的控件,比如:Frame , Dialog, Table, List 等等,就是java的图形界面库。 java.net: 用于java网络编程方面的对象都在该包中。 java.io: input output 用于操作设备上数据的对象都在该包中。比如:读取硬盘数据,往硬盘写入数据。 java.util: java的工具包,时间对象,集合框架。 java.applet: application+let 客户端java小程序。server+let --> servlet 服务端java小程序。 jar :java的压缩包,主要用于存储类文件,或者配置文件等。 命令格式:jar–cf 包名.jar 包目录 解压缩:jar–xvf 包名.jar 将jar包目录列表重定向到一个文件中:jar–tf 包名.jar >c:\1.txt ----------------------------------------------------------------------------------------------- 多线程: 进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。 线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。 一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。 jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。 当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。 随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。 返回当前线程的名称:Thread.currentThread().getName() 线程的名称是由:Thread-编号定义的。编号从0开始。 线程要运行的代码都统一存放在了run方法中。 线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径) start方法:1)、启动了线程;2)、让jvm调用了run方法。 创建线程的第一种方式:继承Thread ,由子类复写run方法。 步骤: 1,定义类继承Thread类; 2,目的是复写run方法,将要让线程运行的代码都存储到run方法中; 3,通过创建Thread类的子类对象,创建线程对象; 4,调用线程的start方法,开启线程,并执行run方法。 线程状态: 被创建:start() 运行:具备执行资格,同时具备执行权; 冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格; 临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权; 消亡:stop() 创建线程的第二种方式:实现一个接口Runnable。 步骤: 1,定义类实现Runnable接口。 2,覆盖接口中的run方法(用于封装线程要运行的代码)。 3,通过Thread类创建线程对象; 4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。 为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。 5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。 Ticket t = new Ticket(); /* 直接创建Ticket对象,并不是创建线程对象。 因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。 所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。 */ Thread t1 = new Thread(t);//创建线程。 /* 只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联 为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。 */ t1.start(); 为什么要有Runnable接口的出现? 1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。 可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢? 只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。 所以,通常创建线程都用第二种方式。 因为实现Runnable接口可以避免单继承的局限性。 2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。 所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。 实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。 ------------------------------------------------------- //面试 new Thread(new Runnable(){ //匿名 public void run(){ System.out.println("runnable run"); } }) { public void run(){ System.out.println("subthread run"); } }.start(); //结果:subthread run --------------------------------------------------------- Try { Thread.sleep(10); }catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu切换情况。 多线程安全问题的原因: 通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。 涉及到两个因素: 1,多个线程在操作共享数据。 2,有多条语句对共享数据进行运算。 原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。 解决安全问题的原理: 只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。 如何进行多句操作共享数据代码的封装呢? java中提供了一个解决方式:就是同步代码块。 格式: synchronized(对象){//任意对象都可以。这个对象就是锁。 需要被同步的代码; } --------------------------------------------------------------- 同步: 好处:解决了线程安全问题。 弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。 定义同步是有前提的: 1,必须要有两个或者两个以上的线程,才需要同步。 2,多个线程必须保证使用的是同一个锁。 同步的第二种表现形式: 同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。 同步函数是用的哪个锁呢? 通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。 当同步函数被static修饰时,这时的同步用的是哪个锁呢? 静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。 所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。 这个对象就是类名.class 同步代码块和同步函数的区别? 同步代码块使用的锁可以是任意对象。 同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。 在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。 ------------------------------------------------------- 考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。 //懒汉式:延迟加载方式。 当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。所以容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。但是却带来了效率降低。 为了效率问题,通过双重判断的形式解决。 class Single{ private static Single s = null; private Single(){} public static Single getInstance(){//锁是谁?字节码文件对象; if(s==null){ synchronized(Single.class){ if(s==null) s = new Single(); } } return s; } } --------------------------------------------------------- 同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。 线程间通信:思路:多个线程在操作同一个资源,但是操作的动作却不一样。 1:将资源封装成对象。 2:将线程执行的任务(任务其实就是run方法。)也封装成对象。 等待唤醒机制:涉及的方法: wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。 notify:唤醒线程池中某一个等待线程。 notifyAll:唤醒的是线程池中的所有线程。 注意: 1:这些方法都需要定义在同步中。 2:因为这些方法必须要标示所属的锁。 你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。 3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中? 因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。 wait和sleep区别:分析这两个方法:从执行权和锁上来分析: wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。 sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。 wait:线程会释放执行权,而且线程会释放锁。 Sleep:线程会释放执行权,但不是不释放锁。 线程的停止:通过stop方法就可以停止线程。但是这个方式过时了。 停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。 怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。 第一种方式:定义循环的结束标记。 第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。 ---------<java.lang.Thread>---------- interrupt():中断线程。 setPriority(int newPriority):更改线程的优先级。 getPriority():返回线程的优先级。 toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。 setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。 join:临时加入一个线程的时候可以使用join方法。 当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。 ----------------------------------------------------------- Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。 解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。 到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。 在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。 所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。 在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。 而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法await()、signal()、signalAll()体现新版本对象的好处。 <java.util.concurrent.locks>Condition接口:await()、signal()、signalAll(); -------------------------------------------------------- class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull =lock.newCondition(); final Condition notEmpty =lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } ------------------------------------------------------------------------------------------------ API:(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。 --<java.lang>--String字符串: java中用String类进行描述。对字符串进行了对象的封装。这样的好处是可以对字符串这种常见数据进行方便的操作。对象封装后,可以定义N多属性和行为。 如何定义字符串对象呢?String s = "abc";只要是双引号引起的数据都是字符串对象。 特点:字符串一旦被初始化,就不可以被改变,存放在方法区中的常量池中。 ------------------------------------------------------ String s1 = "abc";// s1指向的内存中只有一个对象abc。 String s2 = new String("abc");// s2指向的内容中有两个对象abc、new 。 System.out.println(s1==s2);//false System.out.println(s1.equals(s2));//true ,字符串中equals比较的是字符串内容是否相同。 ------------------------------------------------------- 字符串的方法: 1:构造方法:将字节数组或者字符数组转成字符串。 String s1 = new String();//创建了一个空内容的字符串。 String s2 = null;//s2没有任何对象指向,是一个null常量值。 String s3 = "";//s3指向一个具体的字符串对象,只不过这个字符串中没有内容。 //一般在定义字符串时,不用new。 String s4 = new String("abc"); String s5 = "abc";一般用此写法 new String(char[]);//将字符数组转成字符串。 new String(char[],offset,count);//将字符数组中的一部分转成字符串。 2:一般方法: 按照面向对象的思想: 2.1 获取: 2.1.1:获取字符串的长度。length(); 2.1.2:指定位置的字符。charcharAt(int index); 2.1.3:获取指定字符的位置。如果不存在返回-1,所以可以通过返回值-1来判断某一个字符不存在的情况。 intindexOf(int ch);//返回第一次找到的字符角标 int indexOf(int ch,int fromIndex);//返回从指定位置开始第一次找到的角标 int indexOf(String str);//返回第一次找到的字符串角标 int indexOf(String str,int fromIndex); intlastIndexOf(int ch); int lastIndexOf(int ch,int fromIndex); int lastIndexOf(String str); int lastIndexOf(String str,int fromIndex); 2.1.4:获取子串。 Stringsubstring(int start);//从start位开始,到length()-1为止. String substring(int start,int end);//从start开始到end为止。//包含start位,不包含end位。 substring(0,str.length());//获取整串 2.2 判断: 2.2.1:字符串中包含指定的字符串吗? booleancontains(String substring); 2.2.2:字符串是否以指定字符串开头啊? booleanstartsWith(string); 2.2.3:字符串是否以指定字符串结尾啊? booleanendsWith(string); 2.2.4:判断字符串是否相同 booleanequals(string);//覆盖了Object中的方法,判断字符串内容是否相同。 2.2.5:判断字符串内容是否相同,忽略大小写。 booleanequalsIgnoreCase(string) ; 2.3 转换: 2.3.1:通过构造函数可以将字符数组或者字节数组转成字符串。 2.3.2:可以通过字符串中的静态方法,将字符数组转成字符串。 static StringcopyValueOf(char[] ); static String copyValueOf(char[],int offset,int count); static StringvalueOf(char[]); static String valueOf(char[],int offset,int count); 2.3.3:将基本数据类型或者对象转成字符串。 static String valueOf(char); static String valueOf(boolean); static String valueOf(double); static String valueOf(float); static String valueOf(int); static String valueOf(long); static String valueOf(Object); 2.3.4:将字符串转成大小写。 StringtoLowerCase(); StringtoUpperCase(); 2.3.5:将字符串转成数组。 char[]toCharArray();//转成字符数组。 byte[]getBytes();//可以加入编码表。转成字节数组。 2.3.6:将字符串转成字符串数组。切割方法。 String[]split(分割的规则-字符串); 2.3.7:将字符串进行内容替换。注意:修改后变成新字符串,并不是将原字符串直接修改。 Stringreplace(oldChar,newChar); String replace(oldstring,newstring); 2.3.8:Stringconcat(string);//对字符串进行追加。 Stringtrim();//去除字符串两端的空格 intcompareTo();//如果参数字符串等于此字符串,则返回值 0;如果此字符串按字典顺序小于字符串参数,则返回一个小于 0 的值;如果此字符串按字典顺序大于字符串参数,则返回一个大于 0 的值。 ------------------------------------------------------------------------------------------------ --<java.lang>--StringBuffer字符串缓冲区: 构造一个其中不带字符的字符串缓冲区,初始容量为 16个字符。 特点: 1:可以对字符串内容进行修改。 2:是一个容器。 3:是可变长度的。 4:缓冲区中可以存储任意类型的数据。 5:最终需要变成字符串。 容器通常具备一些固定的方法: 1,添加。 StringBufferappend(data):在缓冲区中追加数据。追加到尾部。 StringBufferinsert(index,data):在指定位置插入数据。 2,删除。 StringBufferdelete(start,end);删除从start至end-1范围的元素 StringBuffer deleteCharAt(index);删除指定位置的元素 //sb.delete(0,sb.length());//清空缓冲区。 3,修改。 StringBufferreplace(start,end,string);将start至end-1替换成string voidsetCharAt(index,char);替换指定位置的字符 voidsetLength(len);将原字符串置为指定长度的字符串 4,查找。(查不到返回-1) intindexOf(string);返回指定子字符串在此字符串中第一次出现处的索引。 int indexOf(string,int fromIndex);从指定位置开始查找字符串 intlastIndexOf(string);返回指定子字符串在此字符串中最右边出现处的索引。 int lastIndexOf(string,int fromIndex);从指定的索引开始反向搜索 5,获取子串。 stringsubstring(start);返回start到结尾的子串 string substring(start,end);返回start至end-1的子串 6,反转。 StringBufferreverse();字符串反转 ------------------------------------------------------------------------------------------------ --<java.lang>--StringBuilder字符串缓冲区: JDK1.5出现StringBuiler;构造一个其中不带字符的字符串生成器,初始容量为 16个字符。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。 方法和StringBuffer一样; StringBuffer 和 StringBuilder 的区别: StringBuffer线程安全。 StringBuilder线程不安全。 单线程操作,使用StringBuilder 效率高。 多线程操作,使用StringBuffer 安全。 --------------------------------------------------------- StringBuilder sb = new StringBuilder("abcdefg"); sb.append("ak"); //abcdefgak sb.insert(1,"et");//aetbcdefg sb.deleteCharAt(2);//abdefg sb.delete(2,4);//abefg sb.setLength(4);//abcd sb.setCharAt(0,'k');//kbcdefg sb.replace(0,2,"hhhh");//hhhhcdefg //想要使用缓冲区,先要建立对象。 StringBuffer sb = new StringBuffer(); sb.append(12).append("haha");//方法调用链。 String s = "abc"+4+'q'; s = new StringBuffer().append("abc").append(4).append('q').toString(); --------------------------------------------------------- class Test{ public static void main(String[] args) { String s1 = "java"; String s2 = "hello"; method_1(s1,s2); System.out.println(s1+"...."+s2);//java....hello StringBuilder s11 = new StringBuilder("java"); StringBuilder s22 = new StringBuilder("hello"); method_2(s11,s22); System.out.println(s11+"-----"+s22);//javahello-----hello } public static void method_1(String s1,String s2){ s1.replace('a','k'); s1 = s2; } public static void method_2(StringBuilder s1,StringBuilder s2){ s1.append(s2); s1 = s2; } } --------------------------------------------------------- 基本数据类型对象包装类:是按照面向对象思想将基本数据类型封装成了对象。 好处: 1:可以通过对象中的属性和行为操作基本数据。 2:可以实现基本数据类型和字符串之间的转换。 关键字 对应的类名 byteByte shortShortpaserShort(numstring); intInteger 静态方法:parseInt(numstring) longLong floatFloat doubleDouble charCharacter BooleanBoolean 基本数据类型对象包装类:都有XXX parseXXX方法 只有一个类型没有parse方法:Character ; -------------------------------------------------------- Integer对象: 数字格式的字符串转成基本数据类型的方法: 1:将该字符串封装成了Integer对象,并调用对象的方法intValue(); 2:使用Integer.parseInt(numstring):不用建立对象,直接类名调用; 将基本类型转成字符串: 1:Integer中的静态方法 String toString(int); 2:int+""; 将一个十进制整数转成其他进制: 转成二进制:toBinaryString 转成八进制:toOctalString 转成十六进制:toHexString toString(int num,int radix); 将其他进制转换十进制: parseInt(string,radix);//将给定的数转成指定的基数进制; 在jdk1.5版本后,对基本数据类型对象包装类进行升级。在升级中,使用基本数据类型对象包装类可以像使用基本数据类型一样,进行运算。 Integer i = new Integer(4);//1.5版本之前的写法; Integer i = 4;//自动装箱,1.5版本后的写法; i = i + 5; //i对象是不能直接和5相加的,其实底层先将i转成int类型,在和5相加。而转成int类型的操作是隐式的。自动拆箱:拆箱的原理就是i.intValue();i+5运算完是一个int整数。如何赋值给引用类型i呢?其实有对结果进行装箱。 Integer c = 127; Integer d = 127; System.out.println(c==d);//true //在装箱时,如果数值在byte范围之内,那么数值相同,不会产生新的对象,也就是说多个数值相同的引用指向的是同一个对象。 ------------------------------------------------------------------------------------------------ 集合框架(重点理解): 用于存储数据的容器。 特点: 1:对象封装数据,对象多了也需要存储。集合用于存储对象。 2:对象的个数确定可以使用数组,但是不确定怎么办?可以用集合。因为集合是可变长度的。 集合和数组的区别: 1:数组是固定长度的;集合可变长度的。 2:数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。 3:数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。 数据结构:就是容器中存储数据的方式。 对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。 集合容器在不断向上抽取过程中。出现了集合体系。 在使用一个体系时,原则:参阅顶层内容。建立底层对象。 ------------------------------------------------------------ --<java.util >--Collection接口: Collection: |--List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。 |--Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。 1,添加: add(object):添加一个元素 addAll(Collection) :添加一个集合中的所有元素。 2,删除: clear():将集合中的元素全删除,清空集合。 remove(obj) :删除集合中指定的对象。注意:删除成功,集合的长度会改变。 removeAll(collection) :删除部分元素。部分元素和传入Collection一致。 3,判断: boolean contains(obj) :集合中是否包含指定元素 。 boolean containsAll(Collection) :集合中是否包含指定的多个元素。 boolean isEmpty():集合中是否有元素。 4,获取: int size():集合中有几个元素。 5,取交集: boolean retainAll(Collection) :对当前集合中保留和指定集合中的相同的元素。如果两个集合元素相同,返回flase;如果retainAll修改了当前集合,返回true。 6,获取集合中所有元素: Iterator iterator():迭代器 7,将集合变成数组: toArray(); ------------------------------------------------------------ --<java.util >--Iterator接口: 迭代器:是一个接口。作用:用于取集合中的元素。 boolean hasNext()如果仍有元素可以迭代,则返回true。 E next()返回迭代的下一个元素。 void remove()从迭代器指向的collection中移除迭代器返回的最后一个元素(可选操作)。 每一个集合都有自己的数据结构,都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口。 也就说,只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。 Iterator it = coll.iterator();//获取容器中的迭代器对象,至于这个对象是是什么不重要。这对象肯定符合一个规则Iterator接口。 ----------------------------------------------------------------------------- public static void main(String[] args) { Collection coll = new ArrayList(); coll.add("abc0"); coll.add("abc1"); coll.add("abc2"); //--------------方式1---------------------- Iterator it = coll.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //---------------方式2用此种---------------------- for(Iterator it = coll.iterator();it.hasNext(); ){ System.out.println(it.next()); } } ----------------------------------------------------------------------------- --<java.util >--List接口: List本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。 List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。 |--ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。 |--LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快。 |--Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。 1,添加: add(index,element) :在指定的索引位插入元素。 addAll(index,collection) :在指定的索引位插入一堆元素。 2,删除: remove(index) :删除指定索引位的元素。 返回被删的元素。 3,获取: Object get(index) :通过索引获取指定元素。 intindexOf(obj) :获取指定元素第一次出现的索引位,如果该元素不存在返回-1; 所以,通过-1,可以判断一个元素是否存在。 int lastIndexOf(Object o) :反向索引指定元素的位置。 ListsubList(start,end) :获取子列表。 4,修改: Object set(index,element) :对指定索引位进行元素的修改。 5,获取所有元素: ListIteratorlistIterator():list集合特有的迭代器。 List集合支持对元素的增、删、改、查。 List集合因为角标有了自己的获取元素的方式: 遍历。 for(int x=0; x<list.size(); x++){ sop("get:"+list.get(x)); } 在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候,比如满足条件添加新元素。会发生.ConcurrentModificationException并发修改异常。 导致的原因是: 集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。 如何解决呢? 既然是在迭代中对元素进行操作,找迭代器的方法最为合适.可是Iterator中只有hasNext,next,remove方法.通过查阅的它的子接口,ListIterator,发现该列表迭代器接口具备了对元素的增、删、改、查的动作。 ListIterator是List集合特有的迭代器。 ListIterator it = list.listIterator;//取代Iterator it = list.iterator; 方法摘要 void add(Ee)将指定的元素插入列表(可选操作)。 boolean hasNext()以正向遍历列表时,如果列表迭代器有多个元素,则返回true(换句话说,如果next返回一个元素而不是抛出异常,则返回true)。 boolean hasPrevious()如果以逆向遍历列表,列表迭代器有多个元素,则返回true。 E next()返回列表中的下一个元素。 int nextIndex()返回对next的后续调用所返回元素的索引。 E previous()返回列表中的前一个元素。 int previousIndex()返回对previous的后续调用所返回元素的索引。 void remove()从列表中移除由next或previous返回的最后一个元素(可选操作)。 void set(Ee)用指定元素替换next或previous返回的最后一个元素(可选操作)。 可变长度数组的原理: 当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。 ArrayList:是按照原数组的50%延长。构造一个初始容量为 10的空列表。 Vector:是按照原数组的100%延长。 注意:对于list集合,底层判断元素是否相同,其实用的是元素自身的equals方法完成的。所以建议元素都要复写equals方法,建立元素对象自己的比较相同的条件依据。 LinkedList:的特有方法。 addFirst(); addLast(); 在jdk1.6以后。 offerFirst(); offerLast(); getFirst():获取链表中的第一个元素。如果链表为空,抛出NoSuchElementException; getLast(); 在jdk1.6以后。 peekFirst();获取链表中的第一个元素。如果链表为空,返回null。 peekLast(); removeFirst():获取链表中的第一个元素,但是会删除链表中的第一个元素。如果链表为空,抛出NoSuchElementException removeLast(); 在jdk1.6以后。 pollFirst();获取链表中的第一个元素,但是会删除链表中的第一个元素。如果链表为空,返回null。 pollLast(); ------------------------------------------------------------ --<java.util >--Set接口: Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。 |--HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效; HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。 当元素的hashCode值相同时,才继续判断元素的equals是否为true。 如果为true,那么视为相同元素,不存。如果为false,那么存储。 如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。 |--LinkedHashSet:有序,hashset的子类。 |--TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。 哈希表的原理: 1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。 2,哈希值就是这个元素的位置。 3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。 4,存储哈希值的结构,我们称为哈希表。 5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。 这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。 对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。 对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。 TreeSet: 用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性。 如果元素不具备比较性,在运行时会发生ClassCastException异常。 所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。 依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。 TreeSet方法保证元素唯一性的方式:就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。 注意:在进行比较时,如果判断元素不唯一,比如,同姓名,同年龄,才视为同一个人。 在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。 TreeSet集合排序有两种方式,Comparable和Comparator区别: 1:让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。 2:让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。 第二种方式较为灵活。 ------------------------------------------------------------ Map集合: |--Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。 |--HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable. |--TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序。 Map集合存储和Collection有着很大不同: Collection一次存一个元素;Map一次存一对元素。 Collection是单列集合;Map是双列集合。 Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。 特点:要保证map集合中键的唯一性。 1,添加。 put(key,value):当存储的键相同时,新的值会替换老的值,并将老值返回。如果键没有重复,返回null。 voidputAll(Map); 2,删除。 voidclear():清空 valueremove(key) :删除指定键。 3,判断。 booleanisEmpty(): booleancontainsKey(key):是否包含key booleancontainsValue(value):是否包含value 4,取出。 intsize():返回长度 valueget(key) :通过指定键获取对应的值。如果返回null,可以判断该键不存在。当然有特殊情况,就是在hashmap集合中,是可以存储null键null值的。 Collectionvalues():获取map集合中的所有的值。 5,想要获取map中的所有元素: 原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。 把map集合转成set的方法: Set keySet(); Set entrySet();//取的是键和值的映射关系。 Entry就是Map接口中的内部接口; 为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。 --------------------------------------------------------- 取出map集合中所有元素的方式一:keySet()方法。 可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get方法对获取到的键进行值的获取。 Set keySet = map.keySet(); Iterator it = keySet.iterator(); while(it.hasNext()){ Object key = it.next(); Object value = map.get(key); System.out.println(key+":"+value); } -------------------------------------------------------- 取出map集合中所有元素的方式二:entrySet()方法。 Set entrySet = map.entrySet(); Iterator it = entrySet.iterator(); while(it.hasNext()){ Map.Entryme = (Map.Entry)it.next(); System.out.println(me.getKey()+"::::"+me.getValue()); } -------------------------------------------------------- 使用集合的技巧: 看到Array就是数组结构,有角标,查询速度很快。 看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast(); 看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。 看到tree就是二叉树,就要想到排序,就想要用到比较。 比较的两种方式: 一个是Comparable:覆盖compareTo方法; 一个是Comparator:覆盖compare方法。 LinkedHashSet,LinkedHashMap:这两个集合可以保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。 集合什么时候用? 当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。 保证唯一,就用Set。不保证唯一,就用List。 ------------------------------------------------------------------------------------------------ Collections:它的出现给集合操作提供了更多的功能。这个类不需要创建对象,内部提供的都是静态方法。 静态方法: Collections.sort(list);//list集合进行元素的自然顺序排序。 Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。 class ComparatorByLen implements Comparator<String>{ public int compare(String s1,String s2){ int temp = s1.length()-s2.length(); return temp==0?s1.compareTo(s2):temp; } } Collections.max(list);//返回list中字典顺序最大的元素。 int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。 Collections.reverseOrder();//逆向反转排序。 Collections.shuffle(list);//随机对list中的元素进行位置的置换。 将非同步集合转成同步集合的方法:Collections中的XXX synchronizedXXX(XXX); List synchronizedList(list); Map synchronizedMap(map); 原理:定义一个类,将集合所有的方法加同一把锁后返回。 Collection 和 Collections的区别: Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。 Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。 ------------------------------------------------------- Arrays: 用于操作数组对象的工具类,里面都是静态方法。 asList方法:将数组转换成list集合。 String[] arr = {"abc","kk","qq"}; List<String> list = Arrays.asList(arr);//将arr数组转成list集合。 将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合; 可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set; 注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException); 如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。 如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。 集合变数组:用的是Collection接口中的方法:toArray(); 如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。 如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。 所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。 将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。 ------------------------------------------------------------------------------------------------ Jdk5.0新特性: Collection在jdk1.5以后,有了一个父接口Iterable,这个接口的出现的将iterator方法进行抽取,提高了扩展性。 -------------------------------------------------- 增强for循环:foreach语句,foreach简化了迭代器。 格式:// 增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器 for( 元素类型 变量名 : Collection集合 & 数组 ){ … } 高级for循环和传统for循环的区别: 高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合,在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。 如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。 高级for循环可以遍历map集合吗?不可以。但是可以将map转成set后再使用foreach语句。 1)、作用:对存储对象的容器进行迭代: 数组 collection map 2)、增强for循环迭代数组: String [] arr = {"a", "b", "c"};//数组的静态定义方式,只试用于数组首次定义的时候 for(String s : arr) { System.out.println(s); } 3)、单列集合 Collection: List list = new ArrayList(); list.add("aaa"); // 增强for循环, 没有使用泛型的集合能不能使用增强for循环迭代?能 for(Objectobj : list) { String s = (String) obj; System.out.println(s); } 4)、双列集合 Map: Map map = new HashMap(); map.put("a", "aaa"); // 传统方式:必须掌握这种方式 Set entrys = map.entrySet();// 1.获得所有的键值对Entry对象 iter = entrys.iterator();// 2.迭代出所有的entry while(iter.hasNext()) { Map.Entry entry = (Entry) iter.next(); String key = (String) entry.getKey();// 分别获得key和value String value = (String) entry.getValue(); System.out.println(key + "=" + value); } // 增强for循环迭代:原则上map集合是无法使用增强for循环来迭代的,因为增强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证一定有iterator方法,java有这样的限定是因为增强for循环内部还是用迭代器实现的,而实际上,我们可以通过某种方式来使用增强for循环。 for(Object obj : map.entrySet()) { Map.Entry entry = (Entry) obj;// obj 依次表示Entry System.out.println(entry.getKey() + "=" + entry.getValue()); } 5)、集合迭代注意问题:在迭代集合的过程中,不能对集合进行增删操作(会报并发访问异常);可以用迭代器的方法进行操作(子类listIterator:有增删的方法)。 6)、增强for循环注意问题:在使用增强for循环时,不能对元素进行赋值; int[] arr = {1,2,3}; for(int num : arr) { num = 0;//不能改变数组的值 } System.out.println(arr[1]);//2 -------------------------------------------------- 可变参数(...):用到函数的参数上,当要操作的同一个类型元素个数不确定的时候,可是用这个方式,这个参数可以接受任意个数的同一类型的数据。 和以前接收数组不一样的是: 以前定义数组类型,需要先创建一个数组对象,再将这个数组对象作为参数传递给函数。现在,直接将数组中的元素作为参数传递即可。底层其实是将这些元素进行数组的封装,而这个封装动作,是在底层完成的,被隐藏了。所以简化了用户的书写,少了调用者定义数组的动作。 如果在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,否则编译会失败。)。 如果要获取多个int数的和呢?可以使用将多个int数封装到数组中,直接对数组求和即可。 --------------------------------------------------- 静态导入:导入了类中的所有静态成员,简化静态成员的书写。 import static java.util.Collections.*; //导入了Collections类中的所有静态成员 --------------------------------------------------- 枚举:关键字enum 问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个; 解决办法: 1)、在setGrade方法中做判断,不符合格式要求就抛出异常; 2)、直接限定用户的选择,通过自定义类模拟枚举的方式来限定用户的输入,写一个Grade类,私有构造函数,对外提供5个静态的常量表示类的实例; 3)、jdk5中新定义了枚举类型,专门用于解决此类问题; 4)、枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类; ------------------------------------------------------------------------------ 自动拆装箱:java中数据类型分为两种 : 基本数据类型 引用数据类型(对象) 在java程序中所有的数据都需要当做对象来处理,针对8种基本数据类型提供了包装类,如下: int --> Integer byte --> Byte short --> Short long --> Long char --> Character double --> Double float --> Float boolean --> Boolean jdk5以前基本数据类型和包装类之间需要互转: 基本---引用Integer x = new Integer(x); 引用---基本int num = x.intValue(); 1)、Integer x = 1; x = x + 1;经历了什么过程?装箱à拆箱à装箱; 2)、为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小-128~127一个字节的大小; 3)、String池:Java为了优化字符串操作 提供了一个缓冲池; ---------------------------------------------------------- 泛型:jdk1.5版本以后出现的一个安全机制。表现格式:<> 好处: 1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。 2:避免了强制转换的麻烦。 只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。 泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。 为什么? 因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。 在运行时,如何知道获取的元素类型而不用强转呢? 泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。 什么时候用泛型类呢? 当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。 ---------------------------------------------------------- 泛型在程序定义上的体现: //泛型类:将泛型定义在类上。 class Tool<Q>{ private Q obj; public void setObject(Q obj){ this.obj = obj; } public Q getObject(){ return obj; } } //当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上。 public <W> void method(W w){ System.out.println("method:"+w); } //静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。 public static <Q> void function(Q t){ System.out.println("function:"+t); } //泛型接口. interface Inter<T>{ void show(T t); } class InterImpl<R> implements Inter<R>{ public void show(R r){ System.out.println("show:"+r); } } ------------------------------------------------------------ 泛型中的通配符:可以解决当具体类型不确定的时候,这个通配符就是? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。 泛型限定: 上限:?extends E:可以接收E类型或者E的子类型对象。 下限:?super E:可以接收E类型或者E的父类型对象。 上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。 下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。 泛型的细节: 1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型; 2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致; 原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了; 3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容); ArrayList<String> al = new ArrayList<Object>();//错 //要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。 ArrayList<? extends Object> al = new ArrayList<String>(); al.add("aa");//错 //因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢? public static void method(ArrayList<? extends Object> al){ al.add("abc");//错 //只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。 } ------------------------------------------------------------------------------------------------------------------------------------------------ API---java.lang.System:属性和行为都是静态的。 long currentTimeMillis();//返回当前时间毫秒值 exit(); //退出虚拟机 PropertiesgetProperties(); //获取当前系统的属性信息 Properties prop = System.getProperties();//获取系统的属性信息,并将这些信息存储到Properties集合中。 System.setProperty("myname","毕老师");//给系统属性信息集添加具体的属性信息 //临时设置方式:运行jvm时,可以通过jvm的参数进行系统属性的临时设置,可以在java命令的后面加入–D<name>=<value> 用法:java–Dmyname=小明 类名。 String name = System.getProperty("os.name");//获取指定属性的信息 //想要知道该系统是否是该软件所支持的系统中的一个。 Set<String> hs = new HashSet<String>(); hs.add("Windows XP"); hs.add("Windows 7"); if(hs.contains(name)) System.out.println("可以支持"); else System.out.println("不支持"); -------------------------------------------------------------------------------------------------------------------- API---java.lang.Runtime:类中没有构造方法,不能创建对象。 但是有非静态方法。说明该类中应该定义好了对象,并可以通过一个static方法获取这个对象。用这个对象来调用非静态方法。这个方法就是static Runtime getRuntime(); 这个Runtime其实使用单例设计模式进行设计。 class RuntimeDemo{ public static void main(String[] args) throws Exception{ Runtime r = Runtime.getRuntime(); Process p = r.exec("notepad.exe SystemDemo.java");//运行指定的程序 Thread.sleep(4000); p.destroy();//杀掉进程 } } -------------------------------------------------------------------------------------------------------------------- API---java.util.Math:用于数学运算的工具类,属性和行为都是静态的。该类是final不允许继承。 static double ceil(double a); //返回大于指定数值的最小整数 static double floor(double a); //返回小于指定数值的最大整数 static long round(double a); //四舍五入成整数 static double pow(double a, double b) ; //a的b次幂 static double random(); //返回0~1的伪随机数 public static void main(String[] args) { Random r = new Random(); for(int x=0; x<10; x++){ //double d = Math.floor(Math.random()*10+1); //int d = (int)(Math.random()*10+1); int d = r.nextInt(10)+1; System.out.println(d); } } -------------------------------------------------------------------------------------------------------------------- API---java.util.Date:日期类,月份从0-11; /* 日期对象和毫秒值之间的转换。 1,日期对象转成毫秒值。Date类中的getTime方法。 2,如何将获取到的毫秒值转成具体的日期呢? Date类中的setTime方法。也可以通过构造函数。 */ //日期对象转成毫秒值 Date d = new Date(); long time1 = d.getTime(); long time2 = System.currentTimeMillis(); / /毫秒值。 //毫秒值转成具体的日期 long time = 1322709921312l; Date d = new Date(); d.setTime(time); /* 将日期字符串转换成日期对象:使用的就是DateFormat方法中的Date parse(String source); */ public static void method()throws Exception{ String str_time = "2011/10/25"; DateFormat df = new SimpleDateFormat("yyyy/MM/dd");//SimpleDateFormat作为可以指定用户自定义的格式来完成格式化。 Date d = df.parse(str_time); } /* 如果不需要使用特定的格式化风格,完全可以使用DateFormat类中的静态工厂方法获取具体的已经封装好风格的对象。getDateInstance();getDateTimeInstance(); */ Date d = new Date(); DateFormat df = DateFormat.getDateInstance(DateFormat.LONG); df = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG); String str_time = df.format(d); //将日期对象转换成字符串的方式:DateFormat类中的format方法。 //创建日期格式对象。 DateFormat df = new SimpleDateFormat();//该对象的建立内部会封装一个默认的日期格式。11-12-1下午1:48 //如果想要自定义日期格式的话。可使用SimpleDateFormat的构造函数。将具体的格式作为参数传入到构造函数中。如何表示日期中年的部分呢?可以必须要参与格式对象文档。 df = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss"); //调用DateFormat中的format方法。对已有的日期对象进行格式化。 String str_time = df.format(d); -------------------------------------------------------------------------------------------------------------------- API---java.util.Calendar:日历类 public static void method(){ Calendar c = Calendar.getInstance(); System.out.println(c.get(Calendar.YEAR)+"年"+(c.get(Calendar.MONTH)+1)+"月" +getNum(c.get(Calendar.DAY_OF_MONTH))+"日" +"星期"+getWeek(c.get(Calendar.DAY_OF_WEEK))); } public static String getNum(int num){ return num>9?num+"":"0"+num; } public static String getWeek(int index){ /* 查表法:建立数据的对应关系. 最好:数据个数是确定的,而且有对应关系。如果对应关系的一方,是数字,而且可以作为角标,那么可以通过数组来作为表。 */ String[] weeks = {"","日","一","二","三","四","五","六"}; return weeks[index]; } ------------------------------------------------------------------------------------------------------------------------------------------------ IO流(重点理解) 用于处理设备上数据。 流:可以理解数据的流动,就是一个数据流。IO流最终要以对象来体现,对象都存在IO包中。 流也进行分类: 1:输入流(读)和输出流(写)。 2:因为处理的数据不同,分为字节流和字符流。 字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。 那么为什么要有字符流呢?因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。 注意:流的操作只有两种:读和写。 流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。 字节流:InputStream OutputStream 字符流:Reader Writer 在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。 -------------------------------------------------------------------------------------------------------------------- public static void main(String[] args) throws IOException{//读、写都会发生IO异常 /* 1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。 2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。 3:如果指定位置,出现了同名文件,文件会被覆盖。 */ FileWriter fw = new FileWriter("demo.txt");//FileNotFoundException /* 调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中? */ fw.write("abcde"); fw.flush();//刷新缓冲区,将缓冲区中的数据刷到目的地文件中。 fw.close();// 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。 } close()和flush()的区别: flush():将缓冲区的数据刷到目的地中后,流可以使用。 close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。 -------------------------------------------------------------------------------------------------------------------- io异常的处理方式:io一定要写finally; FileWriter写入数据的细节: 1:window中的换行符:\r\n两个符号组成。linux:\n。 2:续写数据,只要在构造函数中传入新的参数true。 3:目录分割符:window \\ / public static void main(String[] args) { FileWriter fw = null; try{ fw = new FileWriter("demo.txt",true); fw.write("abcde"); } catch (IOException e ){ System.out.println(e.toString()+"...."); } finally{ if(fw!=null) try{ fw.close(); } catch (IOException e){ System.out.println("close:"+e.toString()); } } } -------------------------------------------------------------------------------------------------------------------- FileReader:使用Reader体系,读取一个文本文件中的数据。返回-1,标志读到结尾。 import java.io.*; class FileReaderDemo{ public static void main(String[] args) throws IOException{ /* 创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。 */ FileReader fr = new FileReader("demo.txt"); int ch = 0; while((ch = fr.read())!=-1){//条件是没有读到结尾 System.out.println((char)ch);//调用读取流的read方法,读取一个字符。 } fr.close(); } } -------------------------------------------------------------------------------------------------------------------- 读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。 import java.io.*; class FileReaderDemo2{ public static void main(String[] args) throws IOException{ FileReader fr = new FileReader("demo.txt");//创建读取流对象和指定文件关联。 //因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。 char[] buf = new char[1024]; int len = 0; while((len=fr.read(buf))!=-1){ System.out.println(new String(buf,0,len)); } fr.close(); } } -------------------------------------------------------------------------------------------------------------------- IO中的使用到了一个设计模式:装饰设计模式。 装饰设计模式解决:对一组类进行功能的增强。 包装:写一个类(包装类)对被包装对象进行包装; * 1、包装类和被包装对象要实现同样的接口; * 2、包装类要持有一个被包装对象; * 3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现; -------------------------------------------------------------------------------------------------------------------- 字符流: Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int)和close()。 |---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。 |---LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法setLineNumber(int)和getLineNumber(),它们可分别用于设置和获取当前行号。 |---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。 |---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream上构造一个InputStreamReader。 |---CharArrayReader: |---StringReader: ------------------------------------------------- Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush()和close()。 |---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 |---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。 |---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream上构造一个OutputStreamWriter。 |---PrintWriter: |---CharArrayWriter: |---StringWriter: --------------------------------- 字节流: InputStream:是表示字节输入流的所有类的超类。 |---FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用FileReader。 |---FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。 |---BufferedInputStream:该类实现缓冲的输入流。 |---Stream: |---ObjectInputStream: |---PipedInputStream: ----------------------------------------------- OutputStream:此抽象类是表示输出字节流的所有类的超类。 |---FileOutputStream:文件输出流是用于将数据写入File或FileDescriptor的输出流。 |---FilterOutputStream:此类是过滤输出流的所有类的超类。 |---BufferedOutputStream:该类实现缓冲的输出流。 |--- PrintStream: |--- DataOutputStream: |---ObjectOutputStream: |---PipedOutputStream: -------------------------------- 缓冲区是提高效率用的,给谁提高呢? BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。 FileWriter fw = new FileWriter("bufdemo.txt"); BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。 for(int x=0; x<4; x++){ bufw.write(x+"abc"); bufw.newLine();//写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。 bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。 } bufw.close();//关闭缓冲区,其实就是在关闭具体的流。 ----------------------------- BufferedReader: FileReader fr = new FileReader("bufdemo.txt"); BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null){//readLine方法返回的时候是不带换行符的。 System.out.println(line); } bufr.close(); ----------------------------- //记住,只要一读取键盘录入,就用这句话。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//输出到控制台 String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase());//将输入的字符转成大写字符输出 bufw.newLine(); bufw.flush(); } bufw.close(); bufr.close(); ------------------------------ 流对象:其实很简单,就是读取和写入。但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。 流的操作规律: 1,明确源和目的。 数据源:就是需要读取,可以使用两个体系:InputStream、Reader; 数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer; 2,操作的数据是否是纯文本数据? 如果是:数据源:Reader 数据汇:Writer 如果不是:数据源:InputStream 数据汇:OutputStream 3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢? 明确操作的数据设备。 数据源对应的设备:硬盘(File),内存(数组),键盘(System.in) 数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。 4,需要在基本操作上附加其他功能吗?比如缓冲。 如果需要就进行装饰。 转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。 转换流的最强功能就是基于 字节流+编码表 。没有转换,没有字符流。 发现转换流有一个子类就是操作文件的字符流对象: InputStreamReader |--FileReader OutputStreamWriter |--FileWrier 想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。 但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。 FileReader fr = new FileReader("a.txt"); InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk"); 以上两句代码功能一致, 如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。 如果需要制定码表,必须用转换流。 转换流=字节流+编码表。 转换流的子类File =字节流+默认编码表。 凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。 ----------------------------------------------------------------------------------------------- File类:将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。 File类常见方法: 1:创建。 booleancreateNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。 booleanmkdir():创建此抽象路径名指定的目录。 booleanmkdirs():创建多级目录。 2:删除。 booleandelete():删除此抽象路径名表示的文件或目录。 voiddeleteOnExit():在虚拟机退出时删除。 注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。 window的删除动作,是从里往外删。注意:java删除文件不走回收站。要慎用。 3:获取. longlength():获取文件大小。 StringgetName():返回由此抽象路径名表示的文件或目录的名称。 StringgetPath():将此抽象路径名转换为一个路径名字符串。 StringgetAbsolutePath():返回此抽象路径名的绝对路径名字符串。 StringgetParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回null。 longlastModified():返回此抽象路径名表示的文件最后一次被修改的时间。 File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。 File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。 4:判断: booleanexists():判断文件或者文件夹是否存在。 booleanisDirectory():测试此抽象路径名表示的文件是否是一个目录。 booleanisFile():测试此抽象路径名表示的文件是否是一个标准文件。 booleanisHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。 booleanisAbsolute():测试此抽象路径名是否为绝对路径名。 5:重命名。 booleanrenameTo(File dest):可以实现移动的效果。剪切+重命名。 String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。 如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。 ------------------------------------------------------------------------------------------------ 递归:就是函数自身调用自身。 什么时候用递归呢? 当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。 简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。 递归的注意事项: 1:一定要定义递归的条件。 2:递归的次数不要过多。容易出现StackOverflowError 栈内存溢出错误。 其实递归就是在栈内存中不断的加载同一个函数。 ------------------------------------------------------------------------------------------------ Java.util.Properties:一个可以将键值进行持久化存储的对象。Map--Hashtable的子类。 Map |--Hashtable |--Properties:用于属性配置文件,键和值都是字符串类型。 特点:1:可以持久化存储数据。2:键值都是字符串。3:一般用于配置文件。 |--load():将流中的数据加载进集合。 原理:其实就是将读取流和指定文件相关联,并读取一行数据,因为数据是规则的key=value,所以获取一行后,通过 = 对该行数据进行切割,左边就是键,右边就是值,将键、值存储到properties集合中。 |-- store():写入各个项后,刷新输出流。 |-- list():将集合的键值数据列出到指定的目的地。 ------------------------------------------------------------------------------------------------- 以下介绍IO包中扩展功能的流对象:基本都是装饰设计模式。 Java.io.outputstream.PrintStream:打印流 1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。 2:它有一个自动刷新机制,创建该对象,指定参数,对于指定方法可以自动刷新。 3:它使用的本机默认的字符编码. 4:该流的print方法不抛出IOException。 该对象的构造函数。 PrintStream(File file):创建具有指定文件且不带自动行刷新的新打印流。 PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。 PrintStream(OutputStream out):创建新的打印流。 PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。 PrintStream(OutputStream out, boolean autoFlush, String encoding):创建新的打印流。 PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。 PrintStream(String fileName, String csn) PrintStream可以操作目的:1:File对象。2:字符串路径。3:字节输出流。 前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。 当目的是一个字节输出流时,如果使用的println方法,可以在printStream对象上加入一个true参数。这样对于println方法可以进行自动的刷新,而不是等待缓冲区满了再刷新。最终print方法都将具体的数据转成字符串,而且都对IO异常进行了内部处理。 既然操作的数据都转成了字符串,那么使用PrintWriter更好一些。因为PrintWrite是字符流的子类,可以直接操作字符数据,同时也可以指定具体的编码。 -------------------------------------------------------- PrintWriter:具备了PrintStream的特点同时,还有自身特点: 该对象的目的地有四个:1:File对象。2:字符串路径。3:字节输出流。4:字符输出流。 开发时尽量使用PrintWriter。 方法中直接操作文件的第二参数是编码表。 直接操作输出流的,第二参数是自动刷新。 //读取键盘录入将数据转成大写显示在控制台. BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:键盘输入 //目的:把数据写到文件中,还想自动刷新。 PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//设置true后自动刷新 String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; out.println(line.toUpperCase());//转大写输出 } //注意:System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。随时可以使用。当jvm结束了,这两个流就结束了。但是,当使用了显示的close方法关闭时,这两个流在提前结束了。 out.close(); bufr.close(); ------------------------------------------------------------------------------------------------ SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。 该对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。 但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。 合并原理:多个读取流对应一个输出流。 切割原理:一个读取流对应多个输出流。 import java.io.*; import java.util.*; class SplitFileDemo{ private static final String CFG = ".properties"; private static final String SP = ".part"; public static void main(String[] args) throws IOException{ File file = new File("c:\\0.bmp"); File dir = new File("c:\\partfiles"); meger(dir); } //数据的合并。 public static void meger(File dir)throws IOException{ if(!(dir.exists() && dir.isDirectory())) throw new RuntimeException("指定的目录不存在,或者不是正确的目录"); File[] files = dir.listFiles(new SuffixFilter(CFG)); if(files.length==0) throw new RuntimeException("扩展名.proerpties的文件不存在"); //获取到配置文件 File config = files[0]; //获取配置文件的信息。 Properties prop = new Properties(); FileInputStream fis = new FileInputStream(config); prop.load(fis); String fileName = prop.getProperty("filename"); int partcount = Integer.parseInt(prop.getProperty("partcount")); //-------------------------- File[] partFiles = dir.listFiles(new SuffixFilter(SP)); if(partFiles.length!=partcount) throw new RuntimeException("缺少碎片文件"); //--------------------- ArrayList<FileInputStream> al = new ArrayList<FileInputStream>(); for(int x=0; x<partcount; x++){ al.add(new FileInputStream(new File(dir,x+SP))); } Enumeration<FileInputStream> en = Collections.enumeration(al); SequenceInputStream sis = new SequenceInputStream(en); File file = new File(dir,fileName); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[1024]; int len = 0; while((len=sis.read(buf))!=-1){ fos.write(buf,0,len); } fos.close(); sis.close(); } //带有配置信息的数据切割。 public static void splitFile(File file)throws IOException{ //用一个读取流和文件关联。 FileInputStream fis = new FileInputStream(file); //创建目的地。因为有多个。所以先创建引用。 FileOutputStream fos = null; //指定碎片的位置。 File dir = new File("c:\\partfiles"); if(!dir.exists()) dir.mkdir(); //碎片文件大小引用。 File f = null; byte[] buf = new byte[1024*1024]; //因为切割完的文件通常都有规律的。为了简单标记规律使用计数器。 int count = 0; int len = 0; while((len=fis.read(buf))!=-1){ f = new File(dir,(count++)+".part"); fos = new FileOutputStream(f); fos.write(buf,0,len); fos.close(); } //碎片文件生成后,还需要定义配置文件记录生成的碎片文件个数。以及被切割文件的名称。 //定义简单的键值信息,可是用Properties。 String filename = file.getName(); Properties prop = new Properties(); prop.setProperty("filename",filename); prop.setProperty("partcount",count+""); File config = new File(dir,count+".properties"); fos = new FileOutputStream(config); prop.store(fos,""); fos.close(); fis.close(); } } class SuffixFilter implements FileFilter{ private String suffix; SuffixFilter(String suffix){ this.suffix = suffix; } public boolean accept(File file){ return file.getName().endsWith(suffix); } } ----------------------------------------------------------------------------------------------- RandomAccessFile: 特点: 1:该对象即可读取,又可写入。 2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。 3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。 4:该对象操作的源和目的必须是文件。 5:其实该对象内部封装了字节读取流和字节写入流。 注意:实现随机访问,最好是数据有规律。 class RandomAccessFileDemo{ public static void main(String[] args) throws IOException{ write(); read(); randomWrite(); } //随机写入数据,可以实现已有数据的修改。 public static void randomWrite()throws IOException{ RandomAccessFile raf = new RandomAccessFile("random.txt","rw"); raf.seek(8*4); System.out.println("pos :"+raf.getFilePointer()); raf.write("王武".getBytes()); raf.writeInt(102); raf.close(); } public static void read()throws IOException{ RandomAccessFile raf = new RandomAccessFile("random.txt","r");//只读模式。 //指定指针的位置。 raf.seek(8*1);//实现随机读取文件中的数据。注意:数据最好有规律。 System.out.println("pos1 :"+raf.getFilePointer()); byte[] buf = new byte[4]; raf.read(buf); String name = new String(buf); int age = raf.readInt(); System.out.println(name+"::"+age); System.out.println("pos2 :"+raf.getFilePointer()); raf.close(); } public static void write()throws IOException{ //rw:当这个文件不存在,会创建该文件。当文件已存在,不会创建。所以不会像输出流一样覆盖。 RandomAccessFile raf = new RandomAccessFile("random.txt","rw");//rw读写模式 //往文件中写入人的基本信息,姓名,年龄。 raf.write("张三".getBytes()); raf.writeInt(97); raf.close(); } } ------------------------------------------------------------------------------------------------ 管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。 注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。 public static void main(String[] args) throws IOException{ PipedInputStream pipin = new PipedInputStream(); PipedOutputStream pipout = new PipedOutputStream(); pipin.connect(pipout); new Thread(new Input(pipin)).start(); new Thread(new Output(pipout)).start(); } ------------------------------------------------------------------------------------------------ 对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。 注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。 如何将非静态的数据不进行序列化?用transient关键字修饰此变量即可。 Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。 import java.io.*; class ObjectStreamDemo { public static void main(String[] args) throws Exception{ writeObj(); readObj(); } public static void readObj()throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt")); Object obj = ois.readObject();//读取一个对象。 System.out.println(obj.toString()); } public static void writeObj()throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt")); oos.writeObject(new Person("lisi",25));//写入一个对象。 oos.close(); } } class Personimplements Serializable{ private static final longserialVersionUID= 42L; privatetransientString name;//用transient修饰后name将不会进行序列化 public int age; Person(String name,int age){ this.name = name; this.age = age; } public String toString(){ return name+"::"+age; } } ----------------------------------------------------------------------------------------------- DataOutputStream、DataInputStream:专门用于操作基本数据类型数据的对象。 DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt")); dos.writeInt(256); dos.close(); DataInputStream dis = new DataInputStream(new FileInputStream("data.txt")); int num = dis.readInt(); System.out.println(num); dis.close(); ----------------------------------------------------------------------------------------------- ByteArrayInputStream:源:内存 ByteArrayOutputStream:目的:内存。 这两个流对象不涉及底层资源调用,操作的都是内存中数组,所以不需要关闭。 直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素。 对于数组元素操作无非两种操作:设置(写)和获取(读),而这两操作正好对应流的读写操作。这两个对象就是使用了流的读写思想来操作数组。 //创建源: ByteArrayInputStream bis = new ByteArrayInputStream("abcdef".getBytes()); //创建目的: ByteArrayOutputStream bos = new ByteArrayOutputStream(); int ch = 0; while((ch=bis.read())!=-1){ bos.write(ch); } System.out.println(bos.toString()); ----------------------------------------------------------------------------------------------- 网络编程(重点理解): 端口: 物理端口: 逻辑端口:用于标识进程的逻辑地址,不同进程的标识;有效端口:0~65535,其中0~1024系统使用或保留端口。 java 中ip对象:InetAddress. import java.net.*; class IPDemo{ public static void main(String[] args) throws UnknownHostException{ //通过名称(ip字符串or主机名)来获取一个ip对象。 InetAddress ip = InetAddress.getByName("www.baidu.com");//java.net.UnknownHostException System.out.println("addr:"+ip.getHostAddress()); System.out.println("name:"+ip.getHostName()); } } Socket:,套接字,通信的端点。 就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。 UDP传输: 1,只要是网络传输,必须有socket 。 2,数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。 直接操作udp不可能,对于java语言应该将udp封装成对象,易于我们的使用,这个对象就是DatagramSocket. 封装了udp传输协议的socket对象。 因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包对象就是:DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。 DatagramSocket具备发送和接受功能,在进行udp传输时,需要明确一个是发送端,一个是接收端。 udp的发送端: 1,建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。 2,明确要发送的具体数据。 3,将数据封装成了数据包。 4,用socket服务的send方法将数据包发送出去。 5,关闭资源。 -------------------------------------------------------------- import java.net.*; class UdpSend{ public static void main(String[] args)throws Exception { //1,建立udp的socket服务。 DatagramSocket ds = new DatagramSocket(8888);//指定发送端口,不指定系统会随机分配。 //2,明确要发送的具体数据。 String text = "udp传输演示 哥们来了"; byte[] buf = text.getBytes(); //3,将数据封装成了数据包。 DatagramPacket dp = new DatagramPacket(buf, buf.length,InetAddress.getByName("10.1.31.127"),10000); //4,用socket服务的send方法将数据包发送出去。 ds.send(dp); //5,关闭资源。 ds.close(); } } ------------------------------------------------------------- udp的接收端: 1,创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个接收端可以处理的数据。 2,定义数据包,用于存储接收到数据。 3,通过socket服务的接收方法将收到的数据存储到数据包中。 4,通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。 5,关闭资源。 ------------------------------------------------------------- class UdpRece { public static void main(String[] args) throws Exception{ //1,创建udp的socket服务。 DatagramSocket ds = new DatagramSocket(10000); //2,定义数据包,用于存储接收到数据。先定义字节数组,数据包会把数据存储到字节数组中。 byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf,buf.length); //3,通过socket服务的接收方法将收到的数据存储到数据包中。 ds.receive(dp);//该方法是阻塞式方法。 //4,通过数据包的方法获取数据包中的具体数据内容,比如ip,端口,数据等等。 String ip = dp.getAddress().getHostAddress(); int port = dp.getPort(); String text = new String(dp.getData(),0,dp.getLength());//将字节数组中的有效部分转成字符串。 System.out.println(ip+":"+port+"--"+text); //5,关闭资源。 ds.close(); } } ------------------------------------------------------------- TCP传输:两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。 tcp的两个端点:一个是客户端,一个是服务端。 客户端:对应的对象,Socket 服务端:对应的对象,ServerSocket TCP客户端: 1,建立tcp的socket服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。 2,如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到socket流中的读取流和写入流即可,只要通过getInputStream和getOutputStream就可以获取两个流对象。 3,关闭资源。 -------------------------------------------------------------- import java.net.*; import java.io.*; //需求:客户端给服务器端发送一个数据。 class TcpClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("10.1.31.69",10002); OutputStream out = s.getOutputStream();//获取了socket流中的输出流对象。 out.write("tcp演示,哥们又来了!".getBytes()); s.close(); } } -------------------------------------------------------------- TCP服务端: 1,创建服务端socket服务,并监听一个端口。 2,服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象。 3,可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。 4,如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。 -------------------------------------------------------------- class TcpServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10002);//建立服务端的socket服务 Socket s = ss.accept();//获取客户端对象 String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+".....connected"); //可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。 InputStream in = s.getInputStream();//读取客户端的数据,使用客户端对象的socket读取流 byte[] buf = new byte[1024]; int len = in.read(buf); String text = new String(buf,0,len); System.out.println(text); //如果通讯结束,关闭资源。注意:要先关客户端,在关服务端。 s.close(); ss.close(); } } ----------------------------------------------------------------------------------------------- 反射技术(重点理解) 其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。 反射的好处:大大的增强了程序的扩展性。 反射的基本步骤: 1、获得Class对象,就是获取到指定的名称的字节码文件对象。 2、实例化对象,获得类的属性、方法或构造函数。 3、访问属性、调用方法、调用构造函数创建对象。 获取这个Class对象,有三种方式: 1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。 2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。 前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。 3:使用的Class类中的方法,静态的forName方法。 指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。 // 1. 根据给定的类名来获得 用于类加载 String classname = "cn.itcast.reflect.Person";// 来自配置文件 Class clazz =Class.forName(classname);// 此对象代表Person.class // 2. 如果拿到了对象,不知道是什么类型 用于获得对象的类型 Object obj = new Person(); Class clazz1 = obj.getClass();// 获得对象具体的类型 // 3. 如果是明确地获得某个类的Class对象 主要用于传参 Class clazz2 = Person.class; 反射的用法: 1)、需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式: Class.forName(classname)用于做类加载 obj.getClass()用于获得对象的类型 类名.class 用于获得指定的类型,传参用 2)、反射类的成员方法: Class clazz = Person.class; Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2}); method.invoke(); 3)、反射类的构造函数: Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...}) con.newInstance(params...) 4)、反射类的属性: Field field = clazz.getField(fieldName); field.setAccessible(true); field.setObject(value); 获取了字节码文件对象后,最终都需要创建指定类的对象: 创建对象的两种方式(其实就是对象在进行实例化时的初始化方式): 1,调用空参数的构造函数:使用了Class类中的newInstance()方法。 2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数)进行对象的初始化。 综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。 ------------------------------------------------------ // 如何生成获取到字节码文件对象的实例对象。 Class clazz = Class.forName("cn.itcast.bean.Person");//类加载 //直接获得指定的类型 clazz = Person.class; //根据对象获得类型 Object obj =newPerson("zhangsan", 19); clazz = obj.getClass(); Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2(); public static void method_2() throws Exception{ Class clazz = Class.forName("cn.itcast.bean.Person"); //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。 //获取一个带参数的构造器。 Constructor constructor = clazz.getConstructor(String.class,int.class); //想要对对象进行初始化,使用构造器的方法newInstance(); Object obj = constructor.newInstance("zhagnsan",30); //获取所有构造器。 Constructor[] constructors = clazz.getConstructors();//只包含公共的 constructors = clazz.getDeclaredConstructors();//包含私有的 for(Constructor con : constructors){ System.out.println(con); } } ------------------------------------------------------ 反射指定类中的方法: //获取类中所有的方法。 public static void method_1() throws Exception{ Class clazz = Class.forName("cn.itcast.bean.Person"); Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。 methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。 for(Method method : methods){ System.out.println(method); } } //获取指定方法; public static void method_2() throws Exception{ Class clazz = Class.forName("cn.itcast.bean.Person"); //获取指定名称的方法。 Method method = clazz.getMethod("show", int.class,String.class); //想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。 Object obj = clazz.newInstance(); method.invoke(obj, 39,"hehehe");//执行一个方法 } //想要运行私有方法。 public static void method_3() throws Exception{ Class clazz = Class.forName("cn.itcast.bean.Person"); //想要获取私有方法。必须用getDeclearMethod(); Method method = clazz.getDeclaredMethod("method", null); // 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。 method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。 } //反射静态方法。 public static void method_4() throws Exception{ Class clazz = Class.forName("cn.itcast.bean.Person"); Method method = clazz.getMethod("function",null); method.invoke(null,null); } ------------------------------------------------------------------------------------------------ 正则表达式(理解): 其实是用来操作字符串的一些规则。 好处:正则的出现,对字符串的复杂操作变得更为简单。 特点:将对字符串操作的代码用一些符号来表示。只要使用了指定符号,就可以调用底层的代码对字符串进行操作。符号的出现,简化了代码的书写。 弊端:符号的出现虽然简化了书写,但是却降低了阅读性。 其实更多是用正则解决字符串操作的问题。 组:用小括号标示,每定义一个小括号,就是一个组,而且有自动编号,从1开始。 只要使用组,对应的数字就是使用该组的内容。别忘了,数组要加\\。 (aaa(wwww(ccc))(eee))技巧,从左括号开始数即可。有几个左括号就是几组。 常见操作: 1,匹配:其实用的就是String类中的matches方法。 String reg = "[1-9][0-9]{4,14}"; boolean b = qq.matches(reg);//将正则和字符串关联对字符串进行匹配。 2,切割:其实用的就是String类中的split方法。 3,替换:其实用的就是String类中的replaceAll(); 4,获取: 1),先要将正则表达式编译成正则对象。使用的是Pattern中静态方法 compile(regex); 2),通过Pattern对象获取Matcher对象。 Pattern用于描述正则表达式,可以对正则表达式进行解析。 而将规则操作字符串,需要从新封装到匹配器对象Matcher中。 然后使用Matcher对象的方法来操作字符串。 如何获取匹配器对象呢? 通过Pattern对象中的matcher方法。该方法可以正则规则和字符串想关联。并返回匹配器对象。 3),使用Matcher对象中的方法即可对字符串进行各种正则操作。

优秀的个人博客,低调大师

CentOS基本操作与知识

首先我要说明的问题是对于centOS系统,我们不应该抱着浅尝辄止的态度,有时候有些东西必须要深入,要记录下来。我写这篇文章就是为了把我在学习过程中了解到的东西记录下来,防止以后我有用的时候能够迅速的拾起来! 首先就是对于ls这个基本命令的认识,这是学习Linux命令都是放在第一位去了解的。但是在这之前,我们先来了解一下CentOS系统的目录结构。我以前学的时候就是吃了这个亏,搞不懂目录结构,东西乱放还不知道放在哪里了,安装什么东西全靠教程去搞,很迷糊;下面就来说说这个非常简单的目录结构: 这个图片中root代表当前登录用户,localhost代表主机名, ~代表当前主机目录,#代表用户权限 #表示超级用户,$表示普通用户; 注意除了波浪号,我们发现还会有/的情况,这其实就是根目录。也就是说我们可以明白~和/是不一样的,明白了这一点,东西装在哪里就好说了,值得一提的是/root这个文件就是~代表的目录位置!也就是超级用户的家目录。 了解了这些,我们再来看一下对于LS的命令的一些使用: 我们用 -l 或者 -la 组合 列出详细信息; [root@localhost ~]# ls -l 总用量 4 -rw-------. 1 root root 1237 6月 10 05:24 anaconda-ks.cfg [root@localhost ~]# ls -la 总用量 24 dr-xr-x---. 2 root root 135 6月 11 21:13 . dr-xr-xr-x. 17 root root 224 6月 10 05:23 .. -rw-------. 1 root root 1237 6月 10 05:24 anaconda-ks.cfg -rw-------. 1 root root 0 6月 11 21:13 .bash_history -rw-r--r--. 1 root root 18 12月 29 2013 .bash_logout -rw-r--r--. 1 root root 176 12月 29 2013 .bash_profile -rw-r--r--. 1 root root 176 12月 29 2013 .bashrc -rw-r--r--. 1 root root 100 12月 29 2013 .cshrc -rw-r--r--. 1 root root 129 12月 29 2013 .tcshrc [root@localhost ~]# 这里第一列 比如dr-xr-xr-x. 代表文件类型以及所有者,所属组以及其他者权限 第一位d代表文件类型 常见的有 - 文件 d目录 | 软件链接文件 后面9位 每3位一个组 分别是 所有者u 所属组g 以及 其他者o的权限 权限分三种 r读 w写 x执行 比如dr-xr-xr-x 这个目录 所有者 所属组 以及其他者 都有 读和执行权限; 比如 -rw------- 这个文件 所有者有读写权限 所属组以及其他者没有权限; 第二列 那个数字 是 硬链接次数 后面再说; 第三列 root 是 所有者; 第四列 root 是 所属组; 第五列 是文件大小;假如看不惯 可用 ls -lh [root@localhost ~]# ls -lh 总用量 4.0K -rw-------. 1 root root 1.3K 6月 10 05:24 anaconda-ks.cfg 第六列的日期是该文件最后一次修改时间; 最后一列 是文件名称; 查看目录属性 要加 -d [root@localhost ~]# ls -ld /etc/ drwxr-xr-x. 78 root root 8192 6月 13 15:50 /etc/ [root@localhost ~]# 查看文件的inode属性 类似 主键 唯一识别文件的Id 用 -i [root@localhost ~]# ls -li 总用量 4 33574979 -rw-------. 1 root root 1237 6月 10 05:24 anaconda-ks.cfg [root@localhost ~]# 33574979 就是inode属性 以上就是ls的几个操作,觉得有用的话可以一起交流

优秀的个人博客,低调大师

区块链开发知识框架

区块链学习更多的是去理解这种去中心化的思想和去中心化的价值所在,语言倒是其次的东西。 不过由于以太坊和Hyperledger Fabric 对go语言支持最好,所以go语言肯定是要学习的。 下面把我另外一个问题的答案copy过来,希望对你有所帮助。 背景介绍:本人从2015年开始研究区块链技术,也算是对区块链有一定的了解吧。 申明:预估看到本问题的知乎er应该都是想要入门区块链,那么本回答也主要是进行一个入门级别的介绍。只是入门级别介绍,不适合区块链从业人员提升学习。 ----------------------------------- 回答的目录如下: 一、区块链是什么? 二、从业务角度学习区块链技术? 三、从技术角度学习区块链技术? 四、学习资料推荐与共享? ------------------------------------- 一、区块链是什

优秀的个人博客,低调大师

CSS相关知识点

1、高和行高也可以撑开盒子,背景图不行。 2、文字若不设置行高,是包含文字的盒子的行高。 4、如果给了定位,但是没有给left,top等值,默认会腾出行内元素、padding的位置,有的时候我们可以使用这些特性,有的时候我们不熟悉的话可能产生bug。 5、较少功能使用较少代码(a代替ui>li>a) 6、如果盒子都是左对齐的话,最后一个盒子在右边的位置不够的话,会掉下来,如果第一个盒子A比第二个盒子B高,那么最后一个盒子C掉下来后跟第二个盒子B左对齐,而不是跟第一个盒子A左对齐。如果最后一个盒子C后面还有一个盒子D的话,D盒子的顶端跟C对齐。(下图 JD logo 是 A 盒子, 搜索框是 B 盒子, 周杰伦的歌是 C 盒子,我的购物车是 D 盒子。) 7、标准流中的文字不会被浮动的盒子盖住。所以一个大盒子中的小盒子要么都浮动要么都不浮动。 8、父盒子高度为0 ,子盒子如果是浮动的话不占位置,下面的标准流盒子将会跑到子盒子下面。 或者使用了定位,父盒子高度为0,然后子绝父相,下面的标准流盒子依然会跑到子盒子下面。 (这个可以做类似京东的侧边栏,如果侧边栏挡住了跑上来的标准流盒子,那么把包含标准流的整个大盒子定位:position: relative ,因为定位的层级高,所以就可以显示标准流的所有内容了。) 9、想要盒子随着界面变大变小,而盒子随着界面的中线能够移动的时候(类似定位/脱标的盒子居中对齐),不要加版心(margin: 0 auto;)。 10、父盒子有高度,但是子盒子太高,父盒子会被撑破;如果父盒子没有高度,那么父盒子会被撑开,是所有子盒子最高的高度。撑开的盒子可能会产生影响。(不要让浮动的盒子超出父盒子) 11、浮动盒子的相互影响,不管是否在一个大盒子里面(蓝盒子是包含在粉红盒子里面的,紫盒子和粉红盒子是并列的)。 12、谷歌浏览器不支持12号以下字体大小。 13、子盒子不占位置,父盒子高度为0,会导致下面的盒子顶上来了。(清除浮动) 14、curser: pointer; 模拟鼠标小手样式。 15、<a href=""></a> 如果 href 值为空,刷新页面。 如果 href 值为#,跳转到页面顶端,不刷新页面。 如果 href 值为javascript:; 或者javascript:void(0) ,不刷新页面,也不跳转。 16、定位的时候,left 的权限比 right 权限高,top 比 bottom 高,提高权限也没用。 17、图片半透明 opacity:0.4; 优点方便。缺点:里面的内容也会半透明。 CSS3的技术来解决半透明: background: rgba(0,0,0,0.3); background: rgba(0,0,0, .3); 18、行高可以继承。 19、如果一个大盒子装的是 li 标签,而且 li 标签是浮动的,如果 li 里面的内容超过了大盒子的话,会有 li 标签掉下来,如何使得所有的 li 标签在一行显示呢?用一个辅助盒子装下所有的 li 标签,然后大盒子只装辅助盒子,这样对于大盒子来说,所有的 li 标签都会在一行显示,即使大盒子很小。而辅助盒子可以使用 ul 来代替,给 ul 一个所有 li 加起来的宽度多一点即可,但是多的一点要少于一个 li 的宽度。 20、行内元素给了定位,不需要转block,只有static, relative定位的时候需要转 block。 21、盒子的border有重叠怎么办,可以往左上移动border的margin来覆盖。 22、background: url("spirit.png") -135px 0; 关于背景の问题: 有时候我发现background后面两个px可以调节位置,有的时候又必须使用left,top等调位置。其实后面的两个px本来就是调位置的,而且调的是整个背景的位置,当背景是整张图片的时候,调节这两个px就可以了,但是精灵图因为需要的只是背景图中的某一个区域,调节这两个px只是将选中的区域移动到定位的原点,然后再使用left,top等将选定的区域调节到定位的具体位置。

优秀的个人博客,低调大师

JSP必备知识点

servlet VS CGI 1)servlet 可移植 跨平台 CGI 不行 2)在传统 CGI 中每个请求都要启动一个新进程,若 CGI 程序的本身执行时间较短,启动进程所需要的进行所需要的开销很可能反而超过实际执行的时间;servlet,每个请求由轻量级的 java 线程处理 3)在传统 CGI 中,若有 N 个并发的对同一个 CGI 的请求,该 CGI 的程序代码在内存中重载了 N 次,对于 servlet,处理请求的是 N 个线程,只要一份 servlet 类代码 如何实现 servlet 单线程模式 要实现单线程模式,可以在配置文件中修改 isThreadSafe 属性,比如,<%@page isThreadSafe=”false”%> servlet 页面间对象传递的方法有几种 用 request,session,application。Cookie 等方法实现页面间的对象传递。 jsp VS servlet jsp 是 servlet 技术的扩展,本质上是 servlet 的简单方式,jsp 编译后是“类 servlet”。他们最主要额不同在于:servlet 的应用逻辑在 java 文件中,并且完全从表示层中的 html 分离出来。Jsp 是 java 和 html 可以组合为一个扩展名为.jsp 的文件。Jsp 侧重视图, servlet 侧重控制逻辑。 <jsp:include page=”include.jsp” flash=”true”> <jsp:forword page=”next.jsp”> 前者页面不会转向 include 所指向的页面,只显示该页的结果,主页面还是原来的页面,指向完以后还会回来,相当于函数调用,并且可以带参数。后者完全转向新页面,不会再回来,相当于 goto 语句。 内置对象 1)request:用户端的请求,此请求会包含来着 get or post 请求的参数。 2)response:网页传回用户端的响应。 3)pageContext:网页的属性 4)session:与请求有关的会话 5)application:servlet 正在执行的内容 6)out:传送回应的输出 7)conig:servlet 的部件 8)page:gsp 网页本身 9)exception:网页错误 哪些动作(6 个) 1)jsp:include 在页面被请求时,引入一个文件 2)jsp:useBean 寻找 or 实例化一个 JavaBean 3)jsp:setProperty 这只 javaBean 的属性 4)jsp:getProperty 输出某个 javaBean 的属性 5)jsp:forword 把请求转到一个新页面 6)jsp:plugin 根据浏览器类型为 java 插件生成 object or embed 标配 动态 include 与静态 include 动态 include 用 jsp:include 动作实现,它总是会检查所包含文件中的变化,使用于包含动态页面 静态 include 用 include 伪码实现,不会检查所含文件的变化,适用于包含静态页面。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册