Buffer is actually a container object, which contains data to be written or just read. Adding a Buffer object to NIO reflects an important difference between the new library and the original I/O. In Stream-oriented I/O, you can directly write data or directly read the data to the Stream object.
In the NIO database, all data is processed using a buffer. When reading data, it is directly read into the buffer. When writing data, it is written to the buffer. When you access the data in NIO at any time, you put it in the buffer zone.
The buffer is essentially an array. It is usually a byte array, but other types of arrays can also be used. However, a buffer zone is not just an array. The buffer zone provides structured access to data, and supports tracking system read/write processes.
The most common buffer type is ByteBuffer. A ByteBuffer can perform get/set operations on its underlying byte array (that is, obtaining and setting byte ).
ByteBuffer is not the only buffer type in NIO. In fact, each basic Java type has a buffer type (only the boolean type does not have its corresponding buffer class ):
ByteBuffer
CharBuffer
Protocol Buffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
Each Buffer class is an instance of the Buffer interface. Except for ByteBuffer, each Buffer class has identical operations, but they process different data types. Because most standard I/O operations use ByteBuffer, it has all the shared buffer operations and some special operations. Let's take a look at the Buffer class hierarchy diagram:
Each Buffer has the following attributes:
Capacity
The maximum amount of data that a Buffer can store. Capacity is generally specified when the buffer is created.
Limit
Read/write operations performed on the Buffer cannot exceed this subscript. When writing data to the buffer, the limit is generally equal to the capacity. When reading data, the limit represents the length of valid data in the buffer.
Position
The position variable tracks the amount of data written to or read from the buffer.
More specifically, when you read data from the channel to the buffer, it indicates which element of the next data will be placed in the array. For example, if you read three bytes from the channel to the buffer
Position will be set to 3, pointing to the 4th elements in the array. Conversely, when you obtain data from the buffer for writing, it indicates which element of the next data comes from the array. For example, when you
If the buffer is written in five bytes to the channel, the position of the buffer is set to 5, pointing to the sixth element of the array.
Mark
A position subscript for temporary storage. When you call mark (), the mark is set to the current position value. When you call reset (), the position attribute is set to the mark value. The mark value is always less than or equal to the position value. If the position value is smaller than the mark value, the current mark value will be discarded.
These attributes always meet the following conditions:
0 <= mark <= position <= limit <= capacity
Internal implementation mechanism of the buffer zone:
Next we will take the data copying from an input channel to an output channel as an example to analyze each variable in detail and illustrate how they work collaboratively:
Initial variable:
First, observe a newly created buffer. Taking ByteBuffer as an example, assuming that the buffer size is 8 bytes, the initial status of ByteBuffer is as follows:
Recall that the limit value cannot be greater than capacity. In this example, both values are set to 8. We can illustrate this by pointing them to the end of the array (8th slots.
Set position to 0. It indicates that if we read some data into the buffer, the next read data enters the slot 0. If we write some data from the buffer, the next byte read from the buffer comes from slot 0. The position settings are as follows:
Since the maximum buffer capacity will not change, we can ignore it in the following discussion.
First read:
Now we can start read/write operations on the newly created buffer. First, read some data from the input channel to the buffer. The first read gets three bytes. They are put in the array from
Position. The position is set to 0. After reading it, the position is increased to 3, as shown below, the limit is not changed.
Second read:
During the second read, we read the other two bytes from the input channel to the buffer zone. These two bytes are stored at the position specified by the position, so the position is increased by 2, and the limit is not changed.
Flip:
Now we need to write the data to the output channel. Before that, we must call the flip () method. The source code is as follows:
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
This method is very important to do two things:
It sets limit to the current position.
Ii. It sets position to 0.
The previous figure shows the buffer before flip. The following is the buffer zone after flip:
Now we can write data from the buffer into the channel. Position is set to 0, which means that the next byte we get is the first byte. Limit has been set to the original position, which means that it includes all the previously read bytes, and there are not many bytes.
First write:
At the first write, we take four bytes from the buffer and write them into the output channel. This increases position to 4, while limit remains unchanged, as shown below:
Second write:
We have only one byte left to write. Limit is set to 5 when we call flip (), and the position cannot exceed limit. Therefore, the last write operation extracts a byte from the buffer and writes it to the output channel. This increases position to 5 and keeps limit unchanged, as shown below:
Clear:
The last step is to call the clear () method of the buffer zone. This method resets the buffer to receive more bytes. The source code is as follows:
public final Buffer clear() { osition = 0; limit = capacity; mark = -1; return this; }
Clear does two very important tasks:
It sets limit to be the same as capacity.
Ii. It sets position to 0.
The status of the buffer after clear () is called. Now the buffer can receive new data.
At this point, we only use a buffer to transfer data from one channel to another. However, programs often need to process data directly. For example, you may need to save user data to a disk. In this case, you must put the data directly into the buffer and then write the buffer to the disk using the channel. Alternatively, you may want to read user data from the disk. In this case, you need to read the data from the channel to the buffer, and then check the data in the buffer. In fact, every basic type of buffer provides us with a method to directly access data in the buffer. We take ByteBuffer as an example to analyze how to use the get () and put () provided by it () method to directly access data in the buffer.
A) get ()
The ByteBuffer class has four get () methods:
byte get(); ByteBuffer get( byte dst[] ); ByteBuffer get( byte dst[], int offset, int length ); byte get( int index );
The first method gets a single byte. The second and third Methods read a group of bytes into an array. The fourth method retrieves bytes from a specific location in the buffer zone. The methods that return ByteBuffer only return the this value of the buffer that calls them. In addition, we think that the first three get () methods are relative, and the last method is absolute. "Relative" means that the get () operation is subject to the limit and position values. More specifically, the byte is read from the current position, and the position is increased after the get operation. On the other hand, an "absolute" method ignores the limit and position values and does not affect them. In fact, it completely bypasses the statistical method of the buffer zone. The methods listed above correspond to the ByteBuffer class. Other classes have equivalent get () methods. These methods are identical in other aspects except for processing bytes. They process types that are compatible with the buffer class.
Note:Here we will focus on the second and third methods.
ByteBuffer get( byte dst[] ); ByteBuffer get( byte dst[], int offset, int length );
These two get () methods are mainly used to move data in batches and can be used for data replication from the buffer zone to the array. In the first form, only one array is used as a parameter to release a buffer to a given array. The second method uses the offset and length parameters to specify the subintervals of the target array. The synthesis effect of these batch moves is the same as the loop discussed previously, but these methods may be much more efficient, because this buffer implementation can use local code or other Optimizations to move data.
Buffer. get (myArray) is equivalent:
Buffer. get (myArray, 0, myArray. length );
Note:If the required amount of data cannot be transmitted, no data will be transmitted, the buffer status remains unchanged, and a BufferUnderflowException exception will be thrown.Therefore, when you input an array without specifying the length, you need to fill the entire array. If the data in the buffer is not enough to fill the array completely, you will get an exception. This means that if you want to pass a small buffer into a large array, You need to specify the remaining data length in the buffer. The first example above will not copy the remaining data elements in the buffer zone to the bottom of the array as you have concluded at first glance. For example, the following code:
String str = "com.xiaoluo.nio.MultipartTransfer"; ByteBuffer buffer = ByteBuffer.allocate(50); for(int i = 0; i < str.length(); i++) { buffer.put(str.getBytes()[i]); } buffer.flip();byte[] buffer2 = new byte[100]; buffer.get(buffer2); buffer.get(buffer2, 0, length); System.out.println(new String(buffer2));
The java. nio. BufferUnderflowException exception will be thrown here, because the array wants the data in the cache to be filled up. If it is not filled, an exception will be thrown, so the code should be changed to the following:
// Obtain the length of unread data in the buffer.
Int length = buffer. remaining (); byte [] buffer2 = new byte [100]; buffer. get (buffer2, 0, length );
B) put ()
The ByteBuffer class has five put () methods:
ByteBuffer put( byte b ); ByteBuffer put( byte src[] ); ByteBuffer put( byte src[], int offset, int length ); ByteBuffer put( ByteBuffer src ); ByteBuffer put( int index, byte b );
The first method writes (put) A single byte. The second and third methods are written to a group of bytes from an array. The fourth method writes data from a given source ByteBuffer to this ByteBuffer. The fifth method writes bytes to a specific location in the buffer. The methods that return ByteBuffer only return the this value of the buffer that calls them. Like the get () method, we divide the put () method into "relative" or "absolute. The first four methods are relative, and the fifth method is absolute. The method shown above corresponds to the ByteBuffer class. Other classes have equivalent put () methods. These methods are identical except for processing bytes. They process the types that match the buffer class.
C) typed get () and put () Methods
In addition to the get () and put () methods described in the previous sections, ByteBuffer also has other methods for reading and writing different types of values, as shown below:
GetByte ()
GetChar ()
GetShort ()
GetInt ()
GetLong ()
GetFloat ()
GetDouble ()
PutByte ()
PutChar ()
PutShort ()
PutInt ()
PutLong ()
PutFloat ()
PutDouble ()
In fact, each method has two types: relative and absolute. They are useful for reading formatted binary data (like the file header.
The following internal loop summarizes how to use the bufferCopy data from input channel to output channel.
While (true) {// clear method to reset the buffer, you can read the new content into the buffer. clear (); int val = inChannel. read (buffer); if (val =-1) {break;} // The flip method outputs the data in the buffer to the buffer in the new channel. flip (); outChannel. write (buffer );}
Read () and write () calls are greatly simplified, because many work details are completed by the buffer zone. The clear () and flip () methods are used to switch between read and write buffer.
Now, the buffer content is written here. Next we will continue to learn NIO-Channel ).