The article directories
-
-
- 1. Overview
- 2. Channel
- 3. Buffer
- 4. Scater, Gather
- 5. Selector
- 6. Reference
-
1. Overview
NIO in Java consists of three core concepts:
- Channels:
- -Dan: This is my Buffers.
- Selectors: indicates the Selectors
There are, of course, many other classes and components in NIO, but the three above are at the heart of all the other implementations, which are more like utility classes that glue Channels, Buffers, and Selectors together. A Channel can be regarded as a stream. The program can read data from a Channel and write it to a Buffer, or read data from a Buffer and write it to a Channel.
The main types of channels in NIO are network IO and file IO, and the corresponding implementation classes are as follows:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
The main implementations of Buffer in NIO are:
- ByteBuffer
- CharBuffer
- DoubleBUffer
- FloatBuffer
- IntBuffer
- LongBUffer
- ShortBuffer
It provides implementation classes for different types of data in Java.
A Selector allows a single line to process multiple channels at the same time. It is mainly used when an application has multiple connections, but each connection transmits very little data. Using a Selector can reduce resource consumption and improve processing efficiency. For example, here is a Selector for three channels:
Selector can manage all registered channels by calling the Select method, which blocks until an event occurs for one of the channels. When the event is returned, the corresponding thread of Selector can process the returned event.
2. Channel
A Channel is a Stream. A Channel is a Stream.
- A Channel is bidirectional. Data can be written to or read from a Channel. Streams are usually one-way, such as an input stream or an output stream
- A Channel can read and write data asynchronously
- A Channel is usually used with a Buffer to read and write data
Common implementation classes for channels are:
- FileChannel: Reads data from a file
- DatagramChannel: Supports reading and writing data over UDP
- SocketChannel: Reads and writes data over TCP
- ServerSocketChannel: TCP connection that supports listening for connections. Each connection corresponds to a SocketChannel
For example, we use FileChannel to read the contents of a file as follows:
/ * * *@Author dyliang
* @Date2020/10/25 and *@Version1.0 * /
public class ChannelDemo {
public static void main(String[] args) throws IOException {
RandomAccessFile file = new RandomAccessFile("test.txt"."rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = channel.read(buffer);
while(bytesRead ! = -1){
System.out.println("Read " + bytesRead);
buffer.flip();
while(buffer.hasRemaining()){
System.out.println((char) buffer.get()); } buffer.clear(); bytesRead = channel.read(buffer); System.out.println(bytesRead); } file.close(); }}Copy the code
The above example implements data transfer between Buffer and Channel. In addition, Channel also provides transferTo methods and transferFrom methods for direct data transfer between channels. Such as:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt"."rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt"."rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel, position, count);
Copy the code
In addition, some SocketChannel implementations may only transmit data that the SocketChannel has already prepared in its internal buffer, even though the SocketChannel may have more data available later. Therefore, it may not transfer all of the requested data (count) from SocketChannel to FileChannel.
The transferTo method is used in the same way as the transferFrom method above, except that the form of the method call is different:
fromChannel.transferTo(position, count, toChannel);
Copy the code
Similarly, when SocketChannel calls the transferTo method, it can only transfer data that matches its buffer capacity.
3. Buffer
A Buffer is usually used in conjunction with a Channel. Data can be written from a Buffer to a Channel, and data can be written from a Channel to a Buffer. A Buffer is essentially a piece of memory that can read and write data. It is wrapped as a Buffer object in NIO, and provides a series of methods that make it easy for programs to manipulate this part of memory.
There are four main steps to read and write data through Buffer:
- Writes data to Buffer
- call
buffer.flip()
methods - Read data from Buffer
- call
buffer.clear()
orbuffer.compact()
To reclaim buffer space
As the program writes data to Buffer, Buffer keeps track of how much data was written. When you need to read data from the Buffer, you call the flip method to convert the Buffer from write mode to read mode, and then you can read all the data in the Buffer. In addition, once the data has been read, the program needs to clear the buffer for subsequent writing of other data. NIO provides two ways to do this:
- The clear method clears the entire buffer space
- Compact: The compact method clears only the memory occupied by the data that has already been read. The remaining data that has not been read is moved to the start of the buffer, and subsequent data written to it is saved after it
Such as:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = channel.read(buffer); // Write data to buffer
while(bytesRead ! = -1){
System.out.println("Read " + bytesRead);
buffer.flip(); // Switch to read mode
while(buffer.hasRemaining()){
System.out.println((char) buffer.get()); // Get data
}
buffer.clear(); // Clear the buffer space
}
Copy the code
A Buffer is essentially a block of memory that can be read and written repeatedly. To understand how it uses memory for reading and writing, you need to understand the following three concepts:
- Capacity: indicates the total length of the buffer. If the buffer is full and data needs to be written, clear the buffer before writing data
- position: position, the position of the next data element to be manipulated. The starting position is 0 and moves backwards as data is written, up to a maximum
capacity - 1
. When data is read from buffer, position is reset back to 0, recording the position from which data is to be read - limit: The position of the next non-operable element in the buffer, used to limit the amount of data that can be written or read by a program
limit <=capacity
The amount of data read cannot exceed the amount of data written.
Common implementations of Buffer are:
- ByteBuffer
- CharBuffer
- DoubleBUffer
- FloatBuffer
- IntBuffer
- LongBUffer
- ShortBuffer
You can choose the corresponding implementation for different types of data. The common methods of Buffer are:
The method name | describe |
---|---|
allocate | Buf = bytebuffer.allocate (48); allocate(48); allocate(48). |
Writes data to Buffer | Nio provides two ways to write data to buffer: – Write data from a Channel, e.g. Int bytesRead = inchannel.read (buf) – self-write: buf.put(11) |
flip | Convert Buffer from write mode to read mode, reset position back to 0, and limit marks the amount of data written |
Read data from Buffer | Nio provides two methods to read data from Buffer: – Read data write Channel: int WriteCount = channel.write(buf) – Self read: buf.get() |
rewind | Used to reset position to 0 to facilitate re-reading of data |
clear | Clear the Buffer memory space |
compact | It only emptens the memory space occupied by data that has already been read. The remaining data that has not been read is moved to the start of the buffer, and subsequent data written to it is saved after it |
mark | Marks a given position in the buffer |
reset | Resets the position back to the position of the marker |
equals | Compare the contents of two buffers. The contents of two buffers must meet the following requirements: – Same type – Same amount of remaining data – Same amount of remaining data |
compareTo | In lexicographic order, it returns a negative integer, 0, and positive integer, respectively, if the buffer arguments are less than, equal to, or greater than the object instance referencing compareTo() |
When the clear method and compact method clear the Buffer, it does not mean that the data in the Buffer is cleared. The original data is effectively forgotten, and the program cannot read the data from the buffer again. Subsequent new data is written directly over the old data. This refers to another concept, mark, which is used to record the position preceding the current position or -1 by default. When the clear method is called to clear the buffer, it actually does the following:
public final Buffer clear(a) {
position = 0; // Set position to 0 to write data directly from the beginning next time
limit = capacity; // Limit is the same as capacity
mark = -1; // set mark to -1
return this;
}
Copy the code
4. Scater, Gather
Nio provides support for Scatter and Gather, which are used to write data to or read data from a Channel, for scenarios where multiple parts of data need to be processed separately. Scatter is used to write data from a Channel to multiple Buffers. Gather is used to write data from multiple buffers to a Channel.
Scatter reads data from a single Channel into multiple buffers, as shown below:
For example, write data from a Channel to two Bytebuffers:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
Copy the code
Scatter writes data to the buffers in array order. When the buffers are full, it moves to another buffer to perform the writes. It is not suitable for dynamically adjusting the size of the data to be written.
The Gather is used to write data from multiple buffers to a Channel, as shown below:
Such as:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
Copy the code
At this point, only data is written between the position of the buffer and the limit. For example, if the buffer has a size of 128 bytes but contains only 58 bytes, only 58 bytes are written from that buffer to a Channel.
5. Selector
Selector supports a single thread to manage multiple channels. It is used to detect which managed channels are ready to perform reads or writes, providing a mechanism for IO multiplexing. If channels are not managed using Selector, then each Channel needs a separate thread. Threads require a certain amount of resources, and thread context switching is expensive. As a result, Selector reduces the use of threads by supporting a single thread to manage multiple channels, taking advantage of the multi-core capabilities of hardware and thereby reducing the performance overhead.
Now, let’s go through the program and see how we use Selector. First, create a Selector:
Selector selector = Selector.open();
Copy the code
Call the register method to register the Channel to be managed with the Selector:
channel.configureBlocking(false); // Channel must be of non-blocking type
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
Copy the code
The last parameter to the register method is the Interest set, which indicates that the program wants to listen for the type of event of interest occurring in the Channel via Selector. It has four values:
- Connect
- Accept
- Read
- Write
If a Channel successfully connects to the server, it is called Connect Ready; If the server socket channel receives a connection, it is called Accept Ready. If a Channel is ready to read data, it is said to be read ready. If a Channel is ready to write data, it is called write ready. The four values of SelectionKey represent the four events described above:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
If you are interested in a variety of types of events, you can use |. The register method returns a SelectionKey object, which contains the following information:
- interest set
- ready set
- Channel
- Selector
- an attached object
Where, interest set is the event bound to be monitored when the register method is called. The corresponding event label can be obtained through interestOps method, as shown below:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
Copy the code
In the definition of SelectionKey, different events are represented by different numbers, so it is possible to determine which event is monitored by and operation.
public abstract class SelectionKey {
public static final int OP_READ = 1;
public static final int OP_WRITE = 4;
public static final int OP_CONNECT = 8;
public static final int OP_ACCEPT = 16;
private volatile Object attachment = null;
/ / to omit
}
Copy the code
A ready set denotes the type of operation a Channel is ready to perform, which is obtained by calling the readyOps method:
int readySet = selectionKey.readyOps();
Copy the code
There are four ways to determine what type of operation a Channel is ready to perform.
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Copy the code
In addition, you can use methods to get the corresponding Selector and Channel.
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
Copy the code
Attach Object is an object object that is used to attach something extra to the SelectionKey, such as a Buffer to use with the Channel, or whatever else you want to attach.
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
Copy the code
Alternatively, in the previous register method you can also operate by passing an attach object as a parameter.
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
Copy the code
Once multiple channels have been registered with Selector, and events of interest for each Channel have been listened for, the select method can be called. These channels are returned when a listener event occurs in one of the registered channels. Where, the select method has the following three forms:
int select()
The: method blocks until at least one Channel is ready for the corresponding event operationint select(long timeOut)
: Similar to the above, but with a maximum wait time instead of blocking all the timeint selectNow()
: Returns immediately if no Channel is found, without blocking
The return value represents the number of channels that are ready.
Once one or more channels have been returned via the Select method, the selectKeys method can be called to get the corresponding collection of Selectionkeys.
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Copy the code
These prepared channels can be retrieved by iterating through the resulting collection.
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// Get the corresponding channel through selectionkey.channel ()
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();
}
Copy the code
Note the keyiterator.remove method called at the end of each iteration. Selector does not remove the SelectionKey instance from the collection. When a Channel is processed, it must be removed manually. The next time a Channel becomes ready, selecting Selector will add it to the collection again.
Because the single thread managing the Selector will block all the time if all the channels are not ready. If you want the thread to wakeUp immediately, you can do this by calling Selector. WakeUp by another thread. If another thread calls the wakeUp method and there is no currently blocking thread in the select method, the next thread that calls the select method will wakeUp immediately. Once a Selector is used, you can call Selector’s close method, and all registered instances of SelectionKey are invalidated, but the managed Channel is not closed.
6. Reference
Java NIO Overview
Java NIO Channel
Java NIO Buffer
Java NIO Scatter / Gather
Java NIO Channel to Channel Transfers
Java NIO Selector