Java NIO(New IO)是从Java 1.4版本开始引入的 一个新的IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
NIO 与 IO的主要区别:
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
无 | 选择器(Selectors) |
Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。
即:Channel 负责传输, Buffer 负责存储
缓冲区(Buffer):一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
根据数据类型不同(boolean除外),提供了相应类型的缓冲区,管理方式几乎一致,通过allocate()方法获取缓存区:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
四个核心属性:
方法 | 描述 |
---|---|
Buffer clear() | 清空缓冲区并返回对缓冲区的引用 |
Buffer flip() | 将缓冲区的界限设置为当前位置,并将当前位置充值为 0,即读数据模式 |
int capacity() | 返回 Buffer 的 capacity 大小 |
boolean hasRemaining() | 判断缓冲区中是否还有元素 |
int limit() | 返回 Buffer 的界限(limit) 的位置 |
Buffer limit(int n) | 将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象 |
Buffer mark() | 对缓冲区设置标记 |
int position() | 返回缓冲区的当前位置 position |
Buffer position(int n) | 将设置缓冲区的当前位置为 n , 并返回修改后的 Buffer 对象 |
int remaining() | 返回 position 和 limit 之间的元素个数 |
Buffer reset() | 将位置 position 转到以前设置的 mark 所在的位置 |
Buffer rewind() | 将位置设为为 0, 取消设置的 mark |
数据操作:
String s = "hello";
//1.分配一个指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println(byteBuffer.position());//0
System.out.println(byteBuffer.capacity());//1024
System.out.println(byteBuffer.limit());//1024
System.out.println("----------------------------");
//2.存入一个字节数组
byteBuffer.put(s.getBytes());
System.out.println(byteBuffer.position());//5
System.out.println(byteBuffer.capacity());//1024
System.out.println(byteBuffer.limit());//1024
System.out.println("----------------------------");
//3.切换到读数据模式
byteBuffer.flip();
System.out.println(byteBuffer.position());//0
System.out.println(byteBuffer.capacity());//1024
System.out.println(byteBuffer.limit());//5
System.out.println("----------------------------");
//4.读数据
byteBuffer.mark();//标记此时position位置
byte[] b = new byte[byteBuffer.limit()];
byteBuffer.get(b);
System.out.println(new String(b,0,byteBuffer.limit()));//hello
System.out.println(byteBuffer.position());//5
System.out.println(byteBuffer.capacity());//1024
System.out.println(byteBuffer.limit());//5
byteBuffer.reset();//position回到上次标记的位置
System.out.println(byteBuffer.position());//0
System.out.println("----------------------------");
//5.rewind():可重复读数据
byteBuffer.rewind();
System.out.println(byteBuffer.position());//0
System.out.println(byteBuffer.capacity());//1024
System.out.println(byteBuffer.limit());//5
System.out.println("----------------------------");
//6.clear():清空缓冲区,但缓冲区数据还存在,只是数据是被遗忘状态
byteBuffer.clear();
System.out.println(byteBuffer.position());//0
System.out.println(byteBuffer.capacity());//1024
System.out.println(byteBuffer.limit());//1024
直接缓冲区和非直接缓冲区:
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。
直接缓冲区:通过allocateDirect()方法或者FileChannel 的map() 分配缓冲区,将缓冲区建立在操作系统的物理内存中,只支持ByteBuffer。(提高效率,但消耗资源大,不易控制,何时写入何时回收都无法控制)
建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
byteBuffer.isDirect();//判断是不是直接缓冲区
由 java.nio.channels 包定义,用于 IO 源节点与目标节点的连接。在Java NIO中负责缓冲区中数据的传输。Channel本身不负责存储数据,因此需要配合缓冲区进行数据传输。
主要实现类:
获取通道:
@Test
public void test1() throws Exception {
//通道+非直接缓冲区完成文件的复制
FileInputStream fileInputStream = new FileInputStream(new File("1.jpg"));
FileOutputStream fileOutputStream = new FileOutputStream(new File("2.jpg"));
FileChannel inchannel = fileInputStream.getChannel();
FileChannel outchannel = fileOutputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);//非直接缓冲区
while (inchannel.read(buffer) != -1){
buffer.flip();
outchannel.write(buffer);
buffer.clear();
}
outchannel.close();
inchannel.close();
fileOutputStream.close();
fileInputStream.close();
}
@Test
public void test2() throws IOException {
//直接缓冲区完成文件的复制操作(内存映射文件)
FileChannel inchannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outchannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
MappedByteBuffer inmap = inchannel.map(FileChannel.MapMode.READ_ONLY, 0, inchannel.size());//内存映射文件,道理同非直接缓冲区
MappedByteBuffer outmap = outchannel.map(FileChannel.MapMode.READ_WRITE, 0, inchannel.size());
byte[] b = new byte[inmap.limit()];
inmap.get(b);
outmap.put(b);
inchannel.close();
outchannel.close();
}
通道之间的数据传输:
@Test
public void test3() throws Exception {
//通道之间的数据传输,同样也是直接缓冲区的方式
FileChannel inchannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outchannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
inchannel.transferTo(0,inchannel.size(),outchannel);
outchannel.transferFrom(inchannel,0,inchannel.size());
inchannel.close();
outchannel.close();
}
分散读取(Scattering Reads)是将通道中的数据分散到多个缓冲区中。
聚集写入(Gathering Writes)是指将多个缓冲区中的数据聚集到通道中。(按照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel 。)
ByteBuffer[] bufs = new ByteBuffer[]{buf1,buf2,buf3};
channel.read(bufs);
channe2.writer(bufs)
编码:字符串转换为字节数组
解码:字节数组转换为字符串
@Test
public void test3() throws Exception {
Charset c = Charset.forName("GBK");
CharsetEncoder encoder = c.newEncoder();
CharsetDecoder decoder = c.newDecoder();
CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put("彭于晏真帅");
buffer.flip();
ByteBuffer byteBuffer = encoder.encode(buffer);//编码
byteBuffer.flip();
CharBuffer charBuffer = decoder.decode(byteBuffer);//解码
}
阻塞与非阻塞:
使用 NIO 完成网络通信的三大核心:
SocketChannel
ServerSocketChannel
DatagramChannel
Pipe.SinkChannel
Pipe.SourceChannel
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道(客户端)。ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,就像标准IO中 的ServerSocket一样(服务器端)。
SocketChannel操作步骤:
阻塞式IO网络通信:(不使用选择器)
public class TestBlockingNIO {
//没用Selector,阻塞式IO
@Test
public void client() throws IOException{
//获取通道,FileChannel用于访问本地文件通信, SocketChannel用于网络发送通信
SocketChannel sChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
FileChannel inChannel=FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
//发送给服务器端
ByteBuffer buf=ByteBuffer.allocate(1024);
while(inChannel.read(buf)!=-1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
sChannel.shutdownOutput();//关闭发送通道,表明发送完毕
//接收服务端的反馈
int len=0;
while((len=sChannel.read(buf))!=-1){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server() throws IOException{
ServerSocketChannel ssChannel=ServerSocketChannel.open();
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//获取客户端连接的通道
SocketChannel sChannel=ssChannel.accept();
//接收客户端数据
ByteBuffer buf=ByteBuffer.allocate(1024);
while(sChannel.read(buf)!=-1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
//发送反馈给客户端
buf.put("服务端接收数据成功".getBytes());
buf.flip();
sChannel.write(buf);
sChannel.close();
outChannel.close();
ssChannel.close();
}
}
非阻塞式IO网络通信:(使用选择器)
选择器(Selector)是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector 可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心
使用方式:
public class TestNonBlockingNIO {
//客户端
@Test
public void client()throws IOException{
//1.获取通道
SocketChannel sChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.切换非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
//4.发送数据给服务端
Scanner scan=new Scanner(System.in);
while(scan.hasNext()){
String str=scan.next();
buf.put((new Date().toString()+"\n"+str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
//5.关闭通道
sChannel.close();
}
//服务端
@Test
public void server() throws IOException{
//1.获取通道
ServerSocketChannel ssChannel=ServerSocketChannel.open();
//2.切换非阻塞式模式
ssChannel.configureBlocking(false);
//3.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector=Selector.open();
//5.将通道注册到选择器上,并且指定“监听接收事件”
ssChannel.register(selector,SelectionKey.OP_ACCEPT);
//6.轮询式的获取选择器上已经“准备就绪”的事件
while(selector.select()>0){
//7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while(it.hasNext()){
//8.获取准备“就绪”的事件
SelectionKey sk=it.next();
//9.判断具体是什么时间准备就绪
if(sk.isAcceptable()){
//10.若“接收就绪”,获取客户端连接
SocketChannel sChannel=ssChannel.accept();
//11.切换非阻塞模式
sChannel.configureBlocking(false);
//12.将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
}else if(sk.isReadable()){
//13.获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel=(SocketChannel)sk.channel();
//14.读取数据
ByteBuffer buf=ByteBuffer.allocate(1024);
int len=0;
while((len=sChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//15.取消选择键SelectionKey
it.remove();
}
}
}
}
Java NIO中的DatagramChannel是一个能收发UDP包的通道。
DatagramChannel dc=DatagramChannel.open();
public class TestNonBlockNIO2 {
@Test
public void send() throws IOException{
DatagramChannel dc=DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf=ByteBuffer.allocate(1024);
Scanner scan=new Scanner(System.in);
while(scan.hasNext()){
String str=scan.next();
buf.put((new Date().toString()+"\n"+str).getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
buf.clear();
}
dc.close();
}
@Test
public void receive() throws IOException{
DatagramChannel dc=DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(9898));
Selector selector=Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while(selector.select()>0){
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey sk=it.next();
if(sk.isReadable()){
ByteBuffer buf=ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
it.remove();
}
}
}
Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。
数据会被写到sink通道,从source通道读取。
public class TestPipe {
@Test
public void test1()throws IOException{
//1.获取管道
Pipe pipe=Pipe.open();
//2.将缓冲区中的数据写入管道,写入是sink
ByteBuffer buf=ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel=pipe.sink();
buf.put("通过单向管道发送数据".getBytes());
buf.flip();
sinkChannel.write(buf);
//3.读取缓冲区中的数据,读取是source
Pipe.SourceChannel sourceChannel=pipe.source();
buf.flip();
int len=sourceChannel.read(buf);
System.out.println(new String(buf.array(),0,len));
sourceChannel.close();
sinkChannel.close();
}
}