javaNIO学习笔记(三)
javaNIO 学习笔记(三)Java NIO Buffer
缓冲区,可以通过 channel 将数据写入缓冲区,也可以从通道中读取数据到缓冲区
缓冲区本质上是一个内存块,您可以将数据写入其中,然后再读取数据。这个内存块包装在一个NIO缓冲区对象中,该对象提供了一组方法,使使用内存块变得更容易。
使用缓冲区读取和写数据一般分为下面四个步骤: 将数据写入缓冲区 调用 flip() 方法从缓冲区读取数据 调用 clear() 或者compact() 当数据写入数据到缓冲区时,缓冲区会记录对应数据量的信息(这个需要看缓冲区大小和数据量大小)。
数据读取后需要使用flip()方法切换缓冲区的读写模式(换个角度可以理解为存取模式)。在读取模式下,您可以通过相应的方法将数据从缓冲区中读取出来。读取完数据后需要将缓冲区清空。这样缓冲区就会再次进入写入模式。(这里调用的方式就是 clear 和compact , clear 是清空, compact 则是清理掉已读取的数据,未读取的数据则放入缓冲区头)
使用的例子上一篇学习笔记已经写了。
我们来看下 buffer 的工作模式。buffer 有三个主要属性capacity(buffer容量):这是 buffer 的大小即容量。在调用buffer.allocate()指定大小即是这个属性position(读取模式的位置,在读写模式中表现不一样) limit(读写模式中表现也不一样)
在写入模式下, position 初始值为0.当一个字节,没写入一个字节数字都会将position +1.limit 初始值为capacity 。在读取模式下,会将limit 位置设置在```position位置放置为0,此时读取数据是从 position到limit 。看下clear 和flip 的源码大概就可以理解了public final Buffer clear() { position = 0; //设置当前下标为0 limit = capacity; //设置写越界位置与和Buffer容量相同 mark = -1; //取消标记 return this; } public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
buffer的主要类型如下: ByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer MappedByteBuffer
可以看到,这些缓冲区类型表示不同的数据类型。换句话说,它们允许您将缓冲区中的字节改为char、short、int、long、float或double。
学习下 buffer 的几个常见方法:// 可以通过调用Buffer.mark()方法标记缓冲区中的给定位置。然后,您可以通过调用Buffer.reset()方法将位置重置回标记的位置。 public final Buffer mark() { mark = position; return this; } public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; } equals()和compareTo ()可以使用equals()和compareTo()比较两个缓冲区。 equals ()
两个缓冲区是相等的,如果:
它们是相同类型的(字节、char、int等)。
它们在缓冲区中有相同数量的剩余字节、字符等。
所有剩余的字节、字符等都是相等的。
可以看到,equals只比较缓冲区的一部分,而不是其中的每个元素。实际上,它只是比较缓冲区中剩余的元素。
compareTo ()
方法比较两个缓冲区的剩余元素(字节,字符等),用于排序例程。一个缓冲区被认为比另一个缓冲区"小",如果:
第一个元素等于另一个缓冲区中对应的元素,小于另一个缓冲区中的元素。
所有的元素都是相等的,但是第一个缓冲区比第二个缓冲区早耗尽元素(它的元素更少)。 Java NIO Scatter / Gather
Java NIO 提供了内置功能 Scatter/Gather。可以将数据从一个通道写入多个缓冲区。通道也可以从多个缓冲区收集数据。在需要分别处理传输数据的各个部分的情况下,Scatter/Gather非常有用。例如,如果消息由消息头和消息体组成,则可以将消息头和消息体保存在单独的缓冲区中。这样做可以更容易分别使用标题和主体。 package jniolearn; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @Author: jimmy * @Date: 2020/6/14 14:50 * @Description: */ public class NioScatter { public static void main(String[] args) throws IOException { // 创建一个rw模式的随机文件 RandomAccessFile randomAccessFile =new RandomAccessFile("D:nioFile.txt", "rw"); // 获取fileChinnel FileChannel fileChannel = randomAccessFile.getChannel(); // 1、分配缓冲区 ByteBuffer header = ByteBuffer.allocate(10); ByteBuffer body = ByteBuffer.allocate(128); // 缓冲数组 ByteBuffer[] bufferArray = {header, body}; // 2、将数组读入到数组 fileChannel.read(bufferArray); // Buffer切换模式之前,即处于写模式下,打印Buffer,查看position, limit, capacity属性 System.out.println(header.toString()); System.out.println(body.toString()); // 3、切换模式 header.flip(); body.flip(); // 4、获取数组 System.out.println("header:"); while (header.hasRemaining()) { System.out.print((char) header.get()); } System.out.println(" body:"); while (body.hasRemaining()) { System.out.print((char) body.get()); } header.clear(); body.clear(); fileChannel.close(); } } // 返回结果 java.nio.HeapByteBuffer[pos=10 lim=10 cap=10] java.nio.HeapByteBuffer[pos=24 lim=128 cap=128] header: Hi, I am j body: immy; be happy everyday
read 按照buffer 数组中的顺序将从channel中读取的数据写入到buffer ,当一个buffer 被写满后,channel 紧接着向另一个buffer 中写。Scattering Reads 在移动下一个buffer 前,必须填满当前的buffer ,这也意味着它不适用于动态消息。这样若要是想在消息中使用那么就必须规定好每一段的字节长度,并且按照这个规定严格执行,不能存在偏差。不然就会出现部分信息少读或者多读的情况。package jniolearn; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @Author: jimmy * @Date: 2020/6/14 15:07 * @Description: */ public class NioGather { public static void main(String[] args) throws IOException { RandomAccessFile randomAccessFile =new RandomAccessFile("D:gaterFile.txt", "rw"); // 获取fileChinnel FileChannel fileChannel = randomAccessFile.getChannel(); // 1、分配缓冲区 ByteBuffer header = ByteBuffer.allocate(32); ByteBuffer body = ByteBuffer.allocate(128); // 2、放入数据 header.put("hi, weather ?".getBytes()); header.put("body:raining".getBytes()); // 3、存入buffer数组 ByteBuffer[] bufferArray = {header, body}; System.out.println(header.toString()); System.out.println(body.toString()); header.flip(); body.flip(); fileChannel.write(bufferArray); fileChannel.close(); } }
查看文件信息:hi, weather ?body:raining
write 方法同样是按照buffer 数组的顺序,将数据写入到channel ,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含14byte的数据,那么这14byte的数据将被写入到channel 中。和Scattering Reads 相反,Gathering Writes 能较好地处理动态消息。Java NIO Channel to Channel Transfers
Java NIO还提供了一种通道间传输数据的方法, FileChannel 类有一个transferTo() 和一个transferFrom() 方法---- transferFrom ---- RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); toChannel.transferFrom(fromChannel, position, count); ---- transferTo ---- RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); fromChannel.transferTo(position, count, toChannel);
小结下前面几篇学习的内容: 如何使用 buffer 需要先分配一个缓冲区(我这里理解为初始化一个指定大小的缓冲区)使用ByteBuffer.allocate() 读则是从channel read ,写则是先要将数据放入缓冲区,然后使用channel write 把数据写入通道注意buffer 的模式切换使用flip 方法。另外还有 clear compact rewind 等方法buffer 的几个主要属性capacity 缓冲区大小(比如缓冲区长度48,则capacity也是48.但是这里要注意capacity下表是从0开始,所以这个可以理解为内存溢出位)position 在读模式的时候初始值为0,读数据的时候会逐步增加最大能读到capacity -1。切换到写模式的时候position为0limit 读模式的时候默认为capacity。写模式的时候,会将limit设置为读模式的position的位置。scatter 和gather 可以理解为分散收集,一个是读,一个是写。这个是内置的。就是一个channel 和多个buffer 的读写。channel 如何获取可以直接使用open方法,fileChannel则还可以通过类RandomAccessFile 的实例getChannel