Buffer in NIO

In the code that called Channel before, a class called ByteBuffer was used, which is a subclass of Buffer. This class called Buffer is specially used to solve the problem of speed mismatch between high-speed devices and low-speed devices, and can also reduce the number of reads and writes of the database.

It is divided into input buffer and output buffer.

Many beginners don't understand the difference between "buffering" and "caching". I try to explain it in plain language:

1. The buffer needs to be periodically refreshed, cleared, reset, etc., and these operations may not be needed for the cache. For example, when cooking, the chopping board is the buffer, and the refrigerator is the buffer, because it needs to be cut, patted, and chopped from the refrigerator to the pot, and the next dish must be emptied every time, and the refrigerator does not need to be regularly Empty, reset (unless there's a power outage and things break);

2. The core function of the buffer is to decouple the speed constraints between devices and become a "buffer" between devices, while the cache is used to speed up reading and reduce the number of recalculations or reacquisitions from the database. Compared with buying every dish from the vegetable market, it is obviously much faster to put it in the refrigerator; and compared to taking it from the refrigerator every time you cook it, of course it is faster to take it from the cutting board. That is: "grocery shopping speed (disk) < refrigerator fetching speed (cache) < chopping board fetching speed (buffer)", that is the relationship;

3. The buffer focuses on speed and writing, while the cache focuses on times and reading. Just like the chopping board focuses on cutting vegetables, while the refrigerator focuses on storage;

4. The current cache is generally very large, even reaching the TB level (1TB=1024GB). It is impossible to have such a large buffer (of course, you can also make the chopping board as big as a refrigerator, anyway, I have never seen this kind of - _-!).

When you see buffering and caching again in the future, you can compare the cutting board at home with the refrigerator.

There are eight types of buffers in NIO: ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer, and MappedByteBuffer. The first seven correspond to basic data types, and MappedByteBuffer is specially used for memory mapping.

The buffer area is actually a container, a container composed of continuous arrays/collections. Channel provides a channel for reading data from files and networks, but all data read and written must pass through Buffer.

The process of writing data to Buffer is:

1. Write data from Channel to Buffer: channel.read(buf)

2. Call the put() method of Buffer: buf.put(Object)

The process of reading data from Buffer is:

1. Read data from Buffer to Channel: channel.write(buf)

2. Call the get() method of Buffer: buf.get()

The reading and writing process is probably like this:

It’s still the same sentence from yesterday: If you develop your own RPC-like system or MQ-like middleware in a big factory, then you must be proficient in this; otherwise, you can understand it and don’t have to die. Buffer is actually enough to see this. As for: Buffer properties, steps to use Buffer, how JVM creates buffers in memory, etc., these should be compulsory courses for Mianba, but they are rarely used in development.

Still in code.

Common methods of Buffer:

// 分配JVM间接缓冲区
ByteBuffer buffer = ByteBuffer.allocate(32);
System.out.println("buffer初始状态: " + buffer);
// 将position设回8
buffer.position(8);
System.out.println("buffer设置后状态: " + buffer);

System.out.println("测试reset ======================>>>");
// clear()方法,position将被设回0,limit被设置成capacity的值
buffer.clear();
System.out.println("buffer clear后状态: " + buffer);
// 设置这个缓冲区的位置
buffer.position(5);
// 将此缓冲区的标记设置5
// 如果没有buffer.mark();这句话会报错
buffer.mark();
buffer.position(10);
System.out.println("reset前状态: " + buffer);
// 将此缓冲区的位置重置为先前标记的位置(buffer.position(5))
buffer.reset();
System.out.println("reset后状态: " + buffer);

System.out.println("测试get ======================>>>");
buffer = ByteBuffer.allocate(32);
buffer.put((byte) 'x').put((byte) 'i').put((byte) 'a').put((byte) 'n').put((byte) 'g');
System.out.println("flip前状态: " + buffer);
// 转换为读模式
buffer.flip();
System.out.println("get前状态: " + buffer);
System.out.println((char) buffer.get());
System.out.println("get后状态: " + buffer);

System.out.println("测试put ======================>>>");
ByteBuffer pb = ByteBuffer.allocate(32);
System.out.println("put前状态: " + pb +
        ", put前数据: " + new String(pb.array()));
System.out.println("put后状态: " + pb.put((byte) 'w') +
        ", put后数据: " + new String(pb.array()));
System.out.println(pb.put(3, (byte) '3'));
// put(3, (byte) '3')并不改变position的位置,但put((byte) '3')会
System.out.println("put(3, '3')后状态: " + pb + ", 数据: " + new String(pb.array()));
// 这里的buffer是 xiang[pos=1 lim=5 cap=32]
System.out.println("buffer叠加前状态: " + buffer +
        ", buffer叠加前数据: " + new String(buffer.array()));
// buffer.put(pb);会抛异常BufferOverflowException
pb.put(buffer);
// 叠加后数据是wiang,因为buffer的position=1
System.out.println("put(buffer)后bb状态: " + pb + ", buffer叠加后数据: " + new String(pb.array()));

// 重新读取buffer中所有数据
System.out.println("测试rewind ======================>>>");
buffer.clear();
buffer.position(10);
System.out.println("buffer当前状态: " + buffer);
// 返回此缓冲区的限制
buffer.limit(15);
System.out.println("limit后状态: " + buffer);
// 把position设为0,mark设为-1,不改变limit的值
buffer.rewind();
System.out.println("rewind后状态: " + buffer);

// 将所有未读的数据拷贝到Buffer起始处,然后将position设到最后一个未读元素正后面
System.out.println("测试compact ======================>>>");
buffer.clear();
buffer.put("abcd".getBytes());
System.out.println("compact前状态: " + buffer);
System.out.println(new String(buffer.array()));
// limit=position;position=0;mark=-1; 翻转,也就是让flip之后的position到limit这块区域变成之前的0到position这块
// 翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态,或者相反
buffer.flip();
System.out.println("flip后状态: " + buffer);
// get()方法:相对读,从position位置读取一个byte,并将position+1,为下次读写作准备
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
System.out.println("三次调用get后: " + buffer);
System.out.println(new String(buffer.array()));
// 把从position到limit中的内容移到0到limit-position的区域内
// position和limit的取值也分别变成limit-position、capacity
// 如果先将positon设置到limit,再compact,那么相当于clear()
buffer.compact();
System.out.println("compact后状态: " + buffer);
System.out.println(new String(buffer.array()));

Java generally uses buffered I/O classes such as BufferedInputStream and BufferedReader to process large files, but if the file is very large, such as reaching GB or even TB level, the faster way is to use the file memory mapping scheme introduced in NIO: MappedByteBuffer.

You only need MappedByteBuffer to have extremely high read and write performance. The main reason is that it supports asynchronous operations, and that's it!

You can try it with a large file:

// ByteBuffer读取大文件
public static void useFileChannel() {
    try{
        FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
        FileChannel channel = fis.getChannel();
        long start = System.currentTimeMillis();
        ByteBuffer buff = ByteBuffer.allocate((int) channel.size());
        buff.clear();
        channel.read(buff);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        fis.close();
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// MappedByteBuffer读取大文件
public static void useMappedByteBuffer() {
    try{
        FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
        FileChannel channel = fis.getChannel();
        long start = System.currentTimeMillis();
        MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        fis.close();
        channel.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    useFileChannel();
    useMappedByteBuffer();
}

Finally, put these two methods into main() to see the effect.

The Buffer in NIO is enough to say so much, and it will be more direct to use code to feel it.

Guess you like

Origin blog.csdn.net/weixin_47367099/article/details/127458562