java NIO入门二——buffer与本地channel weir 2018-03-28 23:18:32.0 java nio 1624 Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO 设备(例如:文件、套接字)的连接。若需要使用NIO 系统,需要获取用于连接IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。 简而言之,Channel 负责传输,Buffer 负责存储 Buffer 中的重要概念: 容量(capacity) :表示Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。 限制(limit):第一个不应该读取或写入的数据的索引,即位于limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。 位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制 标记(mark)与重置(reset):标记是一个索引,通过Buffer 中的mark() 方法指定Buffer 中一个特定的position,之后可以通过调用reset() 方法恢复到这个position. 标记、位置、限制、容量遵守以下不变式:0<=mark<=position<=limit<=capacity 方法 描述 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 下面看例子就行,一目了然: package weir.nio.buffer.test; import java.nio.ByteBuffer; import org.junit.Test; public class BufferTest { @Test public void test1() { String ss = "abc123"; //分配一个指定大小的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(30); System.out.println("----------allocate(30)之后------------"); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); //----------allocate(30)之后------------ //0 //30 //30 //存入数据 buffer.put(ss.getBytes()); System.out.println("----------put()之后------------"); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); //----------put()之后------------ //6 //30 //30 //切换到读取数据模式 buffer.flip(); System.out.println("----------flip()之后------------"); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); //----------flip()之后------------ //0 //6 //30 //读取数据 byte[] bs = new byte[buffer.limit()]; buffer.get(bs); System.out.println(new String(bs)); System.out.println("----------get()之后------------"); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); //----------get()之后------------ //6 //6 //30 //可重复读 buffer.rewind(); System.out.println("----------rewind()之后------------"); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); //----------rewind()之后------------ //0 //6 //30 //清空,但是缓冲区数据还在,只是出于“被遗忘”状态 buffer.clear(); System.out.println("----------clear()之后------------"); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); //----------clear()之后------------ //0 //30 //30 System.out.println((char)buffer.get()); //a System.out.println("--------------------------------"); String sa = "qwx3d3f4f"; ByteBuffer buffer2 = ByteBuffer.allocate(30); buffer2.put(sa.getBytes()); buffer2.flip(); byte[] bs2 = new byte[buffer2.limit()]; buffer2.get(bs2, 0, 3); System.out.println(new String(bs2,0,3)); System.out.println(buffer2.position()); //abc //3 //将此缓冲区的标记设置在其位置,这里就是3这个位置 buffer2.mark(); buffer2.get(bs2, 3, 5); System.out.println("---------------mark()之后-----------------"); System.out.println(new String(bs2,3,5)); System.out.println(buffer2.position()); //---------------mark()之后----------------- //3d3f4 //8 //恢复到mark的位置 buffer2.reset(); System.out.println(buffer2.position()); //3 System.out.println("--------------------------------------"); //判断缓冲区中是否还有剩余数据 if (buffer2.hasRemaining()) { //获取可操作的数量 System.out.println(buffer2.remaining()); //6 } } } 大家一运行就全明白了! 利用通道操作本地文件: package weir.nio.buffer.test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; 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; import org.junit.Test; /** * 通道(channel):负责缓冲区中数据的传输,本身不储存数据 * * @author weir 2018年3月28日 下午10:53:14 * */ public class ChannelTest { // 利用通道复制本地文件(非直接缓冲区) @Test public void t1() { // 本地IO FileInputStream fis = null; FileOutputStream fos = null; // 获取文件通道 FileChannel inChannel = null; FileChannel outChannel = null; try { fis = new FileInputStream("d:/1.jpg"); fos = new FileOutputStream("d:/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 (outChannel != null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } // 使用直接缓冲区完成文件的复制(内存映射文件) @Test public void test2() throws IOException { FileChannel inChannel = FileChannel.open(Paths.get("d:/1.jpg"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("d:/2.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[] dst = new byte[inMappedBuf.limit()]; inMappedBuf.get(dst); outMappedBuf.put(dst); inChannel.close(); outChannel.close(); } // 通道之间的数据传输(直接缓冲区) @Test public void test3() throws IOException { FileChannel inChannel = FileChannel.open(Paths.get("d:/1.jpg"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("d:/2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // inChannel.transferTo(0, inChannel.size(), outChannel); outChannel.transferFrom(inChannel, 0, inChannel.size()); inChannel.close(); outChannel.close(); } } 大家还可以测试一下文件复制的快慢,目前好多中间件和nosql都开始用内存映射的方式来持久化数据比如前边文章中提到的rocketMQ还有nosql基于LSM-tree的多层Btree在内存和磁盘之间的数据持久化也是用的MappedByteBuffer,其实这里写的非常明白但是真正去看别人写的源码可能还是有些困难, 这就需要耐心和毅力再加上对nio的理解了。