java NIO 概述:
java NIO 是 java jdk1.4 java.nio.* 包中新引入的io库,目的是提高速度。
旧的io包已由nio重新实现。
nio主要应用于两个场景:文件io,网络io
java NIO 由三个核心部分组成: Channels Buffers Selectors
channel 类似于流 数据可以从channel读到buffer 也可以从buffer 写到channel。
常用的channel实现有: FileChannel(从文件中读写), DatagramChannel (通过UDP读写网络中数据),SocketChannel (通过TCP读写网络中数据),
ServerSocketChannel(监听TCP链接,为每个连接创建socketChannel)
Buffers关键实现有: ByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer
LongBuffer ShortBuffer MappedByteBuffer(表示内存映射文件)
Selector可以使单线程处理多个channel, 向selector注册channel后调用select()
方法,该方法会阻塞直到某个通道有事件就绪
java NiO 与 java IO 的区别:
1.java NIO 面向缓冲,java IO面向流:
IO读取流中的数据不能进行缓存,不能控制流中数据的前后移动。
NIO中读取数据到缓冲区中并可也进行前后移动,但要注意保证数据的正确性
以及避免数据被覆盖。
2. java NIO 有非阻塞模式,java IO流是阻塞的
线程用io流的read write方法读取写入数据时会被阻塞。
NIO从通道(channel)读取数据,若无可用数据则线程不会阻塞,直到数据可用再读取。
NIO向通道写数据,无需等待完全写入,线程可以去做其他操作。一个线程可管理多个
输入输出通道。
3. java NIO 有选择器(selector) java IO 没有
NIO选择器可以注册多个通道,然后用一个线程选择通道,因此单独线程可以管理多个
通道。
channel:
io流的读写是单向的,channel是双向的(channel->buffer或者 buffer->channel),
可以异步读写。
channel可以从多个buffer中读取数据,调用channel.read(bufArray),
传入一个buffer数组,将会按照buffer在数组中的顺序读入channel中。
一个buffer读满后再读另一个.
也可以将数据从多个buffer写入到同一个channel中:channel.write(buffArray)
写入方法类似于读取,也是按buffer数组元素顺序写入,
只写入buffer的position,limit之间的数据.
两个channel之间也可以直接传递数据,但两个channel必须有一个为FileChannel。
调用fileChannel的transferFrom() 和transferTo() 方法:
transferFrom(): 将fromchannel中的数据传输到FileChannel中:
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
toChannel.transferFrom(fromChannel, position, count);
从position处开始向目标文件写入数据,
count是最多传输的字节数,若源通道空间小于count,则传输数量也小于count
另外SocketChannel只会传输已准备好的数据,传输数量也可能小于count
transferTo(): 将filechannel中数据传输至其他channel中:
fromChannel.transferTo(position, count, toChannel);
transferTo方法中同样需要注意SocketChannel的问题
channel的常见类实现如下:
FileChannel:一个连接到文件的通道。可以通过FileChannel读写文件。FileChannel
无法设置为非阻塞模式。需通过InputStream、OutputStream或RandomAccessFile
来获取一个FileChannel实例:
RandomAccessFile file= new RandomAccessFile("data/tst.txt", "rw");
FileChannel inChannel = file.getChannel();
FileInputStream fileInputStream = new FileInputStream("F:\\tst.txt");
jdk1.7之后通过open()方法 传递Path对象来创建:
FileChannel.open(Paths.get("F:\\tst.txt"), StandardOpenOption.WRITE);
调用 int channel.read(buf)从FileChannel中读取数据到buffer,返回值表示读到
的元素数量。读到文件末尾则返回-1
向FileChannel写数据:
while(buf.hasRemaining()) {
channel.write(buf);
} // 需在循环中写入,因为不能保证write()方法一次向FileChannel写入多少字节
channel.close() //关闭FileChannel
filechannel.position(): 获取channel当前位置。
filechannel.position(pos): 设置当前channel位置。
将position设置在文件结束符之后,从channel读取时返回-1,向channel写数据时
文件将撑大并写入数据,导致文件空洞。
filechannel.size(): 返回关联的文件大小
filechannel.truncate(1024): 截取文件前1024个字节
fileChannel.force():强制将channel中剩余的数据写到磁盘上。
Socketchannel:是一个连接到TCP网络套接字的通道。
两种创建方式:
打开一个socketchannel并连接到某台服务器上
SocketChannel.open()
一个新连接到达ServerSocketChannel时,创建一个SocketChannel
socketChannel.connect(new InetSocketAddress("http://xxx.com", 80))
关闭socketchannel: socketChannel.close();
socketChannel.read(buf): 从socketChannel读数据 返回值表述读取的元素数
返回-1表示读到了流的末尾
SocketChannel.write(): 向socketchannel写入数据,同Filechannel一样需要循环写入
设置非阻塞模式:socketChannel.configureBlocking(false);
判断连接是否建立:boolean socketChannel.finishConnect()
ServerSocketChannel:一个可以监听新进来的TCP连接的通道
打开ServerSocketChannel: ServerSocketChannel.open()
关闭:ServerSocketChannel.close()
监听连接: ServerSocketChannel.accept() 返回包含新进
来的连接的 SocketChannel 该方法一直阻塞到有连接进来
可以设置非阻塞模式:该模式下监听方法立刻返回,需检查返回的
SocketChannel是否是null
DatagramChannel:一个能收发UDP包的通道,UDP是无连接的网络协议,因此不能像其它
通道一样读取和写入。只能发送和接收数据包。
打开channel:
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(8888));
//可以在UDP端口8888上接收数据包
channel.receive(buf): 将接收到的数据包复制到指定buffer,若buffer容量不够
多余的数据会丢失
channel.send(buf, new InetSocketAddress("xxx.com", 80)):
用send方法发送buffer中的数据到”xxx.com”服务器的UDP端口80
channel.connect(new InetSocketAddress("xxx.com", 80));
将DatagramChannel“连接”到网络中的特定地址,连接后可以使用
read()和write()方法,类似传统的通道一样。但在数据传送方面没有保证
channel.read(buf); channel.write(but);
AsynchronousFileChannel: 在Java 7中被添加到NIO中。可以实现异步读取
和写入文件数据
打开AsynchronousFileChannel:
AsynchronousFileChannel.open(path, StandardOpenOption.READ);
StandardOpenOption.READ表示对path代表的文件进行读操作
使用future读取数据:
Future future= fileChannelread(buffer, 0);
//从文件0位置处读取数据到buffer中,异步读取使用:futrue.isDone()
来确认是否读完
使用CompletionHandler读取数据:
fileChannel.read(buffer, position, buffer,
new CompletionHandler<Integer, ByteBuffer>(){
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("result = " + 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) {
}
}
CompletionHandler 作为参数,读取完成时调用completed方法,result为
读取的数据量,attachment存储读取的数据。读取失败时 failed()方法会被调用。
使用Future写入数据:
打开channel时open()方法需设定StandardOpenOption.WRITE参数
表示对文件进行写操作
Future<Integer> future = fileChannel.write(buffer, position);
future .isDone()
将buffer中的数据写入文件,需调用isDone()判断是否写入完毕。
目标文件需提前创建否则会抛异常
使用CompletionHandler写入数据:
fileChannel.write(buffer, position, buffer,
new CompletionHandler<Integer, ByteBuffer>() {。。。}
类似于CompletionHandler读取数据,需重写completed和falied方法
Buffer
buffer 本质上是一块包装过的内存,用于和nio channel进行数据读写交互。
读写步骤:
inchannel.read(buf);
buf.flip(); //切换为读模式
outchannel.write(buf);
buf.clear(); //切换到写模式,清空缓冲区 buf.compact() 清除读过的数据,
未读数据放置到buf起始区,新写入的数据放在未读数据后面。
buffer.capacity : 表示buf的大小。capacity不能改变,因为buffer底层实现是数组
buffer.position : 表示buf当前位置,写入数据后position向前移动,范围:0~capaci-1
切换为读模式时position置零。
buffer.limit : 写模式下等于capacity,读模式下表示最多能读到的数据等于写模式下的position
注:切换到读模式本质上是改变了position(设为上次开始写入时的起始位置)和
limit(上次写模式的position)
读完后需要clear()缓冲区,将position变为开始读取时的position位置,
并且limit变为 capacity-position的值
创建buffer:ByteBuffer.allocate();
写入数据到buffer 的两种方法:channel.read(buf), buf.put();
从buffer 中读取数据两种方法:channel.write(buf), buf.get();
buffer.rewind() : position设为0,limit不变;
buffer.mark() : 标记buf中特定的position
buffer.reset() : 回复到之前设定的position
buffer.equals() : 若两个buf 中数据类型相同,各个类型元素的数量和值都相同则表示两个buf相等
buffer.compareTo() : 比较两个buf中第一个不同的元素,若数据都相同则比较元素个数
Selector
selector 可以检测到多个channel,并收集channel的读写状态。使得单线程可以管理多
个channel。使用Selector时 channel必须处在非阻塞模式。FileChannel不能切换到
非阻塞模式
创建Selector:Selector.open();
注册channel: channel.register(selector, Selectionkey.OP_READ);
register方法返回SelectionKey对象包含 监听事件集合(interest集合),ready集合,
Channel,Selector
Selector 监听channel事件:Selectionkey.OP_READ(读就绪),
Selectionkey.OP_WRITE(写就绪),
SelectionKey.OP_ACCEPT(接收就绪)
SelectionKey.OP_CONNECT(链接就绪)
用 ‘|’操作符来监听多个事件 SelectionKey.OP_READ | SelectionKey.OP_WRITE
interest集合包含Selector感兴趣的集合,判断interest集合是否包含某事件:
boolean isAccept = (interestSet & SelectionKey.OP_ACCEPT)
== SelectionKey.OP_ACCEPT;
ready集合包含channel已就绪的事件,访问ready集合: selectionKey.readyOps();
检测channel中某事件是否就绪:
selectionKey.isAcceptable() isConnectable() ....
从SelectionKey访问Channel和Selector:
selectionKey.channel(); 获取channel后需强制转换为对应的channel实现类
selectionKey.selector();
在SelectionKey上附加其他对象:selectionKey.attach(obj);
Object obj2= selectionKey.attachment();
Selector选择channel过程:
首先确定感兴趣的事件是否有channel就绪: 调用 int select()方法,返回就绪
channel数的int值,select() 会阻塞到至少有一个channel就绪。
select(long timeout):该方法设置最长阻塞时间。selectNow():不会阻塞
没有channel就绪则返回0
然后 调用Set selectedKeys = selector.selectedKeys() 方法,selectedKeys
中包含就绪的channel对应的SelectionKey对象,可以遍历selectedKeys来
访问:
Iterator keyIterator = selectedKeys.iterator();
SelectionKey key = keyIterator.next();
遍历完成后需将遍历对象移除:keyIterator.remove();
Selector.wakeup():用另外一个线程调用该方法,使得原来因为调用select()
而阻塞的线程醒来。
Selector.close(): 该方法关闭selector 并使得注册的SelectionKey 无效,注册的
channel不会关闭。
Pipe(管道)
Pipe是2个线程之间的单向数据连接,pipe有两个通道:sourceChannel和sinkchannel
数据从sinkchannel写入,从source通道读出
创建pipe:Pipe.open();
向pipe写数据:先访问sinkChannel:sinkChannel = pipe.sink();
然后 sinkChannel.write(buffer) 将buffer中数据写入sinkChannel
从pipe读数据:首先访问sourceChannel: sourceChannel = pipe.source();
然后 int bytesRead= sourceChannel.read(buf); 返回ing值为读入
buffer的元素数量
Path
Path接口在Java7 中被添加到Java NIO,是Java NIO2 的一部分
一个path代表一个文件系统路径,包括绝对路径和相对路径
nio path 类似于java.io.File类。但有几个不同点,在很多情况下可以使用可以使用
Path 接口替换 file 类
创建绝对路径:
Path path = Paths.get("d:\\data\\tst.txt") //注意路径‘\’需要转义
创建相对路径:Paths.get(basePath, relativePath)
创建指向文件夹路径的实例:
Path ideaprojs= Paths.get("d:\\data", "ideaprojs");
创建指向文件路径的实例:
Path file = Paths.get("d:\\data", "ideaprojs\\proj1\\txt.txt");
"." 代表当前路径:
currentDir = Paths.get(".");
currentDir.toAbsolutePath() //获取当前绝对路径
".."代表上一级目录:
Paths.get(".."); //获取当前目录的父目录
可以用 Path.normalize()方法来解析路径中的. 和 ..