NIO概述:
提升I/O速度
一、缓冲区(Buffer):
包含数组和四个描述缓冲区特征的属性
Buffer:
1、概述:
缓冲区是特定基本类型元素的线性有限序列,基本属性包括内容、容量、限制和位置:
capacity:元素的数量;
limit:第一个不应该读取或写入的元素的索引;
position:下一个尧都区或写入的元素的序列;
2、ByteBuffer:
ByteBuffer是唯一与通道进行交互的缓存器;
allocate:分配一个新的字节缓存区,参数capacity单位为字节;
wrap:将byte数组包装到缓存区中;
filp:让ByteBuffer做好让别人读取字节的准备;
clear:清空缓存区;
rewind:将ByteBuffer的position设置为0;
hasRemaining:判断缓存区是否到达末尾;
put:将给定的字节写入到缓存区的当前位置,然后位置递增;
get:读取此缓存区当前位置的字节,然后位置递增;
常见的Buffer:
Buffer:根类;
ByteBuffer、MappedByteBuffer、CharBuffer、IntBuffer、DoubleBuffer、ShortBuffer、LongBuffer、Float Buffer;
3、Buffer的四个属性:
- 容量(Capacity):容纳数据元素的最大数量,设定后不能修改;
- 上界(Limit):缓冲区第一个不能被读或者写的元素位置,缓存区的上界;
- 位置(Position):缓冲区中下一个要被读或者写的元素位置,位置会自动更新;
- 标记(Mark):备忘位置,初始为’未定义‘
mark<=position<=limit<=capacity
4、Buffer的相关方法:
- remaining():返回缓冲区目前存储的元素个数;
- hasRemaining():缓冲区是否还有元素;
- filp():翻转,设置limit=position,position=0;
- compact():压缩,设置limit=limit-position,position=0;
解码与编码:输入时进行编码,从缓存器输出时进行解码;
二、通道(Channel):
1、概述:
- 双向操作的流,网络数据可以通过Channel读取和写入,因为Channel是全双工的,因此它可以比流更好的映射底层操作系统的api;
- NIO中通过channel封装了对数据源操作,通过channel可以操作数据源,不必关心数据源的具体结构,channel与文件描述符或者socket是一一对应的;
- Channel用于在字节缓冲区和位于通道另一侧的实体之间有效的传输数据;
通过缓冲区来间接的读写数据;
2、常见的Channel:
FileChannel:读取、写入、映射和操作文件的通道;
DatagramChannel:针对面向数据套接字的可选通道;
SocketChannel:针对面向流的连接套接字的可选择通道;
FileChannel:
FileOutputStream、FileInputStream、RandomAccessFile
3、channel相关的方法:
read:将通道的字节序列读入缓冲区中,返回读入字节的长度,达到末尾返回-1;
write:将字节序列从给定的缓冲区写入此通道,返回写入的字节数;
size:返回此通道文件的当前的大小;
transferTo:将字节从此通道的文件传输到给定的可写入字节通道;
transferFrom:将字节从给定的可读取字节通道传输到此通道的文件中;
4、Channel的主要实现有:
FileChannel:文件
DategramChannel:UDP
SocketChannel:TCP
ServerSocketChannel:监听新进来的TCP连接,对于每一个新进来的连接都会创建一个SocketChannel
三、Selector:
1、概述:
- 一个Selector可以监听多个channel,如果channel中有有Selector关心的事,就可以通过SelectionKey获取内容,然后进行操作;
- Selector运行单线程处理多个Channel;
2、FileChannel操作:
-
1 、打开FileChannel:在使用FileChannel之前需要先使用流获取一个FileChannel实例;
RandomAccessFile file = new RandomAccessFile("f:" + File.separator + "workspace" + File.separator+ "sms" + File.separator +"/src/main/java/com/test/u.txt","rw"); FileChannel channel = file.getChannel();
-
2、从FileChannel读取数据:先分配一个Buffer,从FileChannel中读取的数据将被读到Buffer,调用FileChannel.read()方法,该方法将数据从FileChannel读取到Buffer中,read()方法返回的int值表示有多少字节被读到了Buffer中,如果返回-1表示到达了文件末尾;
ByteBuffer buffer = ByteBuffer.allocate(1024); int read = channel.read(buffer);
-
3、向FileChannel写数据:FileChannel.write()是在while循环中调用的,因此无法保证write()方法一次能向FileChannel写入多少字节,因此需要重复调用write()方法,知道Buffer中已经没有尚未写入通道的字节;
ByteBuffer buffer = ByteBuffer.allocate(1024); buffer,clear(); buffer.put("eeeeeeee"); buffer.flip(); while(buffer.hasRemaining()){ channel.write(); }
-
4、关闭Channel:
channel.close();
-
5、position方法:
Long pso = channel.position(); channel.position(pso+123);//设置当前位置,如果位置设置在文件结束之后,然后试图从文件通道中读取数据,读方法将返回-1;
-
6、size()方法:返回关联文件的大小
channel.size();
-
7、truncate()方法:截取文件,将建中将指定长度后面的部分将被删除;
channel.truncate(1024);
-
8、force()方法:将尚未写入磁盘的数据强制写到磁盘上;
-
9、transferTo、transferFrom方法:通道之间的数据传输;
四、Socket通道类:
DatagramChannel、SockertChannel、ServerSocketChannel
1、概述:
- 通道是一个连接IO服务导管并提供服务交互的方法;
- 可以通过configureBlocking()方法来设置socket是阻塞/非阻塞;
2、ServerSocketChannel:
-
基于通道的socket监听器(本身不传数据,而是一个监听器),它增加了通道语义因此能够在非阻塞模式下运行,因为ServerSocketChannel没有bind方法,所以有必要取出对等的socket并使用它来绑定到一个端口以开始监听连接;
-
创建了一个ServerSocketChannel并用对等socket绑定了他,可以在其中一个上调用accept(),如果选择在ServerSocket上调用accept方法,那么他会同其他的ServerSocket表现一样的行为,总是阻塞返回一个Socket对象;
-
ServerSocketChannel的accept()方法会返回SocketChannel类型对象,SocketChannel可以在非阻塞模式下运行,其他Socket的appect()方法会阻塞返回一个Socket对象;
-
ServerSocketChannel以非阻塞模式被调用,当没有传入连接在等待时,
-
ServerSocketChannel.accept()会立即返回null;
- 打开ServerSocketChannel:通过调用ServerSocketChannel.open()方法打开ServerSocketChannel;
- 关闭ServerSocketChannel:通过调用ServerSocketChannel.close()方法关闭ServerSocketChannel;
- 监听新的连接:通过ServerSocketChannel.accept()方法监听新进的连接,它返回一个包含新进来的连接的SocketChannel,因此accept方法会一直阻塞到有新连接到达;
- 阻塞模式:会在SocketChannel sc = ssc.accept();这里阻塞住进程;
- 非阻塞模式:ServerSocketChannel可以设置成非阻塞模式,在非阻塞模式下accept方法会立刻返回,如果没有新进来的连接,返回的将是null
3、SocketChannel:
- SocketChannel是一个连接到TCP网络套接字的通道,操作面向Buffer缓冲区;
- SocketChannel是用来连接Socket套接字;
- SocketChannel主要用途是用来处理网络IO的通道;
- SocketChannel是基于TCP连接输出;
- SocketChannel实现了可选择通道,可以被多路复用的;
4、SocketChannel特征:
-
对于已经存在的socket不能创建SocketChannel;
-
SocketChannel中提供的open接口创建的Channel并没有进行网络级联,需要使用connect接口连接到指定地址;
-
未进行连接的SocketChannel执行IO操作时,抛出NotYetConnectedException;
-
SocketChannel支持两种IO模式:阻塞式和非阻塞式;
-
SockerChannel支持异步关闭,如果SocketChannel在一个线程上read阻塞,另一个线程对该SocketChannel调用shutdownInput,则读阻塞的线程将返回-1表示没有读取任何数据,如果SocketChannel在一个线程上write阻塞,另一个先发成对该SocketChannel调用shuntdownWrite,则写阻塞的线程将抛出AsynchronousCloseException;
-
SocketChannel支持设定参数:
SO_SNDBUF:套接字发送缓冲区大小 SO_RCVBUF:套接字接受缓冲区大小 SO_REUSEADDR:保活连接 O_REUSEADDR:服用地址 SO_LINGER:有数据传输时延缓关闭Channel TCP_NODELAY:禁用Nagle算法
5、SocketChannel的使用:
-
创建SocketChannel:可以使用InetSocketAddress的有参构造和无参构造
SocketChannel.open(new InetSocketAdress("[http://www.baidu.com%22%2C80%29%29%3B//1](http://www.baidu.com"%2C80))%3B//1) Socket.open();//2 socketChannel.connect(new inetSocketAddress("[http://www.baidu.com%22%2C80/](http://www.baidu.com"%2C80/)));
-
连接校验:
socketChannel.isopen:测试SocketChannel是否为ope状态; socketChannel.isConnection:测试SocketChannel是否已经被连接; socketChannel.isConnectionPeding:测试SocketChannel是否正在进行连接; socketChannel.finishConnection:校验套接字连接的SocketChannel是否已经完成连接;
-
读写模式:
SocketChannel支持阻塞和非阻塞模式,通过socketChannel.configureBlocking设置 -
读写:
读写都是面向缓冲区,这个读写方式与FileChannel相同 -
设置和获取参数:
socketChannel.setOption(Standard Socket Options.SO_KEEPALIVE);//相关的参数
五、DatagramChannel:
1、概述:
- 向地址ip端口号发送内容,接受ip端口号发送来的内容;
- 与ServerSocketChannel对应ServerSocket类似,每一个DatagramChannel对象关联一个DatagramSocket对象;
- DatagramChannel模拟包导向的无连接协议,是无连接的,每个数据报都是一个自包含的实体,拥有它自己的目的地址不依赖其他数据报的数据载,与面向流的Channel不同,DatagramChannel可以发送单独的数据报给不同的目的地址;同样也可以接受来自任意的数据包;
2、DatagramChannel的使用:
-
打开DatagramChannel:
DatagramChannel server= DatagramChannel.open(); server.socket().bind(new InetSocketAddress(10086));
-
接收数据:通过receive()接受udp包;
ByteBuffer receiveBuffer=ByteBuffer.allocate(64); receiveBuffer.clear(); SocketAddress receiveAddr=server.receive(receiveBuffer); SocketAddress可以获得发包的信息
-
发送数据:通过send()发送udp包;
DatagramChannel server=DatagreamChannel.open(); ByteBuffer sendBuffer=ByteBuffer.wrap("client send".getBytes()); server.send(sendBuffer,new InetSocketAddress("127.0.0.1",10086));
-
连接:upd不存在真正意义上的连接,这里的连接时向特定服务地址用read和write接受发送数据包;
client.connect(new InetSocketAddress("127.0.0.1",10086)); int readSize=client.send(sendBuffer); server.write(sendBuffer); read和write只有connect后才能使用不然后抛NotYetConnectionException异常; 用read接受时,如果没有接受到包,会抛PortUnreachableException异常;
六、Scatter/Gather:
1、概述:
- NIO支持Scatter/gather用于描述从Channel中读取或写入到Channel的操作;
- 分散(scatter):从Channel中读取是指在读操作的数据写入多个buffer中,Channel将从Channel中读取的数据分散到多个buffer中;
- 聚集(gather):写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,Channel将多个buffer中的数据聚集后发送到Channel;
- scatter/gather经常用于需要将传输的数据分开处理的场合;
2、相关操作:
-
read:首先将buffer插入到数组,然后再将数组作为channel.read()的输入参数,read方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写;
ByteBuffer header=ByteBuffer.allocate(1024); ByteBuffer body =ByteBuffer.allocate(1024); ByteBuffer[] bufferArray={header,body}; channel.read(bufferArray);
-
write:从多个buffer写入到同一个channel,buffers数组是write方法的入参,write方法会按照buffer在数组中的数据,将数据写入到channel,只有position和limit之间的数据才会被写入;
ByteBuffer header=ByteBuffer.allocate(1024); ByteBuffer body=ByteBuffer.allocate(1024); ByteBuffer[] bufferArray={header,body}; channel.write(bufferArray);
Charset:java默认使用Unicode字符集,当读取数据到Java程序时,就会出现乱码问题;
Charset是不可变的,用来处理字节序列和字节序列之间的转换关系,包含了用于创建解码器和编码器的方法;
Encode(编码):字符串 -> 字节数组
Decode(解码):字节数组 -> 字符串
七、Lock接口和AQS:
1、Lock接口概述:
Lock实现提供比使用Synchronized方法和语句可以获得更广泛的锁定操作,允许更灵活的结构化,具有完全不同的属性,并支持多个相关联的对象Condition;
2、Lock相关方法:
Lock():获取锁,如果锁一直阻塞直至获取到锁;
lockInterruptibly():如果当前线程未被中断,则获取锁;如果锁可用,则获取锁,并立即返回,如果在加锁过程中发生了Interrupt中断操作,会抛出InterruptedException异常,并中断掉当前线程的加锁状态;
tryLock():尝试获取锁,仅在调用锁为空闲状态才获取锁,如果锁是可用的返回true,如果锁不可用,该方法会立即返回false;
tryLock(long timeout,TimeUnit unit):在指定时间内尝试性获取锁;
unlock():释放锁,加锁和释放锁是成对出现的,如果成功的话应该对应一个unlock操作,这样可以避免死锁或者资源的浪费;
Condition newCondition():放回绑定到Lock实例的新的Condition实例,可以进行线程间通信;
3、AQS概述:
- 提供了一个为实现依赖于先进先出等待队列的阻塞锁和相关同步器提供一个框架;
AQS是CountdownLatch、ReentrankLock、ThreadPoolExecutor、ReentrankReadWriterLock、Semaphore实现的基础;
4、AQS核心字段:
state
:描述有多少个线程获取锁,0表示锁是空闲状态,大于0表示锁被占用、小于0表示溢出,如果是独占锁,需要通过CAS将其从0变为1,标记加锁成功,如果是共享锁,表示的是并发的线程锁;head
和tail
加上CAS就构成一个FIFO队列,是一个双向链表,每个节点是Node类型通过双向队列来完成同步状态的管理,如果当前线程获取同步状态失败,AQS将会把当前线程以及等待的状态信息构建成一个节点Node并加入到同步队列中,同时会阻塞当前线程,将同步状态释放掉,会将处于队列首节点的线程唤醒,让线程获取同步状态;- 在队列终结点用来保存状态的线程、等待状态、前驱节点、后继节点;
waitStatus
:节点的等待状态,节点的状态;
CANCELLED
= 1:当前线程被取消,节点操作因为超时或者对应的线程被interrupt,节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出;
SIGNAL
= -1:表示当前节点的后继节点包含的线程需要运行,也就是unpark节点的继任节点是BLOCKED状态,因此一个节点一旦被释放或者取消就需要唤醒他的继任节点;
CONDITION
= -2;
PROPAGATE
= -3;
5、ABS同步器原理:
- 获取锁:先判断当前状态是否允许获取锁,是就获取锁,阻塞操作或者获取失败,如果是独占锁就可能阻塞,如果是共享锁就可能失败,如果是阻塞线程,线程就需要进入阻塞队列,当状态位允许获取锁时就修改状态,如果进入队列就从队列中移除;
- 释放锁:修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程;
**条件 **:
- 原子性操作同步器的状态位:使用32位来描述状态位,使用CAS操作来修改状态;
- 阻塞和唤醒线程:
- 一个有序的队列:采用CHL列表来解决有序的队列的问题,这里使用CAS的原因时防止在并发添加尾节点的时候出现线程不安全的问题;
八、Paths、Path、Files:
- Paths:是一个类,通过转换路径字符串返回一个path;
- Path:是一个接口,表示系统相关的文件路径,用于在文件系统中定位文件的对象,可以代替File类;
- Files:是一个工具类,包括对文件、目录进行操作的静态方法;
1、Path接口:
path接口表示的是一个与平台无关的路径,既可以是绝对路径也可以是相对路径,绝对路径包含文件系统根目录到指定具体文件或目录的完整路径,相对路径包含一个文件或目录到其他位置的路径;
-
创建path:
在使用path之前需要创建path对象,可以使用paths工具类的静态方法进行创建: Path path = Paths.get("c:\gggggg.txt"); 根据相对路径创建path: Path projects = Paths.get("c:\gggggg","aaaaa\gg.txt");
-
Path.normalize:标准化路径,会处理路径中的相关路径,通过创建新的Path实现;
Path path = Paths.get("c:/sss/./ttt.txt"); System.out.println("path="+path); path=path.normalize(); System.out.println("path="+path); 输出: path="c:\sss.\ttt.txt path="c:\sss\ttt.txt
2、Files类:
1. Files.exists():检查一个path在文件系统中是否存在,path指向的文件或目录可能存在也可能不存在,需要使用此方法来判断;
2. Files.createDirctory():创建目录,先判断是否已存在目录;
3. Files.copy():从一个路径拷贝文件到另一个路径,只能复制到不存在对应文件的路径,强制覆盖需要增加相应参数;
Path source = Paths.get(classPath,"ceshi.txt");
Path target = Paths.get(classPath,"ceshi_copy.txt");
try{
Files.copy(source,target);//添加第三个参数可以强制覆盖StandardCopyOption.REPLACE_EXISTING
}
4. Files.move():移动文件;
5. Files.delete():删除文件或目录;
6. Files.walkFileTree():遍历文件,参数接收一个Path、FileVisitor,Path对象指向需要遍历的目录,FileVisitor在遍历的时候调用;
preVisitDirectory():在访问任意目录前调用;
postVisitDirectory():在访问任意目录完成后调用;
visitFile():在访问到每个文件时调用;
visitFileFaild():在访问文件失败时调用;
FileVisitResult枚举:
CONTINUE:继续遍历;
TERMINATE:立即终止遍历;
SKIP_SIBLINGS:跳过当前目录下的兄弟文件继续遍历;
SKIP_SUBTREE:跳过当前目录继续遍历,只能在preVisitDirectory使用,在其他方法中与CONTINUE效果相同;
也可用来实现文件检索、递归删除目录;
九、AsynchronousFileChannel(异步读写文件):
1、创建AsynchronousFileChannel:
AsynchronousFileChannel channel = AsynchronousFileChannel.open(filePath,Standard OpenOption.READ);
只能通过open创建实例,需要传入文件路径参数和打开模式;
2、读数据:有两种方式,都会调用read方法;
-
通过future读取数据:第二个参数时读取的其实位置
ByteBuffer buffer = ByteBuffer.allocate(1024); Future operation = asyncFileChannel.read(buffer,0);
-
通过CompletionHandler读取数据:
asyncFileChannel.read(buffer,position,buffer,new CompletionHandler<Integer,ByteBuffer>()){ @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println(result); attachment.flip(); byte[] data = new byte[attachment.limit()]; attachment.get(data); System.out.println(new String(data)); attachment.clear(); } @Override public void failed(Throwable exc, ByteBuffer attachment) { });
3、写数据:和读数据一样
-
基于Future写数据:
AsynchronousFileChannel wrie = AsynchronousFileChannel.open(filePath,StandardOpenOption.WRITE); position = 12; buffer.put("test data".getBytes()); buffer.flip(); Future writeOperation = writeFileChannel.write(buffer, position); buffer.clear(); while (!writeOperation.isDone()){} System.out.println("write done");
-
基于CompletionHandler写数据:
- writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println("bytes written:" + result); } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.out.println("bytes written failed"); } }); }