Master Java in 49 days, day 43, buffer data structure bytebuffer

Insert image description here

Column introduction

This column is included in "Proficient in Java in 49 Days from Entry to Employment" . This column is specially prepared for students with no basic knowledge and those who need advanced improvement. It starts from 0 and continues to advance in depth. There will also be "Step by Step" in the follow-up Springboot+vue Practical Project", easily cope with interviews, column subscription address: https://blog.csdn.net/guorui_java/category_11461823.html .

Insert image description here

1. Buffer zone

A buffer is an array composed of values ​​of the same type. Buffer is an abstract class that has many subclasses, including ByteBuffer, CharBuffer, DoubleBuffer, IntBuffer, LongBuffer, and ShortBuffer.

Each buffer has 4 properties:

  1. Capacity , the maximum number of data elements that the buffer can hold. This capacity is set when the buffer is created and can never be changed;
  2. Read and write position position , the index of the next element to be read or written. The position is automatically updated by the corresponding get() and put() functions. What needs to be noted here is that the position of positon starts from 0. For example, if 3 elements have been written into the buffer, then position points to the 4th position, that is, position is set to 3 (the array starts from 0);
  3. limit , the first element of the buffer that cannot be read or written. When the buffer is created, the value of limit is equal to the value of capacity. Suppose capacity = 1024, we set limit = 512 in the program, which means that the capacity of the Buffer is 1024, but it can neither be read nor written from 512 onwards, so it can be understood that the actual available size of the Buffer is 512;
  4. Optional mark mark , mark, a memo location. Save the value of the position pointer at a certain moment by calling mark(). When mark is set to a negative value, it means that the mark is discarded. The tag is undefined until it is set. The usage scenario is, assuming there are 10 elements in the buffer, the current position of position is 2 (that is, the third element if get), and now we only want to send buffered data between 6 - 10, at this time we can buffer .mark(buffer.position()); that is, record the current position into mark, and then buffer.postion(6); at this time, the data sent to the channel is the data 6 - 10. After sending, we can call buffer.reset() to make position = mark, so the mark here is only used to temporarily record the position.

The distance between position and limit specifies the number of bytes that can be read/written.

-1 <= mark <= position <= limit <= capacity
0<= position <= limit <= capacity

Insert image description here

The main purpose of using buffers is to perform read and write loop operations.

Suppose we have a buffer. At the beginning, its position is 0 and its bounds are equal to its capacity. We continuously call put to add values ​​to this buffer. When we exhaust all the data or the amount of data written reaches the capacity, it is time to switch to the read operation.

At this time, you can call the flip method to set the limit to the current position and reset the position to 0. Now when the remaining method returns a positive number (the value it returns is the limit - position), get is called continuously. After we have written all the values ​​in the buffer, call clear to prepare the buffer for the next write cycle. The clear method resets the position to 0 and the limit to the capacity.

If you want to reread the buffer, you can use rewind or mark/reset to reset it.

The buffer can then be filled with data from a channel, or the contents of the buffer can be written out to the channel.

ByteBuffer buffer = ByteBuffer.allocate(RECORD_SIZE);
channel.read(buffer);
channel.position(newpos);
buffer.flip();
channel.write(buffer);

Buffer and its subclasses are not thread-safe. If multiple threads operate the buffer, access to the buffer should be controlled through synchronization.

2. Commonly used methods

1、clear()

Set position to 0, set limit to capacity, and cancel the mark. This is usually called before writing data to the Buffer. The data in the Buffer is not cleared, but these marks tell us where to start writing data to the Buffer. If there is some unread data in the Buffer, after calling the clear() method, the data will be "forgotten", which means that there will no longer be any marks to tell you which data has been read and which has not.

2、 compact()

If there is still unread data in the Buffer and the data is needed later, but you want to write some data first, use the compact() method. The compact() method copies all unread data to the beginning of the Buffer. Then set the position to just behind the last unread element. The limit attribute is still set to capacity like the clear() method. The Buffer is now ready for writing data, but unread data will not be overwritten.

3、flip()

Set limit to the current position, set position to 0, and cancel the mark. This is usually called before reading data from the Buffer.

4、rewind()

Set position to 0 and limit unchanged. It is usually called before rewriting data into the Buffer.

5、hasRemaining()

Returns true when there is at least one element left in the buffer.

6、remaining()

The number of bytes between position and limit

7、 reset()

Restore the value of position to the value of position after the last call to the mark() method.

8、allocate()

Allocate an underlying array of the specified size and wrap it in a buffer object. Use the static method allocate() to allocate the buffer.

9、wrap()

Wrap an existing byte array into a new bytebuffer object.

10、slice()

Creates a subbuffer based on an existing buffer. That is, it creates a new buffer. The new buffer shares data with part of the original buffer. The sub-buffer and the buffer share the same underlying data array. The sharding operation is determined based on the current position and limit values. .

11、order()

Returns the byte order of ByteBuffer.

3. Channel acquisition

1. Obtain from FileInputStream / FileOutputStream

The channel obtained from the FileInputStream object opens the file for reading, and the channel obtained from the FileOutpuStream object opens the file for writing.

FileOutputStream ous = new FileOutputStream(new File("nezha.txt"));
// 获取一个只读通道
FileChannel out = ous.getChannel(); 
FileInputStream ins = new FileInputStream(new File("nezha.txt"));
// 获取一个只写通道
FileChannel in = ins.getChannel();  

2. Obtain from RandomAccessFile

The channel obtained from RandomAccessFaile depends on how the RandomAccessFaile object is created. "r", "w", and "rw" correspond to read mode, write mode, and read-write mode respectively.

RandomAccessFile file = new RandomAccessFile("a.txt", "rw");
// 获取一个可读写文件通道
FileChannel channel = file.getChannel(); 

3. Obtain through FileChannel.open()

Channels opened through the static method FileChannel.open() can specify the opening mode, and the mode is specified through the StandardOpenOption enumeration type.

// 以只读的方式打开一个文件 nezha.txt 
FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.READ); 的通道

4. Write data

1. Writing from a single buffer

The single buffer operation is also very simple, it returns the number of bytes written to the channel.

FileChannel channel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.WRITE);
ByteBuffer buf = ByteBuffer.allocate(5);
byte[] data = "Hello, Java NIO.".getBytes();
for (int i = 0; i < data.length; ) {
    
    
    buf.put(data, i, Math.min(data.length - i, buf.limit() - buf.position()));
    buf.flip();
    i += channel.write(buf);
    buf.compact();
}
channel.force(false);
channel.close();

2. Write from multiple buffers

FileChannel implements the GatherringByteChannel interface, echoing ScatteringByteChannel. Data from multiple buffers can be written to the channel at one time.

FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.WRITE);
ByteBuffer key = ByteBuffer.allocate(10), value = ByteBuffer.allocate(10);
byte[] data = "017 Robothy".getBytes();
key.put(data, 0, 3);
value.put(data, 4, data.length-4);
ByteBuffer[] buffers = new ByteBuffer[]{
    
    key, value};
key.flip();
value.flip();
channel.write(buffers);
channel.force(false);
channel.close();

5. Read data

The value returned by the read(ByteBuffer buf) method of reading data indicates the number of bytes read. If the end of the file is read, the return value is -1. When reading data, position will move backward.

1. Read data into a single buffer

Like general channel operations, data also needs to be read into a buffer and then taken out from the buffer. When calling the read method to read data, you can pass in the parameters position and length to specify the position and length to start reading.

FileChannel channel = FileChannel.open(Paths.get("nezha.txt"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(5);
while(channel.read(buf)!=-1){
    
    
    buf.flip();
    System.out.print(new String(buf.array()));
    buf.clear();
}
channel.close();

2. Read multiple buffers

File channel FileChannel implements the ScatteringByteChannel interface, which can read the contents of the file channel into multiple ByteBuffers at the same time, which is useful when processing files containing several fixed-length data blocks.

ScatteringByteChannel channel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
ByteBuffer key = ByteBuffer.allocate(5), value=ByteBuffer.allocate(10);
ByteBuffer[] buffers = new ByteBuffer[]{
    
    key, value};
while(channel.read(buffers)!=-1){
    
    
    key.flip();
    value.flip();
    System.out.println(new String(key.array()));
    System.out.println(new String(value.array()));
    key.clear();
    value.clear();
}
channel.close();

6. View buffer

You can use the ByteBuffer object to create other types of buffers. The new buffer shares all or part of the memory of the original ByteBuffer. Such a buffer is called a view buffer, which creates other basic data type buffers through the source buffer. The new buffer The area and source buffer share data, but each maintains its own attributes (capacity, limit, position, mark).

7. Using Buffer to read/write data generally follows the following four steps:

  1. Write data to Buffer;
  2. Call flip() method;
  3. Read data from Buffer;
  4. Call the clear() method or compact() method;

When writing data to the buffer, the buffer will record how much data has been written; once you want to read the data, you need to switch the Buffer from write mode to read mode through the flip() method; in read mode, you can read the previously written All data in the buffer. Once all the data has been read, the buffer needs to be cleared so that it can be written to again.

There are two ways to clear the buffer: calling the clear() or compact() method. The clear() method clears the entire buffer. The compact() method will only clear the data that has been read. Any unread data is moved to the beginning of the buffer, and newly written data is placed after the unread data in the buffer.

Guess you like

Origin blog.csdn.net/guorui_java/article/details/132819027