March 01, 2020

Java NIO Interview Questions Answers

NIO or New I/O is the second I/O system, it provides the different way of working with I/O than the standard I/O API's. It is an alternative IO API for Java (from Java 1.4).

Difference between Blocking and Non-blocking I/O
  • Blocking IO wait for the data to be write or read before returning. Java IO's various streams are blocking. It means when the thread invoke a write() or read(), then the thread is blocked until there is some data available for read, or the data is fully written.
  • Non blocking IO does not wait for the data to be read or write before returning. Java NIO non- blocking mode allows the thread to request writing data to a channel, but not wait for it to be fully written. The thread is allowed to go on and do something else in a mean time.

Difference between Standard IO and NIO
  • Standard IO is based on streams and NIO is buffer oriented. Buffer oriented operations provide us the flexibility in handling data. In buffer oriented NIO, data is first read into a buffer and then it is made available for processing (asynchronous processing). So we can move back and forth in the buffer, which is not possible in the case of streams it is not possible.
  • In case of streams, a thread will be blocked until it completes the IO operation, wherein the NIO allows for non-blocking operations. If the data is not available for IO operations, then the thread can do something else and need not stay in blocked mode. With channels and selectors a single thread can manage multiple threads and parallel IO operations.
Fundamental Components of Java NIO
  • Channels Buffers: In standard I/O API the character streams and byte streams are used. In NIO we work with channels and buffers. Data is always written from a buffer to a channel and read from a channel to a buffer.
  • Selectors: It is an object that can be used for monitoring the multiple channels for events like data arrived, connection opened etc. Therefore single thread can monitor the multiple channels for data.
  • Non-blocking I/O: Here the application returns immediately whatever the data available and application should have pooling mechanism to find out when more data is ready.
Channels
  • Channels are used for I/O transfers in Java NIO. 
  • A channel is a like a tube that transports data between a buffer and an entity at other end. 
  • A channel reads data from an entity and places it in buffer for consumption.
  • Channels act as gateway provided by java NIO to access the I/O mechanism.
  • Unlike streams, channels are two-way, it can both read and write.
  • Channel reads data into a buffer and writes data from a buffer. It can do asynchronous read and write operations.
  • Channels can be on blocking or non-blocking modes.
  • Non-blocking channel does not put the invoking thread in sleep mode. Stream-oriented channels like sockets only can be placed in non-blocking mode.
  • Data can be transferred from Channel to Channel if any one of them is a FileChannel.
Buffers
  • Previously data is read directly from a stream or written directly into it. Now the data is read from a buffer or written into it.
  • Buffers are the endpoints provided by channels to send and receive data. We can say a buffer is a block of data that is to be written to a channel or read from a channel.
  • Buffer is an object that holds data and acts as an endpoint in a NIO channel.
  • Buffers are the basic building blocks of Java NIO. They are endpoints of Channels.
  • By default they are not thread-safe.
  • Buffers provides a fixed size container to read and write data.
  • Every buffer is readable, but only chosen buffers are writable.
  • In a read-only buffer content is not mutable but, its mark, position and limit are mutable.
Java NIO Channel Classes
Java NIO package provided two major types of Channels classes:
  • FileChannel: These are File based read/write channels that cannot be placed on nonblocking mode. Object of file channel can be created only by calling the getChannel() method on file object as we can't create file object directly.
  • SocketChannel: These are selectable channels that can operate in nonblocking mode. There are three socket channel types namely, SocketChannel, ServerSocketChannel and DatagramChannel.
    • DatagramChannel-The datagram channel can read and write the data over the network via UDP (User Datagram Protocol).Object of DataGramchannel can be created using factory methods.
    • SocketChannel-The SocketChannel channel can read and write the data over the network via TCP (Transmission Control Protocol). It also uses the factory methods for creating the new object.
    • ServerSocketChannel-The ServerSocketChannel read and write the data over TCP connections, same as a web server. For every incoming connection a SocketChannel is created.
What is the difference between TCP and UDP protocol?
1). TCP(Transmission control Protocol) is connection oriented, while UDP(User Datagram Protocol) is connection less. This means a connection is established between client and server before they can send data.

UDP is a connectionless protocol, and point to point connection is not established before sending messages. That's why UDP is more suitable for multicast distribution of the message, one to many distributions of data in single transmission.

In case of TCP the client (which is the initiator of TCP connection) sends SYN message to the server, which is listening on a TCP port. The server receives and sends an SYN-ACK message, which is received by client again and responded using ACK. Once the server receives this ACK message, TCP connection is established and ready for data transmission.

2). TCP provides the delivery guarantee. If a message is lost in transits then its recovered using resending, which is handled by TCP protocol itself. Where as UDP doesn't provide any delivery guarantee. A datagram package may be lost in transits. That's why UDP is not suitable for programs which require guaranteed delivery.

3). TCP also guarantees the order of message. The message will be delivered to the client in the same order, the server has sent, though it's possible they may reach out of order to the other end of the network. TCP protocol will do all sequencing and ordering for you. 

UDP doesn't provide any ordering or sequencing guarantee. Datagram packets may arrive in any order. That's why TCP is suitable for application which needs delivery in a sequenced manner, though there is UDP based protocol as well which provides ordering and reliability by using sequence number and redelivery e.g. TIBCO Rendezvous, which is actually a UDP based application.

4). TCP is slower compared to UDP.

5). Data boundary is not preserved in TCP, but UDP preserves it. 

In TCP, data is sent as a byte stream, and no distinguishing indications are transmitted to signal message (segment) boundaries. On UDP, Packets are sent individually and are checked for integrity only if they arrived. Packets have definite boundaries which are honored upon receipt, meaning a read operation at the receiver socket will yield an entire message as it was originally sent. Though TCP will also deliver the complete message after assembling all bytes. Messages are stored on TCP buffers before sending to make optimum use of network bandwidth.

Buffer Types
Java NIO package provided a buffer type for each primitive type. All the buffer classes implement the Buffer interface. 
  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • MappedByteBuffer
Buffer Capacity
  • Buffer is a fixed size type and the maximum fixed size is called capacity of the buffer. 
  • Once the buffer is full it should be cleared before writing to it. 
  • Once the capacity is defined it never changes in its lifetime.
Buffer Limit
  • On write mode, limit is equal to the capacity of the buffer. 
  • On read mode, limit is one past the last filled index of the buffer. 
  • When the buffer is being written, the limit keeps incrementing. 
  • Limit of a buffer is always greater than or equal to zero and less than or equal to capacity (0 < = limit < = capacity).
Buffer Position
  • It is the current location in buffer. 
  • Position is always set between zero and the limit of the buffer. When the buffer is created, position is set to zero. 
  • On write or read, the position is incremented to next index. 
Buffer mark() 
  • Buffer Mark is like setting a bookmark of the position in a buffer. 
  • When we mark() method is called, the current position is recorded and when reset() is called the marked position is restored.
Buffer flip()
  • flip() method is used to prepare a buffer for get operation or makes it ready for a new sequence of write. 
  • flip() sets the limit to the current position and then position to zero.
Buffer clear()
  • clear() sets the limit to the capacity and position to zero.
  • clear() method is used to prepare a buffer for put operation or makes it ready for new sequence of read. 
Buffer rewind()
  • rewind() sets the buffer position to zero.
  • rewind() method is used to read again the data that it already contains. 
How to write to NIO Buffer?
  • Create a buffer by allocating a size. Buffer has a method allocate(size) and returns a Buffer instance. ByteBuffer byteBuffer = ByteBuffer.allocate(512);
  • Put data into buffer, byteBuffer.put((byte) 0xfff);
How to read from NIO Buffer?
  • Create a buffer by allocating a size. Buffer has a method allocate(size) and returns a Buffer instance. ByteBuffer byteBuffer = ByteBuffer.allocate(512);
  • Flip the buffer to prepare for read operation, byteBuffer.flip();
  • Then the buffer can be read into, int numberOfBytes = fileChannel.read(byteBuffer);
  • Read from the buffer, char c = (char)byteBuffer.get();
Java NIO Selectors
  • A selector is an object that can be used for monitoring the multiple channels for events like data arrived, connection opened etc. Thus, a single thread can be used for managing multiple channels, and thus multiple network connections.
  • If the application has many channels/ connections open, but has low traffic on each connection (e.g in chat window), selectors can be used.
  • Context-switching between threads is expensive for the operating system, and additionally, each thread takes up memory.
How to create a Selector?
A selector may be created by invoking the static open() method of the Selector class, which will use the system’s default selector provider to create a new selector.
Selector selector = Selector.open();

Registering Selectable Channels
  • In order for a selector to monitor any channels, we must register these channels with the selector. We do this by invoking the register method of the selectable channel.
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
  • But before a channel is registered with a selector, it must be in non-blocking mode. This means that we cannot use FileChannels with a selector since they cannot be switched into non-blocking mode the way we do with socket channels.
  • The first parameter is the Selector object, the second parameter defines an interest set, meaning what events we are interested in listening for in the monitored channel via the selector.
There are four different events we can listen for, each is represented by a constant in the SelectionKey class:
  • SelectionKey.OP_CONNECT: When a client attempts to connect to the server  
  • SelectionKey.OP_ACCEPT: When the server accepts a connection from a client.
  • SelectionKey.OP_READ: When the server is ready to read from the channel.
  • SelectionKey.OP_WRITE: When the server is ready to write to the channel.
SelectionKey Object
  • We get a SelectionKey object after registering a channel with a selector. 
  • The SelectionKey object holds data representing the registration of the channel.
Selection Key Methods
  • attach() is used to attach an object with the key.The main purpose of attaching an object to a channel is to recognizing the same channel.
  • attachment() is used to retain the attached object from the channel.
  • channel() is used to get the channel for which the particular key is created.
  • selector() is used to get the selector for which the particular key is created.
  • isValid() returns weather the key is valid or not.
  • isReadable() states that weather key's channel is ready for read or not.
  • isWritable() states that weather key's channel is ready for write or not.
  • isAcceptable() states that weather key's channel is ready for accepting incoming connection or not.
  • isConnectable() tests whether this key's channel has either finished, or failed to finish, its socket-connection operation.
  • isAcceptable() tests whether this key's channel is ready to accept a new socket connection.
  • interestOps() retrieves this key's interest set.
  • readyOps() retrieves the ready set which is the set of operations the channel is ready for.
We can select a channel from selector by calling its static method select(). Select method of selector is overloaded as:
  • select() blocks the current thread until at least one channel is ready for the events it is registered for.
  • select(long timeout) does the same as select() except it blocks the thread for a maximum of timeout milliseconds.
  • selectNow() doesn't block at all. It returns immediately with whatever channels are ready.
Also in order to leave a blocked thread which call out select method, wakeup() method can be called from selector instance after which the thread waiting inside select() will then return immediately.

We can close the selector by calling close() method which also invalidates all SelectionKey instances registered with this Selector along with closing the selector.

Interest Set
  • It is an integer value, which defines the set of events that we want the selector to watch out for on this channel.
  • First, we have the interest set returned by the SelectionKey‘s interestOps method. Then we have the event constant in SelectionKey we looked at earlier. When we AND these two values, we get a boolean value that tells us whether the event is being watched for or not.
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;

Ready Set
  • It is an integer value as well, which defines the set of events that the channel is ready for. 
  • We can get the ready set from SelectionKey‘s readyOps method. When we AND this value with the events constants , we get a boolean representing whether the channel is ready for a particular value or not.
  • Another alternative and shorter way to do this is to use SelectionKey’s convenience methods for this same purpose, e.g : selectionKey.isAcceptable(), selectionKey.isConnectable(), selectionKey.isReadable(), selectionKey.isWriteable().
Getting Channel from SelectionKey 
  • We can access the channel being watched from the SelectionKey object by calling channel() method, Channel channel = selectionKey.channel();
Getting Selector from SelectionKey
  • We can obtain the Selector object from the SelectionKey object from the selector() method, Selector selector = key.selector();
Attaching Objects to a SelectionKey
  • Sometimes we may want to give a channel a custom ID or attach any kind of Java object we may want to keep track of. We can do this by attaching an object to a SelectionKey. This is done by calling attach method, selectionKey.attach(Object);
  • Alternatively, we can choose to attach an object during channel registration. We add it as a third parameter to channel’s register method, like so: SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_ACCEPT, object);
Getting the attached Object from SelectionKey
  • We can do this by calling attachment() method, Object object = selectionKey.attachment();
Scatter and Gather

  • Java NIO provides provides the support to read/write data from/to multiple buffers to channel. 
  • This multiple read and write support is termed as Scatter and Gather in which data is scattered to multiple buffers from single channel in case of read data while data is gathered from multiple buffers to single channel in case of write data.
  • Scatter / gather can be really useful in situations where you need to work with various parts of the transmitted data separately. For instance, if a message consists of a header and a body, you might keep the header and body in separate buffers. Doing so may make it easier for you to work with header and body separately.
  • In order to achieve this multiple read and write from channel there is ScatteringByteChannel and GatheringByteChannel.

Scattering Reads Example

  • In this we made to reads data from a single channel into multiple buffers.For this multiple buffers are allocated and are added to a buffer type array. Then this array is passed as parameter to the ScatteringByteChannel read() method which then writes data from the channel in the sequence the buffers occur in the array. Once a buffer is full, the channel moves on to fill the next buffer.
  • The fact that scattering reads fill up one buffer before moving on to the next, means that it is not suited for dynamically sized message parts. In other words, if you have a header and a body, and the header is fixed size (e.g. 128 bytes), then a scattering read works fine.

The following example shows how scattering of data is performed in Java NIO:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

//read data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

Gathering Writes Example

  • Multiple buffers are allocated and are added to a buffer type array. Then this array is passed as parameter to the GatheringByteChannel write() method which then writes data from the multiple buffers in the sequence the buffers occur in the array. 
  • Only the data between the position and the limit of the buffers are written. Thus, if a buffer has a capacity of 128 bytes, but only contains 58 bytes, only 58 bytes are written from that buffer to the channel. Thus, a gathering write works fine with dynamically sized message parts, in contrast to scattering reads.

Here is a code example that shows how to perform a gathering write:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

Java NIO Pipe

  • Pipe is used to write and read data between two threads. 
  • It mainly consist of two channels which are responsible for data propagation. Among two constituent channels one is called as Sink channel, which is mainly for writing data and other one is Source channel whose main purpose is to read data from Sink channel.
  • To ensure that data must be read in a same order in which it is written to the Pipe, the data synchronization is kept in order during data writing and reading.
  • It is a unidirectional flow of data in Pipe i.e data is written in Sink channel only and could only be read from Source channel.

In Java NIO pipe is defined as a abstract class with mainly three methods out of which two are abstract.

  • open() is used get an instance of Pipe or we can say pipe is created by calling out this method.
  • sink() returns the Pipe's sink channel which is used to write data by calling its write method.
  • source() method returns the Pipe's source channel which is used to read data by calling its read method.

-K Himaanshu Shuklaa..

No comments:

Post a Comment