nio简介
Java NIO(New IO)是从Java 1.4版本开始引入的 一个新的IO API,可以替代标准的Java IO API。 NIO与原来的IO有同样的作用和目的,但是使用 的方式完全不同,NIO支持面向缓冲区的、基于 通道的IO操 作。NIO将以更加高效的方式进行文件的读写操作。javaNIO与IO的区别: 传统方式的IO: 通道和缓冲区 javaNIO系统的核心在于:
通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。 Channel 负责传输, Buffer 负责存储。
缓冲区: 一个用于特定基本数据类型的容器。由java.nio包定义,所有缓冲区都是buffer抽象类的子类
java NIO中的buffer主要用于与NIO通道进行交互,数据是同通道读入缓冲区,从缓冲区写入通道中去。
Buffer的重要概念:
容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创 建后不能更改。 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据 不可读写。缓冲区的限制不能为负,并且不能大于其容量。 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position。标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity
缓冲区的基本属性 Buffer的常用方法 缓冲区的数据操作 Buffer 所有子类提供了两个用于数据操作的方法:get() 与 put() 方法
/** * 缓冲区 在java NIO中负责数据的存取。 缓冲区就是数组,用于存储不同数据类型的数据 * 根据数据类型不同(Boolean除外),提供相应的缓冲区 * ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer * * 上述缓冲区的管理方式几乎一致,allocate() * 缓冲区存取数据的两个核心方法 put():放入数据到缓冲区 get():获取缓冲区的数据 * 缓冲区的四个核心属性 * capacity 容量 一旦声明,不能改变。 * limit 界限:表示缓冲区可以操作数据的大小 limit后面的数据不能进行读写 * position 位置,表示缓冲区中正在操作数据的位置 * position <=limit<=capacity * * 直接缓冲区 非直接缓冲区 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm的内存中 * 缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率 */ public class testBuffer { @Test public void Test2(){ ByteBuffer buf = ByteBuffer.allocate(1024); String str="abcde"; buf.put(str.getBytes()); buf.flip(); byte[] dst = new byte[buf.limit()]; buf.get(dst,0,2); System.out.println(new String(dst,0,2)); System.out.println(buf.position()); //mark() 标记一下 buf.mark(); buf.get(dst,2,2); System.out.println(new String(dst,2,2)); System.out.println(buf.position()); buf.reset();//回到mark的位置 System.out.println(buf.position()); if(buf.hasRemaining()){ //剩余的可以操作的数据个数 刚才position回到了2,所以还剩下3个 System.out.println(buf.remaining()); } } @Test public void test1(){ ByteBuffer buf = ByteBuffer.allocate(1024); String str="abcde"; System.out.println("`````````````````allocate"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //利用put()存入数据到缓冲区 buf.put(str.getBytes()); System.out.println("`````````````````put"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //切换为读取数据模式 buf.flip(); System.out.println("````````````````flip"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //利用get方法读取数据,读取缓冲区的数据 byte[] dst=new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst,0,dst.length)); System.out.println("``````````````get"); System.out.println(buf.position());//获取的时候position的位置往后移动 System.out.println(buf.capacity()); System.out.println(buf.limit()); //rewind():可重复读数据 buf.rewind(); System.out.println("``````````````rewind"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //清空缓冲区 clear方法 回到最初状态 但是缓冲区的数据还在,只是处于被"遗忘状态" System.out.println("`````````````clear"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); } }/** * 缓冲区 在java NIO中负责数据的存取。 缓冲区就是数组,用于存储不同数据类型的数据 * * 根据数据类型不同(Boolean除外),提供相应的缓冲区 * ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer * * 上述缓冲区的管理方式几乎一致,allocate() * 缓冲区存取数据的两个核心方法 put():放入数据到缓冲区 get():获取缓冲区的数据 * 缓冲区的四个核心属性 * capacity 容量 一旦声明,不能改变。 * limit 界限:表示缓冲区可以操作数据的大小 limit后面的数据不能进行读写 * position 位置,表示缓冲区中正在操作数据的位置 * position <=limit<=capacity * * 直接缓冲区 非直接缓冲区 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm的内存中 * 缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率 * * */ public class testBuffer { @Test public void Test2(){ ByteBuffer buf = ByteBuffer.allocate(1024); String str="abcde"; buf.put(str.getBytes()); buf.flip(); byte[] dst = new byte[buf.limit()]; buf.get(dst,0,2); System.out.println(new String(dst,0,2)); System.out.println(buf.position()); //mark() 标记一下 buf.mark(); buf.get(dst,2,2); System.out.println(new String(dst,2,2)); System.out.println(buf.position()); buf.reset();//回到mark的位置 System.out.println(buf.position()); if(buf.hasRemaining()){ //剩余的可以操作的数据个数 刚才position回到了2,所以还剩下3个 System.out.println(buf.remaining()); } } @Test public void test1(){ ByteBuffer buf = ByteBuffer.allocate(1024); String str="abcde"; System.out.println("`````````````````allocate"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //利用put()存入数据到缓冲区 buf.put(str.getBytes()); System.out.println("`````````````````put"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //切换为读取数据模式 buf.flip(); System.out.println("````````````````flip"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //利用get方法读取数据,读取缓冲区的数据 byte[] dst=new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst,0,dst.length)); System.out.println("``````````````get"); System.out.println(buf.position());//获取的时候position的位置往后移动 System.out.println(buf.capacity()); System.out.println(buf.limit()); //rewind():可重复读数据 buf.rewind(); System.out.println("``````````````rewind"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); //清空缓冲区 clear方法 回到最初状态 但是缓冲区的数据还在,只是处于被"遗忘状态" System.out.println("`````````````clear"); System.out.println(buf.position()); System.out.println(buf.capacity()); System.out.println(buf.limit()); } @Test public void Test3(){ //分配直接缓冲区 ByteBuffer buf = ByteBuffer.allocateDirect(1024); System.out.println(buf.isDirect()); } } /** * 通道:用于源节点与目标节点的连接,在java nio负责缓冲区的数据的传输。channel本身是不存储数据的,因此需要配合缓冲区进行传输。 * 通道的一些主要实现类 java.nio,channel接口 * fileChannel socketChannel serverSocketChannel 网络io DatagramChannel UDP * * 1.如何获取通道 * java针对支持通道的类提供了getChannel()方法 * 本地IO * FileInputStream/FileOutPutStream/RandomAccessStream * * 网络IO * socket * ServerAccessFile * DatagramSocket * * 获取通道的方法 * 2.在JDK1.7中的NIO.2针对各个通道提供了静态方法 open() * *3.在JDK1.7中的NIO.2的Files工具类的newByteChannel() * */ public class testChannel { //利用通道完成文件的复制 @Test public void test1() { FileInputStream fis= null; FileOutputStream fos= null; FileChannel inChannel = null; FileChannel outChannel = null; try { fis = new FileInputStream("src\\1.jpg"); fos = new FileOutputStream("src\\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 { try { if(outChannel!=null) outChannel.close(); if(inChannel!=null) inChannel.close(); if(fos!=null) fos.close(); if(fis!=null) fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test //使用内存映射文件 完成复制 public void test2() throws Exception{ FileChannel inChannel = FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);//读的模式 FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW); //内存映射文件 MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); //直接对缓冲区的数据进行读写操作 byte[] dst = new byte[inMappedBuf.limit()]; inMappedBuf.get(dst); outMappedBuf.put(dst); inChannel.close(); outChannel.close(); } //四、通道之间的数据传输 transferFrom() transformTo() @Test public void test3() throws Exception{ FileChannel inChannel = FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);//读的模式 FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW); // inChannel.transferTo(0,inChannel.size(),outChannel); outChannel.transferFrom(inChannel,0,inChannel.size()); inChannel.close(); outChannel.close(); } } //五、分散与聚集 // 分散读取:将通道中的数据读取的数据分散到多个通道buffer中 //聚集写入:将多个buffer缓冲区聚集到channel @Test public void test4() throws Exception{ RandomAccessFile file=new RandomAccessFile("src\\1.txt","rw");//设置为读写模式 //1、获取通道 FileChannel channel = file.getChannel(); //2.获取指定大小的缓冲区 ByteBuffer btf1 = ByteBuffer.allocate(100);//缓冲区1 ByteBuffer btf2 = ByteBuffer.allocate(200);//缓冲区2 //3.分散读取 ByteBuffer[] buf={btf1,btf2}; channel.read(buf); for (ByteBuffer temp:buf){ temp.flip(); } System.out.println(new String(buf[0].array(),0,buf[0].limit()));//String方法的三个参数 数组 起始坐标 读取的长度 System.out.println(new String(buf[1].array(),0,buf[1].limit()));//String方法的三个参数 数组 起始坐标 读取的长度 //聚集写入 写入到2.txt RandomAccessFile file2=new RandomAccessFile("src\\3.txt","rw"); FileChannel channel1 = file2.getChannel(); channel1.write(buf); channel1.close(); } //字符集 CharSet //编码:字符串--->字节数组 //解码:字节数组--->字符集 @Test public void test5(){ SortedMap<String, Charset> map = Charset.availableCharsets(); Set<Map.Entry<String, Charset>> set = map.entrySet(); for (Map.Entry<String,Charset> entry:set){ System.out.println(entry.getKey()+"="+entry.getValue()); } } //字符集 @Test public void test6() throws Exception{ Charset gbk = Charset.forName("GBK"); //获取编码器 CharsetEncoder encoder = gbk.newEncoder(); //获取解码器 CharsetDecoder decoder = gbk.newDecoder(); CharBuffer buf = CharBuffer.allocate(1024); buf.put("xxx"); buf.flip(); //编码 ByteBuffer encode1 = encoder.encode(buf); for (int i = 0; i < encode1.limit(); i++) { System.out.println(encode1.get()); } //对编码的内容进行解码 encode1.flip(); CharBuffer decode = decoder.decode(encode1); System.out.println(decode.toString()); System.out.println("-----------");//换utf-8进行解码 Charset charset = Charset.forName("utf-8"); encode1.flip(); CharBuffer decode1 = charset.decode(encode1); System.out.println(decode1.toString()); }
NIO的非阻塞网络通信 阻塞与非阻塞 缺点:即使加入多线程,让其去访问服务器,但是CPU的利用率还是不够高。 NIO的形式 非阻塞式 阻塞式示例代码
/** * 使用nio完成完成网络通信的三个核心 * 1.通道:负责连接 * * --java.nio.channels.channel接口 * --SelectableChannel * --SocketChannel TCP * --serverSocketChannel TCP * --DatagramChannel UDP * * --Pipe.SinkChannel * --Pipe.SourceChannel * * 2.缓冲区:负责数据的存取 * 3.选择器:是selectableChannel的多路复用器。用于监控selectableChannel的IO状况 */ //阻塞示例 public class TestBlockNIO { //客户端 @Test public void client() throws Exception{ //1.获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));//服务端通道 FileChannel inChannel=FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ); //2.分配指定大小的缓冲区域 ByteBuffer buf = ByteBuffer.allocate(1024); //3.读取本地文件,并发送到服务器 while(inChannel.read(buf)!=-1){ buf.flip(); sChannel.write(buf); buf.clear(); } //关闭通道 inChannel.close(); sChannel.close(); } //服务端 @Test public void server()throws Exception{ //1、获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open();//ssChannel serverSocketChannel FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW); //2.绑定连接 ssChannel.bind(new InetSocketAddress(9898)); //3.客户端连接的通道 SocketChannel sChannel = ssChannel.accept(); //4.接受客户端的数据,并保存到本地 //缓冲区 ByteBuffer dst = ByteBuffer.allocate(1024); while (sChannel.read(dst)!=-1){ dst.flip(); outChannel.write(dst); dst.clear();//必须放在while循环的内部,为什么呢?我想应该是终止while,也就是读取完数据跳出循环的条件 } outChannel.close(); sChannel.close(); ssChannel.close(); } }思路:如果出现错误,可以使用try//catch方法看看哪里出现了问题 注意:先启动服务器,之后再其中客户端。如果先启动服务器,那和谁连呢吗,所以,需要先让服务器处于就绪状态。
非阻塞示例代码
public class testBlockNIO { //客户端 @Test public void client() throws Exception{ SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); FileChannel file=FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ); ByteBuffer buf = ByteBuffer.allocate(1024); while(file.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(); } file.close(); sChannel.close(); } //服务端 @Test public void server()throws Exception{ ServerSocketChannel ssChannel = ServerSocketChannel.open(); ssChannel.bind(new InetSocketAddress(9898)); FileChannel outChannel = FileChannel.open(Paths.get("src\\2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW); 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(); } }非阻塞式NIO
public class testNotBlockingNIO { //客户端 @Test public void client()throws Exception{ //1.获取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898)); //2.切换为非阻塞模式 sChannel.configureBlocking(false); //3.分配指定大小的缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); //4.发送数据给服务端 buf.put(new Date().toString().getBytes()); buf.flip(); buf.clear(); //关闭通道 sChannel.close(); } //服务端 @Test public void server() throws Exception{ //获取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); ssChannel.configureBlocking(false);//切换为非阻塞模式 FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); //绑定连接 ssChannel.bind(new InetSocketAddress(9898)); //获取选择器 Selector selector = Selector.open(); //将通道注册到选择器上,指定“监听接受事件” SelectionKey register = ssChannel.register(selector, SelectionKey.OP_ACCEPT); //轮询的获取选择器上已经准备就绪的事件 while(selector.select()>0){ //获取当前选择器中所有注册的选择键(已就绪的监听事件) Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); //迭代器迭代 while (iterator.hasNext()){ //获取准备就绪的事件 SelectionKey sk = iterator.next(); //判断是什么事件准备就绪 if(sk.isAcceptable()){ //若接受就绪,获取客户端连接 SocketChannel sChannel = ssChannel.accept(); //切换非阻塞模式 sChannel.configureBlocking(false); //将通道注册到选择器上 sChannel.register(selector,SelectionKey.OP_READ); }else if(sk.isReadable()){ //获取当前选择器上 读就绪 状态的通道 SocketChannel sChannel = (SocketChannel) sk.channel(); //读取数据 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(); } } //取消SelectionKey iterator.remove(); } } } } Java NIO中的DatagramChannel是一个能收发UDP包的通道。 操作步骤: 打开DatagramChannel---------------------------接受/发送数据Datagram示例代码:
public class testNonblockingNIO { @Test public void send() throws IOException { DatagramChannel dc = DatagramChannel.open(); dc.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner scanner = new Scanner(System.in); while(scanner.hasNext()){ String str = scanner.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(); } } }idea出现控制台无法输入的问题:
help---edit custom vm options...粘贴下面这句话就可以解决。
-Deditable.java.test.console=true管道:Java NIO 管道是2个线程之间的单向数据连接。 Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。 示例代码:
public class TestPipe { @Test public void test1()throws Exception{ //1.获取管道 Pipe pipe = Pipe.open(); //2.将缓冲区的数据写入管道 ByteBuffer buf = ByteBuffer.allocate(1024); //向管道写输入 Pipe.SinkChannel sinkChannel = pipe.sink(); buf.put("通过单向管道发送数据".getBytes()); buf.flip(); //向管道中写入数据 sinkChannel.write(buf); //读取缓冲区的数据,访问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(); } }