NIO 是什么
java.nio全称java non-blocking(非阻塞) IO(实际上是 new io),是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
NIO与IO的区别
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞(Non Blocking IO) |
无 | 选择器(Selectors) |
NIO系统的核心是:通道(Channel)和缓冲区(Buffer)
缓冲区(Buffer)
位于 java.nio 包,所有缓冲区都是 Buffer 抽象类的子类,使用数组对数据进行缓冲。
除了 boolean 类型,Buffer 对每种基本数据类型都有针对的实现类:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
创建缓冲区通过 xxxBuffer.allocate(int capacity)方法
ByteBuffer buf1 = ByteBuffer.allocate(512);LongBuffer buf2 = LongBuffer.allocate(1024);……
缓冲区的属性
容量(capacity):表示缓冲区存储数据的最大容量,不能为负数,创建后不可修改。
限制:第一个不可以读取或写入的数据的索引,即位于 limit 后的数据不能读写。不能为负数,不能大于容量。
位置(position):下一个要读取或写入的数据的索引,位置不能为负数,不能大于 limit
标记(mark):标记是一个索引,通过 Buffer 中的 mark() 方法指 Buffer 中一个特定的 position,之后可以通过 reset() 方法回到这个 postion。
Buffer 的常用方法
方法名称 | 说明 |
---|---|
Buffer clear() | 清空缓冲区并返回对缓冲区的引用 |
Buffer flip() | 将缓冲区的 limit 设置为当前位置,并将当前位置重置为0 |
int capacity() | 返回 Buffer 的容量大小 |
boolean hasRemaining() | 判断缓冲区是否还有元素 |
int 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 |
Buffer 所有子类提供了两个操作的数据的方法:get() 方法和 put() 方法
缓冲区存取数据操作
package testnio;import java.nio.ByteBuffer;public class TestBuffer1 { public static void main(String[] args) { testuse(); } public static void testuse() { //1.分配一个指定大小的缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); System.out.println("---------------allocate()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //2.利用 put() 存入数据到缓冲区中 String str="hello"; //将字符串转为 byte 数组存入缓冲区 buf.put(str.getBytes()); System.out.println("---------------put()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //3.切换读取数据模式 buf.flip(); System.out.println("---------------flip()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //4.利用get() 读取缓冲区中的数据 byte[] data=new byte[buf.limit()]; System.out.println("---------------get()----------------"); buf.get(data); System.out.println(new String(data,0,data.length)); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //5.rewind() 重复读 buf.rewind(); System.out.println("---------------rewind()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //6.clear() 清空缓冲区,但缓冲区中的数据依然存在 buf.clear(); System.out.println("---------------clear()----------------"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); System.out.println((char)buf.get()); }}
使用 mark()方法标记
package testnio;import java.nio.ByteBuffer;public class TestBuffer2 { public static void main(String[] args) { testmark(); } public static void testmark() { String str="jikedaquan.com"; //创建缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); //存入数据 buf.put(str.getBytes()); //切换模式 buf.flip(); //临时数组用于接收缓冲区获取的数据,长度与缓冲区 limit 一值 byte[] data=new byte[buf.limit()]; //获取缓冲区的数据从0开始获取4个,存入 data 数组中 buf.get(data, 0, 4); //将数组转为字符串打印 System.out.println(new String(data,0,4)); //打印 position System.out.println(buf.position()); //标记 buf.mark(); System.out.println("---------------再次获取----------------"); //从索引4开始,获取6个字节(余下数据) buf.get(data, 4, 6); System.out.println(new String(data,4,6)); System.out.println(buf.position()); //恢复到标记位置 buf.reset(); System.out.println("---------------reset()----------------"); System.out.println(buf.position()); //判断缓冲区是是有还有剩余数据 if (buf.hasRemaining()) { //获取缓冲区中可以操作的数量 System.out.println("可操作数量:"+buf.remaining()); } }}
mark <= position <= limit <= capacity
虽然使用了缓冲区提高了一定的IO速度,但这样的效率仍然不是最高的。非直接缓冲区在与物理磁盘操作中需要经过内核地址空间copy操作,直接缓冲区不经过copy操作,直接操作物理内存映射文件,
使用直接缓冲区将大大提高效率。直接缓冲区进行分配和取消分配所需成本工厂高于非直接缓冲区,一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接缓冲区可以通过调用此类的 allocateDirect()工厂方法创建
创建直接缓冲区
package testnio;import java.nio.ByteBuffer;public class TestBuffer3 { public static void main(String[] args) { testAllocateDirect(); } public static void testAllocateDirect() { //创建直接缓冲区 ByteBuffer buf=ByteBuffer.allocateDirect(1024); //是否是直接缓冲区 System.out.println(buf.isDirect()); }}
通道(Channel)
缓冲区仅是运载数据的容器,需要对数据读写还需要有一条通道,这两者是密不可分的。
Channel 接口的主要实现类:
- FileChannel:用于读取、写入、映射和操作文件的通道
- DatagramChannel:通过 UDP 读写网络中的数据通道
- ScoketChannel:通过 TCP 读写网络中的数据
- ServerScoketChannel:可以监听新进来的 TCP 链接,对每一个新进来的连接都会创建一个 SocketChannel
如何获取通道?
1、通过支持通道的对象调用 getChannel() 方法
支持通道的类:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramScoket
- Socket
- ServerScoket
使用通道和缓冲区实现文件读和写
package testnio;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class TestChannel { public static void main(String[] args) { FileInputStream fis=null; FileOutputStream fos=null; FileChannel inChannel=null; FileChannel outChannel=null; try { //创建输入流 fis=new FileInputStream("F:/1.jpg"); //创建输出流 fos=new FileOutputStream("F:/2.jpg"); //获取通道 inChannel=fis.getChannel(); outChannel=fos.getChannel(); //分配指定大小的缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); //将通道中的数据存入缓存区 while(inChannel.read(buf)!=-1) { //切换读取数据的模式 buf.flip(); //将读入的缓冲区存入写数据的管道 outChannel.write(buf); //清空缓存区(清空才能再次读入) buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { if (fis!=null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos!=null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(inChannel!=null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outChannel!=null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
2、通过通道类的静态方法 open()
package testnio;import java.io.IOException;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import java.nio.channels.FileChannel.MapMode;import java.nio.file.Paths;import java.nio.file.StandardOpenOption;public class TestOpenAndMapped { public static void main(String[] args) { FileChannel inChannel=null; FileChannel outChannel=null; try { //通过open创建通道 inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //内存映射文件 直接缓冲区 MappedByteBuffer inMappedBuf=inChannel.map(MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMappedBuf=outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); //直接对缓冲区进行数据的读写操作 byte[] data=new byte[inMappedBuf.limit()]; inMappedBuf.get(data);//读 outMappedBuf.put(data);//写 } catch (IOException e) { e.printStackTrace(); }finally { if (inChannel!=null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outChannel!=null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
JDK 1.7 新增的方法 open(),参数 path 通常代表一个依赖系统的文件路径,通过Paths.get()获取。
参数 StandardOpenOption 是一个枚举类型,常用值如下:
- READ :打开读访问
- WRITE:打开写访问
- APPEND:向后追加
- CREATE:创建新文件,存在则覆盖
- CREATE_NEW:创建新文件,存在则报错
MappedByteBuffer:直接字节缓冲区,其内容是文件的内存映射区域。
通道数据传输
将数据从源通道传输到其他 Channel 中,transferTo() 和 transferFrom()
package testnio;import java.io.IOException;import java.nio.channels.FileChannel;import java.nio.file.Paths;import java.nio.file.StandardOpenOption;public class TestChannelTransfer { public static void main(String[] args) { FileChannel inChannel=null; FileChannel outChannel=null; try { //通过open创建管道 inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //将inChannel中所有数据发送到outChannel //inChannel.transferTo(0, inChannel.size(), outChannel); outChannel.transferFrom(inChannel, 0, inChannel.size()); } catch (IOException e) { e.printStackTrace(); }finally { if (inChannel!=null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outChannel!=null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
Channel 负责传输,Buffer 负责存储