This is the 7th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021 “.
I. Use cases
/ * * *@description: Tests buffer reads and writes *@author: weirx *@date: 2021/11/2 when *@version: 3.0 * /
public class test {
public static void main(String[] args) {
try (RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt"."rw")) {
FileChannel channel = file.getChannel();
// Apply for a buffer of size 10
ByteBuffer buffer = ByteBuffer.allocate(10);
do {
// Write to buffer
int len = channel.read(buffer);
System.out.println("Read bytes:" + len);
if (len == -1) {
break;
}
// Switch buffer read mode
buffer.flip();
while (buffer.hasRemaining()) {
System.out.println((char) buffer.get());
}
// Switch buffer write mode
buffer.clear();
} while (true);
} catch(IOException e) { e.printStackTrace(); }}Copy the code
The output is as follows:
Number of read bytes: 10 1 2 3 4 5 6 7 8 9 0 Number of read bytes: 5 A B C D E Number of read bytes: -1Copy the code
Based on the use case above, we can summarize the following correct positions for using buffer:
1) Write data to buffer, such as channel.read(buffer) 2) call flip() to switch buffer to read mode 3) call buffer.get(), Read data from buffer 4) Switch to write mode by calling clear() or Compact (), where clear() overwrites the data in buffer and Compact () continues to write on the original data. 5) Repeat steps 1 to 4
2. ByteBuffer code analysis
In an article on www.jianshu.com/p/994df8e0d… The basic content, this chapter simple analysis of its source code, to help us understand and learn.
Take a look at the class diagram:
The illustration above shows that it inherits the Buffer class and implements the Comparable interface.
2.1 Buffer class
Take a look at the main attributes in buffer:
Mark: Records the current read or write position. Position: Indicates the next position. Limit: indicates the range. Capacity: Indicates the capacity of the Buffer. It is specified when the Buffer is created and cannot be modified.
Address: indicates the address of the direct memory.
2.1.1 Buffer Read/write model
For the whole read-write model, let’s go straight to the graph:
2.1.2 Main methods of Buffer
1) Constructor: specify mark, position, limit, capacity, etc. If capacity is less than 0, or mark is greater than pos, raise an exception.
Buffer(int mark, int pos, int lim, int cap) { // package-private if (cap < 0) throw new IllegalArgumentException("Negative capacity: " + cap); this.capacity = cap; limit(lim); position(pos); if (mark >= 0) { if (mark > pos) throw new IllegalArgumentException("mark > position: (" + mark + " > " + pos + ")"); this.mark = mark; }}Copy the code
Limit (lim) method: Specifies a new range and throws an exception if the range is greater than the capacity or less than 0. If the current position is greater than the set range, the range is assigned a position. If mark is greater than the range, restore mark to -1. The reciprocal two judgments usually only happen when the new limit is less than the old limit.
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
Copy the code
Position (pos) method: Throws an exception if the new postion is greater than limit or less than 0. If mark is greater than position, restore mark to -1.
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
Copy the code
2) Some common methods provided by Buffer **capacity(),limit(),position()** are methods to obtain the current capacity, range, and position.
Mark () method: Manually sets the current position with a mark mark, usually used by the reset() method.
public final Buffer mark(a) {
mark = position;
return this;
}
Copy the code
Reset () method: After marking position with the mark() method, use the reset method to assign the marked mark value to position.
public final Buffer reset(a) {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
Copy the code
Flip () method: switch the buffer to read mode, which can be seen in the previous figure. Set the maximum position in which the data exists to limit, position to 0, and discard mark.
public final Buffer flip(a) {
limit = position;
position = 0;
mark = -1;
return this;
}
Copy the code
Rewind () : This method is used when the buffer has read some or all of the data and resets the buffer to start again, discarding Mark.
public final Buffer rewind(a) {
position = 0;
mark = -1;
return this;
}
Copy the code
The remaining() method returns the remaining capacity of the current buffer.
public final int remaining() {
return limit - position;
}
Copy the code
HasRemaining () : Returns true if the current position is less than limit. In write mode, it indicates that the write can continue. In read mode, it indicates that there is still unread data. If false is returned, the opposite is true.
public final boolean hasRemaining(a) {
return position < limit;
}
Copy the code
IsReadOnly () : indicates whether it is read-only. IsDirect () : indicates whether it isDirect memory
2.2 Comparable interface
ByteBuffer implements the Comparable interface, in which there is only one comparison method, compareTo(T O).
ByteBuffer implements this interface method:
public int compareTo(ByteBuffer that) {
int n = this.position() + Math.min(this.remaining(), that.remaining());
for (int i = this.position(), j = that.position(); i < n; i++, j++) {
int cmp = compare(this.get(i), that.get(j));
if(cmp ! =0)
return cmp;
}
return this.remaining() - that.remaining();
}
private static int compare(byte x, byte y) {
return Byte.compare(x, y);
}
Copy the code
Compare this buffer to another buffer. Compare two byte buffers by comparing the sequence of their remaining elements in lexicographical order, regardless of the starting position of each sequence in its corresponding buffer.
Compare pairs of byte elements as if by calling Byte.compare(byte, byte).
The byte buffer cannot be compared with any other type of object.
2.3 ByteBuffer
Here comes the main ByteBuffer source analysis.
2.3.1 initialization
First, let’s see how to initialize a ByteBuffer. There are two ways to initialize ByteBuffer in the source: allocate and allocateDirect.
2.3.1.1 the allocate (int capacity)
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
Copy the code
As shown above, you end up with a new HeapByteBuffer, the buffer in the heap. Tracing to the bottom layer is actually the Buffer class we analyzed earlier. We focus on the way direct memory is allocated.
2.3.1.2 allocateDirect (int capacity)
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
Copy the code
As shown above, new a DirectByteBuffer, a direct memory buffer. Let’s look at the class diagram for this class:
As shown in the figure above, it inherits from MappedByteBuffer and implements the DirectBuffer interface. Let’s analyze the source code bit by bit.
When we apply for direct memory, we use the parent constructor, which is an MappedByteBuffer. Keep using the constructor of its parent class and, eventually, a ByteBuffer.
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); . . }Copy the code
MappedByteBuffer(int mark, int pos, int lim, int cap) { // package-private
super(mark, pos, lim, cap);
this.fd = null;
}
Copy the code
ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
this(mark, pos, lim, cap, null, 0);
}
Copy the code
What is MappedByteBuffer? First take a look at the class comment:
A direct byte buffer whose contents are a memory-mapped region of the file. The mapped byte buffer is created using the Filechannel.map method. This class extends the ByteBuffer class with operations specific to memory-mapped file areas. The mapped byte buffer and the file mapping it represents remain in effect until the buffer itself is garbage collected. The contents of the mapped byte buffer can change at any time, for example, if this program or another program changes the contents of the corresponding area of the mapped file. Whether and when such changes occur depends on the operating system and is therefore not specified. All or part of the mapped byte buffer may not be accessible at any time, for example, if the mapped file is truncated. It is strongly recommended that appropriate precautions be taken to avoid operations by the program or concurrent programs on the mapped file, except for reading or writing the contents of the file. The other behavior of mapped byte buffers is no different from normal direct byte buffers.Copy the code
Let’s continue tracing the constructor of direct memory:
DirectByteBuffer(int cap) {// package-private // Construct a MappedByteBuffer super(-1, 0, cap, cap); / / whether the distribution of the page in memory alignment Boolean pa = VM. IsDirectMemoryPageAligned (); // initialize the pageSize int ps = bits.pagesize (); Cap long size = math.max (1L, (long)cap + (pa? ps : 0)); -xx :MaxDirectMemorySize specifies the maximum size of out-of-heap memory, and based on the size of out-of-heap memory currently in use, ReserveMemory (size, cap); long base = 0; Unsafe. allocateMemory(size); // Unsafe. base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) {// Release memory Bits. UnreserveMemory (size, cap); throw x; Unsafe.setmemory (base, size, (byte) 0); unsafe.setMemory(base, size, (byte) 0); // If memory is allocated per page, and the allocated memory divided by the size of each page is divisible without remainder, calculate the address of direct memory if (pa && (base % ps! // round to the edge of the page address = base + ps - (base & (ps - 1)); } else { address = base; } // Create a clean daemon thread that handles direct memory, which is a PhantomReference virtual reference, when full GC occurs, Cleaner = cleaner. create(this, new Deallocator(base, size, cap)); att = null; }Copy the code
2.3.2 ByteBuffer write
There are usually two ways to write data: 1) Call channel’s read method
int readBytes = channel.read(buf);
Copy the code
2) Call buffer’s own PUT method
As shown in the figure above, it supports writing to the middle type, writing to the specified index position, and so on.
2.3.2 ByteBuffer read
There are also two ways to read data: 1) call channel’s write method
int writeBytes = channel.write(buf);
Copy the code
2) Call buffer’s own get method
Various types of reads are supported, as well as specified ranges of reads, as shown in the figure above.
Note:
- The get() method moves the position of the buffer backward. If you want to read repeated data, you can reset the position using rewind(). \
- The get(int) method that specifies the index position does not move position backward.
The other methods are the same as Buffer, which has been explained above.
3. ByteBuffer and string conversion
public class StringToBuffer {
public static void main(String[] args) {
// String to byteBuffer
ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("Hello");
System.out.println(buffer1);
//byteBuffer contains a stringCharBuffer buffer2 = StandardCharsets.UTF_8.decode(buffer1); System.out.println(buffer2.getClass()); System.out.println(buffer2.toString()); }}Copy the code
4. Scattered read and write (multiple buffers read or write simultaneously)
There is a file called text.txt, which reads as follows:
onetwothree
Copy the code
4.1 Scattered Reading:
Example code is as follows:
public class ScatteringRead {
public static void main(String[] args) {
try (RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt", "rw")) {
FileChannel channel = file.getChannel();
ByteBuffer a = ByteBuffer.allocate(3);
ByteBuffer b = ByteBuffer.allocate(3);
ByteBuffer c = ByteBuffer.allocate(5);
channel.read(new ByteBuffer[]{a, b, c});
a.flip();
b.flip();
c.flip();
System.out.println(print(a));
System.out.println(print(b));
System.out.println(print(c));
} catch (IOException e) {
e.printStackTrace();
}
}
static String print(ByteBuffer b){
StringBuilder stringBuilder = new StringBuilder();
for(int i =0 ; i< b.limit();i++){
stringBuilder.append((char)b.get(i));
}
return stringBuilder.toString();
}
}
Copy the code
Results:
one
two
three
Copy the code
4.2 dispersed to write
public class GatheringWrite { public static void main(String[] args) throws Exception { RandomAccessFile file = new RandomAccessFile("C:\\Users\\P50\\Desktop\\text.txt", "rw"); FileChannel channel = file.getChannel(); ByteBuffer d = ByteBuffer.allocate(4); ByteBuffer e = ByteBuffer.allocate(4); channel.position(11); d.put(new byte[]{'f', 'o', 'u', 'r'}); e.put(new byte[]{'f', 'i', 'v', 'e'}); d.flip(); e.flip(); System.out.println(print(d)); System.out.println(print(e)); channel.write(new ByteBuffer[]{d, e}); } static String print(ByteBuffer b) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < b.limit(); i++) { stringBuilder.append((char) b.get(i)); } return stringBuilder.toString(); }}Copy the code
Results:
four
five
Copy the code
The file content is as follows:
onetwothreefourfive
Copy the code
Pay attention to
Buffers are not thread-safe.