Netty source code analysis series byte buffer ByteBuf

preface

After understanding the principle of ByteBuffer, it is easier to understand Netty's ByteBuffer.

ByteBuf is a data buffer encapsulated by Netty framework. It is different from position, limit, flip and other attributes and operations to control the reading and writing of ByteBuffer. ByteBuf assists the reading and writing of buffer through two position pointers, readIndex and writeIndex respectively.

The readIndex, writeIndex, and capacity variables have the following relationships:

0 <= readIndex <= writeIndex <= capacity
 Copy code

Implementation principle

When initializing ByteBuffer, the values of readIndex and writeIndex are 0 at first. As shown in the figure below:

After writing data, writeIndex will increase, as shown in the following figure:

After reading data, the readIndex will increase, but not exceed the writeIndex, as shown in the following figure:

After reading, the area from index 0 to readIndex is regarded as discarded bytes. You can call the discardReadBytes method to free up this space, which is similar to the compact() method of ByteBuffer to remove useless data and reuse the buffer. The following figure shows the situation after discardReadBytes is executed, which is equivalent to that the writable space becomes larger.

Use case for ByteBuf

In order to better understand ByteBuf, the following example is written:

public class ByteBufDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// Create a buffer
		ByteBuf buffer = Unpooled.buffer(10);
		System.out.println("------------Initial buffer------------");
		printBuffer(buffer);

		// Add some data to the buffer
		System.out.println("------------Add data to buffer------------");

		String s = "love";
		buffer.writeBytes(s.getBytes());
		printBuffer(buffer);

		// Read data
		System.out.println("------------Read data------------");

		while (buffer.isReadable()) {
			System.out.println(buffer.readByte());
		}

		printBuffer(buffer);

		// Execute compact
		System.out.println("------------implement discardReadBytes------------");
		buffer.discardReadBytes();
		printBuffer(buffer);

		// Execute clear
		System.out.println("------------implement clear Empty buffer------------");
		buffer.clear();
		printBuffer(buffer);

	}

	/**
	 * Print out ByteBuf information
	 * 
	 * @param buffer
	 */
	private static void printBuffer(ByteBuf buffer) {
		System.out.println("readerIndex: " + buffer.readerIndex());
		System.out.println("writerIndex: " + buffer.writerIndex());
		System.out.println("capacity: " + buffer.capacity());
	}
}
Copy code

Output results:

------------Initial buffer------------
readerIndex: 0
writerIndex: 0
capacity: 10
------------Add data to buffer------------
readerIndex: 0
writerIndex: 4
capacity: 10
------------Read data------------
108
111
118
101
readerIndex: 4
writerIndex: 4
capacity: 10
------------implement discardReadBytes------------
readerIndex: 0
writerIndex: 0
capacity: 10
------------implement clear Empty buffer------------
readerIndex: 0
writerIndex: 0
capacity: 10

Process finished with exit code 0
 Copy code

Comparing the ByteBuffer and ByteBuffer examples, we can see that Netty provides a more convenient tool to create ByteBuffer (unpooled). At the same time, it is no longer necessary to execute the flip() method to switch the read-write mode. ByteBuf is easier to use by contrast.

Three usage modes of ByteBuf

ByteBuf fer has three usage modes: Heap Buffer mode, Direct Buffer mode and Composite Buffer mode.

Heap buffer mode

Heap buffer mode is also called supporting array. Its data is stored in the heap space of the JVM, which is realized by storing the data in the array.

Advantages: data stored in JVM heap can be quickly created and released, and provides a method for fast data access;

Disadvantages: each time the data is transferred with I/O, the data needs to be copied to the direct buffer.

The following is a code example of a heap buffer:

public class ByteBufHeapBufferDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		// Create a heap buffer
		ByteBuf buffer = Unpooled.buffer(10);
		String s = "waylau";
		buffer.writeBytes(s.getBytes());

		// Check whether it is a support array
		if (buffer.hasArray()) {

			// Gets a reference to the support array
			byte[] array = buffer.array();

			// Calculates the offset of the first byte
			int offset = buffer.readerIndex() + buffer.arrayOffset();

			// Readable bytes
			int length = buffer.readableBytes();
			printBuffer(array, offset, length);
		}
	}

	/**
	 * Print the Buffer information
	 * 
	 * @param buffer
	 */
	private static void printBuffer(byte[] array, int offset, int len) {
		System.out.println("array: " + array);
		System.out.println("array->String: " + new String(array));
		System.out.println("offset: " + offset);
		System.out.println("len: " + len);
	}
}
Copy code

Output results:

array: [B@5b37e0d2
array->String: waylau    
offset: 0
len: 6

Process finished with exit code 0
 Copy code

Direct buffer mode

The direct buffer belongs to the direct memory allocated outside the heap and will not occupy the space of the heap.

Advantages: when using socket to transmit data, the performance is very good, avoiding the process of copying data from JVM heap memory to direct buffer, and improving the performance.

Disadvantages: compared with heap buffers, direct buffers are more expensive to allocate memory space and free up.

For reading and writing involving a large amount of I/O data, it is recommended to use direct buffer. For the back-end business message encoding and decoding module, it is recommended to use heap buffer.

The following is an example of direct buffer Code:

public class ByteBufDirectBufferDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		// Create a direct buffer
		ByteBuf buffer = Unpooled.directBuffer(10);
		String s = "waylau";
		buffer.writeBytes(s.getBytes());

		// Check whether it is a support array
		// If it is not a supporting array, it is a direct buffer
		if (!buffer.hasArray()) {

			// Calculates the offset of the first byte
			int offset = buffer.readerIndex();

			// Readable bytes
			int length = buffer.readableBytes();

			// Get byte content
			byte[] array = new byte[length];
			buffer.getBytes(offset, array);

			printBuffer(array, offset, length);
		}
	}

	/**
	 * Print the Buffer information
	 * 
	 * @param buffer
	 */
	private static void printBuffer(byte[] array, int offset, int len) {
		System.out.println("array: " + array);
		System.out.println("array->String: " + new String(array));
		System.out.println("offset: " + offset);
		System.out.println("len: " + len);
	}
}
Copy code

Output results:

array: [B@6d5380c2
array->String: waylau
offset: 0
len: 6

Process finished with exit code 0
 Copy code

Compound buffer mode

Composite buffers are Netty specific buffers. Essentially, it is similar to providing a combined view of one or more bytebufs. Different types of bytebufs can be added and deleted as needed.

Advantages: it provides an access method, allowing users to freely combine multiple bytebufs, avoiding copying and allocating new buffers.

Disadvantages: access to its supporting array is not supported. Therefore, if you want to access, you need to copy the contents into heap memory before accessing.

The following example is a composite buffer, which combines a heap buffer and a direct buffer without any replication process, and only creates a view.

public class ByteBufCompositeBufferDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		// Create a heap buffer
		ByteBuf heapBuf = Unpooled.buffer(3);
		String way = "way";
		heapBuf.writeBytes(way.getBytes());

		// Create a direct buffer
		ByteBuf directBuf = Unpooled.directBuffer(3);
		String lau = "lau";
		directBuf.writeBytes(lau.getBytes());

		// Create a composite buffer
		CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer(10);
		compositeBuffer.addComponents(heapBuf, directBuf); // Add buffer to match buffer

		// Check whether it is a support array
		// If it is not a supporting array, it is a composite buffer
		if (!compositeBuffer.hasArray()) {

			for (ByteBuf buffer : compositeBuffer) {
				// Calculates the offset of the first byte
				int offset = buffer.readerIndex();

				// Readable bytes
				int length = buffer.readableBytes();

				// Get byte content
				byte[] array = new byte[length];
				buffer.getBytes(offset, array);

				printBuffer(array, offset, length);
			}

		}
	}

	/**
	 * Print the Buffer information
	 * 
	 * @param buffer
	 */
	private static void printBuffer(byte[] array, int offset, int len) {
		System.out.println("array: " + array);
		System.out.println("array->String: " + new String(array));
		System.out.println("offset: " + offset);
		System.out.println("len: " + len);
	}
}
Copy code

Output results:

array: [B@4d76f3f8
array->String: way
offset: 0
len: 3
array: [B@2d8e6db6
array->String: lau
offset: 0
len: 3

Process finished with exit code 0
 Copy code

CompositeByteBuf is a virtual buffer used to display multiple buffers as a single merge buffer, similar to views in a database.

summary

Through the above introduction to ByteBuf, I believe our friends have a certain understanding of the principle of ByteBuf. In the next section, we continue to delve into the source code of Netty.

ending

I am a yard farmer who is being hit and still trying to move forward. If the article is helpful to you, remember to praise and pay attention, thank you!


Author: first love
Link: https://juejin.cn/post/6995058704714301476
Source: Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.

Keywords: Java

Added by colinexl on Sun, 26 Dec 2021 23:21:28 +0200