✨这里是小松猿的博客✨小松,欢迎您的到来~✨
系列专栏:无
✈️本篇内容: java基础
本篇收录完整代码地址:无
复习一下java基础,作为一名技术人员,要让自己随时处于可以面试的一种状态,不能被动,要主动
反射
多线程
IO流
静态导入
可变参数
泛型
枚举
注解
单一职责原则:一个类只负责一个功能领域中的相应职责
开闭原则:对扩展开放,对修改关闭
依赖倒置原则:依赖于抽象而不依赖于具体。
接口隔离原则:使用多个隔离的接口,比使用单个接口要好
里氏替换原则:任何基类可以出现的地方,子类一定可以出现
迪米特原则:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
创建型(5种):
#单例模式、
#工厂方法模式、
#抽象工厂模式、
#建造者模式、
#原型模式
结构型(7种):适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型(11种):策略模式、模版方法模式、观察者模式、迭代模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
a,内存模型
程序计数器:线程私有,一块较小的内存空间,是jvm执行程序的流水线,存放一些跳转指令,维护下一个将要执行指令的地址。(仅限于Java方法, Native方法该计数器值为undefined).
java虚拟机栈:线程私有,每个方法执行都会创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度).
本地方法栈:虚拟机使用到的Native方法服务
java堆:-Xmx最大堆内存 -Xms初始堆内存。所有线程共享,GC收集器管理的主要区域,jvm创建的对象存放于此,主要分为新生代和旧生代,而新生代又分为eden区(80%),from survivor(10%)和to survivor区,之所以这么分是因为新生代中98%的对象是朝生夕死,
##所以采用复制算法的GC策略,将eden和其中一块survivor中存活的对象复制到另一块survivor区,然后清理eden和用过的survivor区
方法区:所有线程共享,方法区是jvm规范中定义的一个概念,存放加载的类,常量,静态变量,JIT编译后的代码等数据。hotspot中用永久代来实现,别的jvm没有永久代的概念
在Java 6中,方法区中包含的数据,除了JIT编译生成的代码存放在native memory的CodeCache区域,其他都存放在永久代;
在Java 7中,Symbol的存储从PermGen移动到了native memory,并且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内);
在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),-XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。
元空间:jdk1.8中移除永久代,而使用元空间,两者都是对jvm方法区的实现,不过元空间并不在虚拟机中,而是使用本地内存。
使用元空间代替永久代的原因:
#字符串存在永久代中,容易出现性能问题和内存溢出
#类及方法信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
#永久代会为GC带来不必要的复杂度,并且回收效率偏低
#oracle可能会将HotSpot与JRockit合二为一
内存分配原则:
#对象优先在Eden分配,当没有足够空间触发Minor GC
#大对象直接进入老年代,很长的字符串及数组都是大对象
#长期存活的对象进入老年代,新生代中经历一次Minor GC年龄就增加一岁,当达到默认的15岁,就进入老年代,这个默认值可以设置
注:直接内存:直接内存并不是JVM运行时数据区的一部分, 但也会被频繁的使用: 在JDK 1.4引入的NIO提供了基于Channel与Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存(java.nio.ByteBuffer.allocateDirect()), 然后使用DirectByteBuffer对象作为这块内存的引用进行操作(详见: Java I/O 扩展),
这样就避免了在Java堆和Native堆中来回复制数据, 因此在一些场景中可以显著提高性能. 显然, 本机直接内存的分配不会受到Java堆大小的限制(即不会遵守-Xms、-Xmx等设置), 但既然是内存, 则肯定还是会受到本机总内存大小及处理器寻址空间的限制, 因此动态扩展时也会出现OutOfMemoryError异常.
b,GC机制
#判断对象死亡算法
#引用计数法:给对象添加一个引用计算器,每当有地方引用它就加1,引用失效就减1,为0时就表示对象不再被使用,简单效率高,但是存在一个对象之间相互循环引用问题。
#可达性分析算法:主流语言都采用此算法判断对象存活,原理是对象到GC Roots是不可达的,就认定为此对象可回收。
##在java语言中,可作为GC Roots的对象包括:虚拟机栈中引用的对象、java堆类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象
#垃圾收集算法
#标记-清除算法:最基础的收集算法,后续算法都是基于这种思路改进。分为标记和清楚两个过程,但是效率不高,且产生大量不连续内存碎片。再分配大对象时,可能无法找到足够的连续内存而导致提前出发一次gc
#复制算法:实现简单,效率高,现在商用虚拟机都采用这种算法来回收新生代。将eden和其中一块survivor中存活的对象复制到另一块survivor区,然后清理eden和用过的survivor区
#标记整理算法:复制算法当对象存活率较高时效率会降低,所以根据老年代特点设计出这种算法,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
#分代收集:根据对象存活周期不同选择以上三种不同的算法去收集,java堆新生代使用 复制算法,旧生代使用 标记-整理算法
#垃圾回收器
#串行垃圾回收器Serial:最基本,最古老的收集器,只用一个单独的线程进行垃圾回收,冻结所有应用程序进行工作,客户端可能会用
#并行垃圾回收器Parallel:使用多线程进行垃圾回收,停止其他所有应用程序线程
#并发标记扫描垃圾回收器CMS:基于标记-清除算法实现,主要有四步:初始标记-并发标记-重新标记-并发清除,初始标记,重新标记任要停止其他应用线程。并发收集,低停顿。缺点使用标记-清除算法
#G1垃圾回收器:目前最先进的收集器,替换CMS,将整个java堆划分为多个大小相等的独立区。特点:并行与并发、分代收集、空间整合(标记-整理)、 可预测停顿
c,类加载
#类加载过程5步
#加载
#通过一个类的全限定名来获取定义此类的二进制字节流
#将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
#在内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口
#验证
#文件格式验证:验证字节流是否符合Class文件格式规范
#元数据验证:是否有父类、父类是否继承final修饰类、不是抽象类是否实现父类或接口需要实现的方法
#字节码验证:确定语义合法、符合逻辑
#引用符号验证:检查引用的类、字段、方法的访问性是否可悲当前类访问等。
#准备
#为类变量分配内存并设置初始化值
#解析
#类或接口解析
#字段解析
#类方法解析
#接口方法解析
#初始化
#执行
#类加载器
#种类4类:启动类加载器(c++实现)、扩展类加载器->应用程序类加载器->自定义类加载器
#双亲委派模型:一个类加载器收到类加载请求,先判断自己是否已经加载了此类,如果没有则会把这个请求交给父类加载器完成,
##父类加载器同样判断自己是否已加载,如果没有加载则一层一层往上,直到有父类反馈这个类不该自己加载,子类才自己去加载,优点:安全,避免重复加载
#注意:很多文章介绍类加载等级时将启动类加载器列在扩展类加载器的上一级,其实不对,因为启动类加载器由c++实现,属于jvm的一部分,并不属于jvm的类等级结构,
##而且启动类加载器也没有子类
#常用数据结构:
#堆栈(Stack):先进后出,压栈和弹栈,访问、插入和删除元素只能在栈顶进行
#队列(Queue):先进先出,元素只能从队列尾插入,从队列头访问和删除。
#数组(Array):数组连续内存空间,查找速度快,增删需要创建新数组效率慢
#链表(Linked):存储空间是不连续的,首尾存储上下一个节点的信息,所以寻址麻烦,查找速度慢,但是增删快
#哈希表(Hash):哈希表综合了它们两个的优点,一个哈希表,由数组和链表组成。
#二叉树(Tree):每个节点最多有两个子树的树结构,数组和链表的折中方式
#Collection
#List:允许重复
#ArrayList:查询快,增删慢,内部是通过数组实现的,默认大小为10,默认是扩展50% + 1个
#LinkedList:增删快,查询慢,内部是通过双向链表结构存储数据的
#Vector:与ArrayList功能相似,也是通过数组实现的,默认大小为10,线程安全(synchronized),但是效率低,不推荐使用Vector,即使是需要线程安全。默认扩展1倍。
#Stack:先进后出,继承于Vector,也通过数组实现的,线程安全的,性能较差。
#Set:不允许重复
#HashSet:无序,按照Hash算法存储元素,基于HashMap,key存储元素,value为一个final的Object对象,具有良好的存取和查找性能,非线程安全的
#TreeSet:采用红黑树的结构来存储集合元素,基于TreeMap实现,value为一个final的Object对象,TreeSet支持两种排序方法:自然排序和定制排序。
#LinkedHashSet:HashSet的一个子类,使用链表来维护元素的次序
#总结
#HashSet性能最高,要实现排序Set使用TreeSet,实现插入顺序用LinkedHashSet
#在HashSet和TreeSet中尽量只添加不可变对象
#上述三个Set的实现类都是线程不安全的。如果多个线程同时访问修改一个Set集合,必须手动实现线程同步性。例如通过Collections工具类的synchronizeSorted方法包装Set集合
#Map
#HashMap:HashMap在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,
##会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
##默认大小16,加载因子为0.75,可以使用Collections.synchronizeMap(hashMap)实现同步
#LinkedHashMap:相对于HashMap,插入的时候有序,所以排序的时候
#HashTable:数据结构与HashMap一致,只不过HashTable线程安全(synchronized),效率差,需要同步建议使用ConcurrentHashMap
#ConcurrentHashMap:jdk1.5以后,与HashTable相同是线程安全,但是在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
#TreeMap:底层使用的数据结构是二叉树,无序,不允许重复(无序指元素顺序与添加顺序不一致),TreeMap集合默认会对键进行排序,所以键必须实现自然排序和定制排序中的一种
#主内存与工作内存:java中实例字段,静态字段、构成数组对象的元素都存放在主内存中,每个线程需要操作共享变量都需要与主内存互交完成
#内存互交操作8种
#锁定lock:作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
#解锁unlock:作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
#读取read:作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
#载入load:作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
#使用use:作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
#赋值assign:作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
#存储store:作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
#写入write:作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
#read与load、store与write必须一一对应。
#并发3个特征
#原子性:基本数据类型的访问读写具备原子性(long和double两个64位数据非原子性协议,在32位的计算机上),synchronized可以保证更大范围的原子性
#可见性:当一个线程修改了共享变量,其他线程能够立即得知新值,volatile、synchronized、final都可以保证可见性
#有序性:java天然有序:线程内表现为串行的语义,一个线程观察另一个线程,所有操作都是无序的:指令重排和工作内存与主内存同步延迟现象
#关键字volatile
#轻量级同步机制,不保存副本,每次使用前立即从主内存刷新
#保证可见性,但不保证原子性,所以当有多线程对它进行修改操作,可能获得不正确值
#禁止指令重排序优化
#使用场景:运算不依赖当前结果或只有一个线程会修改变量的值、不需要其他状态变量共同参与不变约束,比如ThreadPoolExecutor线程池类中定义了很多volatile变量,以保证其他变量的可见性
#volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。
#线程
#定义:线程是cpu调度最小单位(进程是cpu资源分配的最小单位),线程是建立在进程的基础上的一次程序运行单位,各个线程既可以共享进程资源
#创建线程实现的三种方式:
#继承Thread类,重写run方法
#实现Runnable接口,重写run方法
#通过Callable和Future创建线程,过程复杂,不常用
#三种方式对比:
#1和3都是实现接口,还可以继承其他类、多线程可以共享一个target对象,缺点访问当前线程使用Thread.currentThread()方法
#2使用继承方式不可再继承其他类,获取当前线程直接用this即可
#Callable和Future方式可以有返回值,而Runnable没有
#建议实现Runnable接口来创建线程
#线程共有10个优先级
#5种状态
#新建new:创建后尚未启动的线程处于这种状态。start() -> running
#运行runable:正在执行或等待cpu分配执行时间。Thread.yield():线程放弃运行,将CPU的控制权让出。
#等待waiting:
#无限期等待:需要被其他线程显示唤醒 notify()/notifyAll() -> Running -> wait()
#没有设置Timeout参数的Object.wait()方法。
#没有设置Timeout参数的Thread.join()方法
#LockSupport.park()方法
#限期等待:不需要其他线程显示唤醒
#Thread.sleep()方法
#设置了Timeout参数的Object.wait()方法
#设置了Timeout参数的Thread.join()方法
#LockSupport.parkNanos()方法
#LockSupport.parkUntil()方法
#阻塞blocked:等待其他线程放弃这个锁,synchronized
#结束terminated:已终止线程的线程状态,线程已经结束执行。run()结束
#线程安全
#定义:如果一个对象可以安全地被多个线程同时使用,那它就是线程安全的
#实现方法:
#阻塞同步:synchronized和重入锁(ReentrantLock)
#非阻塞同步:先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施
#无同步:有些代码天生安全,比如不涉及共享数据
#线程池
#定义:频繁的创建和销毁线程会消耗大量资源,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理
#实现:java.util.concurrent.ThreadPoolExecutor
#优点:
#避免线程的创建和销毁带来的性能开销。
#避免大量的线程间因互相抢占系统资源导致的阻塞现象。
#能够对线程进行简单的管理并提供定时执行、间隔执行等功能
#死锁
#定义:多个线程同时等待其他线程释放锁,导致被无限期阻塞
#原因:A线程持有锁1,这时主内存的锁1变量进入锁定状态,其他想获得此变量的的线程必须等待。B线程持有锁2,主内存中的锁2变量进入锁定状态。
##这时A线程再去获取锁2,B线程再去获取锁1,而此时A、B线程都没有对原先锁变量进行解锁,故A线程等待B线程释放锁2,而B线程等待A线程释放锁1。
##这时就出现了A、B线程同时被无限期阻塞,故导致死锁
#避免方法:
#加锁顺序:上述例子出现死锁因为A、B线程加锁的顺序不同,如果按照相同顺序,则可以避免死锁
#加锁时限:给锁加一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。
##这种机制存在一个问题,在Java中不能对synchronized同步块设置超时时间。你需要创建一个自定义锁,或使用Java5中java.util.concurrent包下的工具
#死锁检测:死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。
##还是上述例子,线程A持有锁1,请求锁2,线程B持有锁2,请求锁1。这时可以让线程A去检测线程B是否已经请求了线程A当前锁持有的锁,如果线程B的确是在请求,
##则线程A取消请求,并释放锁1,回退和等待。当然现实可能多条线程交叉,它需要递进地检测。
#推荐书籍:《java高并发程序设计》倾力推荐,书中介绍了关于并包和并包的使用场景
#介绍:Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包。这个包包含有一系列能够让 Java 的并发编程变得更加简单轻松的类。在这个包被添加以前,你需要自己去动手实现自己的相关工具类。
#阻塞队列 BlockingQueue:
#数组阻塞队列 ArrayBlockingQueue:
#延迟队列 DelayQueue:
#链阻塞队列 LinkedBlockingQueue
#同步队列 SynchronousQueue:它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走
#阻塞双端队列 BlockingDeque:
#并发Map ConcurrentMap:ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。
#栅栏 CyclicBarrier:是一种同步机制,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情
#交换机 Exchanger:表示一种两个线程可以进行互相交换对象
#执行器服务 ExecutorService:一个可以实现线程池的接口
#线程池执行者 ThreadPoolExecutor:底层是实现ExecutorService
#定时执行者服务 ScheduledExecutorService:它能够将任务延后执行,或者间隔固定时间多次执行。 任务由一个工作者线程异步执行,而不是由提交任务给 ScheduledExecutorService 的那个线程执行。
# ForkJoinPool 进行分叉和合并:它和 ExecutorService 很相似,除了一点不同。ForkJoinPool 让我们可以很方便地把任务分裂成几个更小的任务,也可以合并任务
#锁 Lock:是一个类似于 synchronized 块的线程同步机制。但是 Lock 比 synchronized 块更加灵活、精细。
#读写锁 ReadWriteLock:读写锁是一种先进的线程锁机制。它能够允许多个线程在同一时间对某特定资源进行读取,但同一时间内只能有一个线程对其进行写入。
#原子性布尔 AtomicBoolean:提供了一个可以用原子方式进行读和写的布尔值
#原子性整型 AtomicInteger
#原子性长整型 AtomicLong
#原子性引用型 AtomicReference
#流:流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象,本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
#io
#分类:根据数据类型分为:字符流和字节流,根据流的方向分为输入流和输出流
#字节流以字节为单位,字符流以字符为单位,一次可读2个字节,字节流可用于处理所有类型数据,而字符流只能处理字符类型的数据,只要是处理纯文本就优先考虑使用字符流,除此之外都使用字节流
#字节输入流InputStream:
#InputStream 是所有的输入字节流的父类,它是一个抽象类。
#ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道
#ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。
#字节输出流OutputStream:
#OutputStream 是所有的输出字节流的父类,它是一个抽象类。
#ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据,
#ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。
#字符输入流Reader:
#Reader 是所有的输入字符流的父类,它是一个抽象类。
#CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
#BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
#FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
#InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。
#字符输出流Writer:
#Writer 是所有的输出字符流的父类,它是一个抽象类。
#CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
#BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
#PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。
#OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。
#nio
#io靠字节字符单项传输,I/O 操作的阻塞管理粒度是以服务于请求的线程为单位的,有可能大量的线程会闲置,处于盲等状态,造成I/O资源利用率不高,影响整个系统的性能。
#而NIO是靠块, 也就相当于一个Buffer, 一块一块的传输, 速度较快。一个NIO流可以同时传输多个块等, 也就是所谓的异步传输
#非阻塞原理:事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
#NIO 工具包提出了基于Buffer(缓冲区)、Channel(通道)、Selector(选择器)的新模式
#buffer内部构造
#一个 buffer 主要由 position,limit,capacity 三个变量来控制读写的过程。
#参数:写模式;读模式
#position:当前写入的单位数据数量;当前读取的单位数据位置。
#limit:代表最多能写多少单位数据和容量是一样的;代表最多能读多少单位数据,和之前写入的单位数据量一致。
#capacity:buffer 容量;buffer 容量
#常见方法:
#flip(): 写模式转换成读模式
#rewind() :将 position 重置为 0 ,一般用于重复读。
#clear() :清空 buffer ,准备再次被写入 (position 变成 0 , limit 变成 capacity) 。
#compact(): 将未读取的数据拷贝到 buffer 的头部位。
#mark() 、 reset():mark 可以标记一个位置, reset 可以重置到该位置。
#常见类:ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、ShortBuffer 。
#channel常见类:FileChannel 、 DatagramChannel(UDP) 、 SocketChannel(TCP客户端) 、 ServerSocketChannel(TCP服务端)
#Selector:异步 IO 的核心类,它能检测一个或多个通道 (channel) 上的事件,并将事件分发出去。使用一个 select 线程就能监听多个通道上的事件,并基于事件驱动触发相应的响应。而不需要为每个 channel 去分配一个线程。
#SelectionKey:包含了事件的状态信息和时间对应的通道的绑定。
#代码示例:
String infile = "C:\\copy.sql";
String outfile = "C:\\copy.txt";
// 获取源文件和目标文件的输入输出流
FileInputStream fin = new FileInputStream(infile);
FileOutputStream fout = new FileOutputStream(outfile);
// 获取输入输出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// clear方法重设缓冲区,使它可以接受读入的数据
buffer.clear();
// 从输入通道中将数据读到缓冲区
int r = fcin.read(buffer);
// read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1
if (r == -1) {
break;
}
// 写模式转换成读模式,
buffer.flip();
// 从输出通道中将数据写入缓冲区
fcout.write(buffer);
#静态代理:编译时增强,修改目标对象的class文件
#AspectJ:它会在编译阶段将Aspect织入Java字节码中, 运行的时候就是经过增强之后的AOP对象。
#动态代理:运行时增强,生成代理对象
#jdk动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
#被代理的对象必须要实现接口
#创建一个实现InvocationHandler的代理类
#使用Proxy.newProxyInstance(classLoader, interfaces, handler)产生代理对象
#cglib动态代理:利用asm(一个短小精悍的字节码操作框架)开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
#必须依赖于CGLib的类库
#可对无实现接口的类进行代理
#Enhancer类为主要类