Buffer

A Buffer is essentially a block of memory from which data can be written and then retrieved.

Java.nio defines the following Buffer implementations, which readers have probably seen in many places.

The core is the final ByteBuffer. The previous list of classes just wraps it up, and it’s usually the ByteBuffer we use the most.

We should think of Buffer as an array. IntBuffer, CharBuffer, DoubleBuffer, etc. correspond to int[], char[], double[], etc.

MappedByteBuffer, used to implement memory-mapped files, is also not the focus of this article.

I think of buffers as similar to arrays and class sets, except that most of the time we use them in NIO scenarios. Here are some important properties and methods in Buffer.

Position, limit, capacity

Just as arrays have array capacities and subscripts are specified for each element accessed, buffers have several important properties: position, limit, and Capacity.

 

Capacity, of course, represents the capacity of the buffer, which cannot be changed once it is set. For example, IntBuffer (capacity: 1024) can hold 1024 int values at a time. Once the Buffer reaches capacity, the Buffer needs to be emptied before the value can be rewritten.

Position and limit change, so let’s look at how they change for read and write operations.

The initial value of position is 0, and each time a value is written to the Buffer, position automatically increments by 1, representing the position of the next write. Similarly for read operations, position is automatically incremented by 1 for each value read.

When you switch from write mode to read mode (flip), position returns to zero, allowing you to start reading and writing from scratch.

Limit: In write mode, the limit indicates the maximum number of data that can be written. In this case, the limit is equal to capacity. After the write is complete, the Buffer is switched to read mode, where the limit is equal to the actual size of the Buffer, because the Buffer may not be full.

Initialize the Buffer

Each Buffer implementation class provides a static method allocate(int capacity) to help us quickly instantiate a Buffer. Such as:


ByteBuffer byteBuf = ByteBuffer.allocate(1024);

IntBuffer intBuf = IntBuffer.allocate(1024);

LongBuffer longBuf = LongBuffer.allocate(1024);

 

In addition, we often use the wrap method to initialize a Buffer.



public static ByteBuffer wrap(byte[] array) {

.

}

 

Fill the Buffer

Each Buffer class provides several put methods to fill buffers, such as the several put methods in ByteBuffer:

?




// Fill in a byte value

public abstract ByteBuffer put(byte b);

// Fill an int at the specified position

public abstract ByteBuffer put(int index, byte b); 

// Fill in an array of values

public final ByteBuffer put(byte[] src) {… }

public ByteBuffer put(byte[] src, int offset, int length) {… }

These methods need to control the size of Buffer, cannot exceed capacity, will more than Java nio. BufferOverflowException anomalies.

Another common operation for buffers is to fill the Buffer with data from a Channel. At the system level, this operation is called a read operation because the data is read from outside (a file or network, etc.) into memory.



int num = channel.read(buf);

The above method returns the size of the data read into the Buffer from the Channel.

Extracts the values in Buffer

The value of position is incremented by 1 for each value written to the Buffer, so the position ends up pointing to the position after the last value written to the Buffer. If the Buffer is full, So position is equal to capacity (position starts at 0).

If you want to read the values in Buffer, you need to switch mode from write mode to read mode. Note that when talking about NIO reads, we usually talk about reading data from a Channel to a Buffer, corresponding to writing to a Buffer. Beginners need to understand this.

The flip() method of Buffer switches from write mode to read mode by resetting position and limit.


    public final Buffer flip() {

    limit = position; 

// Set limit to the amount of data actually written

    position = 0

// Reset position to 0

mark = -1

// We’ll talk about it later

return this; 

}

Corresponding to a set of put methods for write operations, read operations provide a set of get() methods:



// Get data according to position

public abstract byte get(); 

// Get data at the specified location

public abstract byte get(int index); 

// Write the data in the Buffer to the array

public ByteBuffer get(byte[] dst)

Here is a frequently used method:



new String(buffer.array()).trim();

In addition to reading data from a Buffer and using it, more common operations are to write data to a Channel, such as writing data to a file via a FileChannel or writing data to a network for sending to a remote machine via a SocketChannel. The corresponding operation is called a write operation.



int num = channel.write(buf);

 

Mark (), the reset ()

In addition to the three basic attributes position, limit, and Capacity, another commonly used attribute is mark.

Mark is used to temporarily hold the value of position. Each call to the mark() method sets mark to the current position for later use.



public final Buffer mark() {

    mark = position;     

return this; 

}

So when do you use it? Consider the following scenario: mark() at position 5 and continue reading. When I get to 10, I want to go back to position 5 and start again. Position goes back to 5.



public final Buffer reset() {

    int m = mark;

    if (m < 0) 

          throw new InvalidMarkException();   

  position = m;     

return this; 

}

 

Rewind (), Clear (), compact()

Rewind () : Resets position to 0, usually used to read and write Buffer from scratch.


public final Buffer rewind() {

    position = 0;

    mark = -1;    

 return this; 

}

 

Clear () : equivalent to re-instantiation.

Normally, we fill the Buffer, read data from the Buffer, and then refill it with new data. We usually call clear() before filling.



public final Buffer clear() {

    position = 0;     

limit = capacity;     

mark = -1;     

return this; 

}

Compact () : Similar to clear() is called just before filling the Buffer with new data.

Clear () resets several properties, but does not clear the Buffer, but overwrites the previous data.

After the Compact () method is called, the data that has not yet been read, i.e. the data directly from position to limit, is moved to the left and then written on top of that. In this case,limit is still equal to Capacity, and position points to the right of the original data.

 

Channel

All NIO operations begin with channels, which are data sources or data writing destinations. Primarily, we’ll be concerned with the following channels implemented in the Java.nio package:

 

FileChannel: FileChannel for reading and writing files.

DatagramChannel: Used for receiving and sending UDP connections

SocketChannel: INDICATES the TCP client

ServerSocketChannel:TCP server that listens for incoming requests from a certain port.

A Channel is often translated as a Channel, similar to a stream in IO, used for reading and writing. It works with the Buffer described earlier by filling the Buffer with data from a Channel during read operations and writing data from a Buffer to a Channel during write operations.

 

 

 

FileChannel

 

Initialization:



FileInputStream inputStream = new FileInputStream(new File( "/data.txt"));

FileChannel fileChannel = inputStream.getChannel();

Of course, you can also get a FileChannel from getChannel in the RandomAccessFile class.

Read file contents:



ByteBuffer buffer = ByteBuffer.allocate(1024);

   int num = fileChannel.read(buffer);

Write file contents:


ByteBuffer buffer = ByteBuffer.allocate(1024);

Buffer.put (” Write something randomly into buffer “.getBytes());

// Switch Buffer to read buffer.flip();

while(buffer.hasRemaining()) {     

// Write the contents of Buffer to a file

fileChannel.write(buffer); 

}

 

SocketChannel

To open a TCP connection:


SocketChannel SocketChannel = SocketChannel. < br > open (new InetSocketAddress("127.0.0.1" .80));

Of course, the above line is equivalent to the following two lines:



// Open a channel

SocketChannel socketChannel = SocketChannel.open();

// Initiate a connection

SocketChannel. Connect (new InetSocketAddress (80) “127.0.0.1,”);

SocketChannel reads and writes are no different from FileChannel in that they operate on buffers.



// Read data

socketChannel.read(buffer);   

// Write data to the network connection

while(buffer.hasRemaining()) {

    socketChannel.write(buffer);   

}

ServerSocketChannel

ServerSocketChannel Listens for a machine port and manages TCP connections coming in from this port.



/ / instantiate

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 

// Listen on port 8080

serverSocketChannel.socket().bind(new InetSocketAddress(8080)); 

   while (true) {    

// Once a TCP connection enters, a SocketChannel is created for processing

 SocketChannel socketChannel = serverSocketChannel.accept(); 

  }

 

Here we see the second instantiation of SocketChannel.

A SocketChannel is more than just a TCP client. It represents a network channel that can be read and written.

ServerSocketChannel doesn’t deal with Buffer because it doesn’t actually process data. Once it receives a request, it instantiates a SocketChannel, and then it doesn’t care about the data passing through the channel. It continues to listen on the port waiting for the next connection.

DatagramChannel

Unlike TCP, DatagramChannel is a class that handles both the server and the client.

UDP is connectionless, without shaking hands with the other party, without notifying the other party, it can directly send the packet, as for whether it can be delivered, it does not know.

Listening port:



DatagramChannel channel = DatagramChannel.open();

channel.socket().bind(new InetSocketAddress(9090));

  ByteBuffer buf = ByteBuffer.allocate(48);

  channel.receive(buf);

Sending data:



String newData = "New String to write to file..."                     + System.currentTimeMillis();  

ByteBuffer buf = ByteBuffer.allocate(48);

buf.put(newData.getBytes()); 

buf.flip();  

  int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com" , 80));

 

Selector

Selector is built on a non-blocking basis, and that’s what you hear a lot about multiplexing in the Java world, is used to implement a thread managing multiple channels.

Open the Selector:



Selector selector = Selector.open();

Registers a Channel with a Selector. As we said, a Selector is built on non-blocking mode, so any Channel registered with a Selector must support non-blocking mode, FileChannel does not support non-blocking mode, Here we discuss the most common socketChannels and ServerSocketChannels.



// Set channels to non-blocking mode, as they are blocked by default

channel.configureBlocking(false); 

/ / register

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

 

The second int argument to the register method (using a binary marker bit) is used to indicate which events of interest you want to listen for:

Selectionkey. OP_READ: indicates 00000001. Data can be read in the channel

Selectionkey. OP_WRITE: indicates 00000100. Data can be written to the channel

Selectionkey. OP_CONNECT: indicates 00001000. The TCP connection is successfully established

Selectionkey. OP_ACCEPT: indicates 00010000, which accepts TCP connections

We can listen for multiple events in a Channel at the same time. For example, if we want to listen for ACCEPT and READ events, specify the binary value 000 10001, which is the decimal value 17.

The return value of the registration method is an instance of SelectionKey, which contains Channel and Selector information, as well as an information called Interest Set, which is the Set of events that we are interested in listening for.

Call the select() method to get channel information. Used to determine whether an event of interest has occurred.

Example:

 



Selector selector = Selector.open();

   channel.configureBlocking(false);

  SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

   while(true) {

// Determine if any events are ready

 int readyChannels = selector.select(); 

  if(readyChannels == 0continue                     ;   

/ / traverse

Set<SelectionKey> selectedKeys = selector.selectedKeys();

  Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); 

  while(keyIterator.hasNext()) { 

    SelectionKey key = keyIterator.next();   

    if(key.isAcceptable()) {   

      // a connection was accepted by a ServerSocketChannel.       

} else if (key.isConnectable()) {         

// a connection was established with a remote server.      

 } else if (key.isReadable()) { 

        // a channel is ready for reading       

} else if (key.isWritable()) {       

  // a channel is ready for writing     

}       

keyIterator.remove();   

} 

}

 

For Selector, you need to be familiar with the following methods:

select()

Calling this method copies the SelectionKey corresponding to the prepared channel since the last select into the Selected Set. If no channels are ready, this method blocks until at least one channel is ready.

 

selectNow()

The same function as select, except that if no channel is ready, this method returns 0 immediately.

select(long timeout)

Given the previous two, this should make a lot of sense. If no channel is ready, this method will wait for a while

wakeup()

This method is used to wake up threads waiting on select() and select(timeout). If wakeup() is called first and no threads are blocking on the SELECT, a subsequent SELECT () or select(timeout) will return immediately without blocking, and of course it will only work once.

 

 

adjustable

Using Buffer’s flip() method, you can switch from write mode to read mode. This method simply sets position and limit