Java nio usage sample sharing

  • 2020-04-01 03:11:36
  • OfStack

Java NIO(New Input/Output) -- the New Input/Output API package -- was introduced in J2SE 1.4 in 2002. The goal of Java NIO is to improve the performance of I/o-intensive tasks on the Java platform. Ten years later, many Java developers still don't know how to make the most of NIO, and even fewer are aware of the introduction of a newer input/output API (nio.2) in Java SE 7. The biggest contribution of NIO and nio.2 to the Java platform has been to improve the performance of one of the core components of Java application development: input/output processing. Neither of these packages works well, and they don't work well for all scenarios. If used correctly, Java NIO and nio.2 can significantly reduce the time spent on some commonly used I/O operations. These are the superpowers that NIO and NIO.2 have, and I'll show you five easy ways to use them in this article.

Change notification (because each event requires a listener)
Selectors and asynchronous IO: improve multiplexing with selectors
Channel - promise and reality
Memory mapping - good steel for the edge
Character encoding and searching
The background of the NIO

Why is an enhanced package that's been around for 10 years still the new I/O package for Java? The reason is that for most Java programmers, the basic I/O operations are competent. On a day-to-day basis, most Java developers don't need to learn NIO. Furthermore, NIO is more than just a performance improvement package. Instead, it is a collection of different functions related to Java I/O. NIO achieves performance improvements by "getting the performance of Java applications closer to the real thing," which means that NIO and nio.2 apis expose low-level access to system operations. The cost of NIO is that while it provides more control over I/O, it also requires more careful use and practice than basic I/O programming. Another feature of NIO is its focus on application expressiveness, which we'll see in the following exercise.

Start learning about NIO and nio.2

NIO's resources are numerous -- some of the links selected in resources. To learn about NIO and nio.2, the Java 2 SDK Standard Edition(SE) documentation and Java SE 7 documentation are indispensable. To use the code in this article, you'll need to use JDK 7 or later.

For many developers, the first time they encounter NIO is probably while maintaining an application: a functioning application is getting slower and slower, so some suggest using NIO to improve response times. NIO is great for improving application performance, but the results depend on the underlying system (note that NIO is platform dependent). If this is your first time using NIO, you need to weigh it carefully. You'll find that NIO's ability to improve performance depends not only on the OS, but also on the JVM you're using, the virtual context of the host, the nature of the bulk storage and even the data. Therefore, the work of performance measurement is more difficult to do. This is especially true if your system has a mobile deployment environment.

With that said, we have no further worries, so let's take a look at the five important features of NIO and nio. 2.

1. Change notification (because each event requires a listener)

A common concern for developers interested in NIO and nio.2 is the performance of Java applications. In my experience, the file change notifier in nio.2 is the most interesting (and underrated) feature in the new input/output API.

Many enterprise applications require special treatment in the following situations:

When a file is uploaded to an FTP folder
When a definition in a configuration is modified
When a draft document is uploaded
Other file system events occur
These are examples of change notifications or change responses. In early versions of Java (and other languages), polling was the best way to detect these change events. Polling is a special kind of infinite loop: check the file system or other object and compare it to its previous state, if nothing changes, then continue checking after a few hundred milliseconds or 10 seconds. So it goes on and on and on.

Nio.2 provides a better way to do change detection. Listing 1 is a simple example.

The change notification mechanism in listing 1. Nio. 2


import java.nio.file.attribute.*; 
importjava.io.*;
importjava.util.*;
importjava.nio.file.Path;
importjava.nio.file.Paths;
importjava.nio.file.StandardWatchEventKinds;
importjava.nio.file.WatchEvent;
importjava.nio.file.WatchKey;
importjava.nio.file.WatchService;
importjava.util.List;
publicclassWatcher{
publicstaticvoidmain(String[]args){
Paththis_dir=Paths.get(".");
System.out.println("Nowwatchingthecurrentdirectory...");
try{
WatchServicewatcher=this_dir.getFileSystem().newWatchService();
this_dir.register(watcher,StandardWatchEventKinds.ENTRY_CREATE);
WatchKeywatckKey=watcher.take();
List<WatchEvent<<64;>>events=watckKey.pollEvents();
for(WatchEventevent:events){
System.out.println("Someonejustcreatedthefile'"+event.context().toString()+"'.");
}
}catch(Exceptione){
System.out.println("Error:"+e.toString());
}
}
}

Compile this code and execute it on the command line. Create a new file in the same directory, for example, by running the touchexample or copywatcher.classexample commands. You will see the following change notification message:

Someonejustcreatethefiel 'example1'.


This simple example shows how to get started using JavaNIO's capabilities. It also introduces nio.2's Watcher class, which is more straightforward and easy to use than the original I/O polling scheme.

Pay attention to spelling mistakes

When you copy code from this article, watch out for spelling mistakes. For example, the StandardWatchEventKinds object in listing 1 is plural. Even the Java.net documentation misspelled it.

tip

The notification mechanism in NIO is simpler to use than the old polling method, which can induce you to ignore detailed analysis of specific requirements. When you first use a listener, you need to think carefully about the semantics of the concepts you are using. For example, knowing when a change will end is more important than knowing when it will start. This kind of analysis needs to be very careful, especially in common scenarios like moving FTP folders. NIO is a very powerful package, but it also has some subtle "traps" that can be confusing to those unfamiliar with it.

Selectors and asynchronous IO: improve multiplexing with selectors

NIO novices typically associate it with "non-blocking input/output." NIO is not just non-blocking I/O, but the perception is not entirely wrong: Java's basic I/O is blocking I/O -- meaning it waits until the operation is complete -- whereas non-blocking or asynchronous I/O is one of the most common features of NIO, not the whole NIO.

NIO's non-blocking I/O is event-driven and is shown in listing 1 in the file system listening example. This means that a selector (callback or listener) is defined for an I/O channel, and the program can continue to run. When an event occurs on the selector -- such as receiving a line of input -- the selector "wakes up" and executes. All of this is done with a single thread, which is significantly different from Java's standard I/O.

Listing 2 shows a multi-port network program called echo-er implemented using NIO's selector. Here is a modification of a small program created by GregTravis in 2003 (resources list). Unix and unix-like systems have long implemented efficient selectors, which are a good reference to the high-performance programming model of the Java network.

Listing 2. The NIO selector


importjava.io.*;
importjava.net.*;
importjava.nio.*;
importjava.nio.channels.*;
importjava.util.*;
publicclassMultiPortEcho
{
privateintports[];
privateByteBufferechoBuffer=ByteBuffer.allocate(1024);
publicMultiPortEcho(intports[])throwsIOException{
this.ports=ports;
configure_selector();
}
privatevoidconfigure_selector()throwsIOException{
//Createanewselector
Selectorselector=Selector.open();
//Openalisteneroneachport,andregistereachone
//withtheselector
for(inti=0;i<ports.length;++i){
ServerSocketChannelssc=ServerSocketChannel.open();
ssc.configureBlocking(false);
ServerSocketss=ssc.socket();
InetSocketAddressaddress=newInetSocketAddress(ports[i]);
ss.bind(address);
SelectionKeykey=ssc.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("Goingtolistenon"+ports[i]);
}
while(true){
intnum=selector.select();
SetselectedKeys=selector.selectedKeys();
Iteratorit=selectedKeys.iterator();
while(it.hasNext()){
SelectionKeykey=(SelectionKey)it.next();
if((key.readyOps()&SelectionKey.OP_ACCEPT)
==SelectionKey.OP_ACCEPT){
//Acceptthenewconnection
ServerSocketChannelssc=(ServerSocketChannel)key.channel();
SocketChannelsc=ssc.accept();
sc.configureBlocking(false);
//Addthenewconnectiontotheselector
SelectionKeynewKey=sc.register(selector,SelectionKey.OP_READ);
it.remove();
System.out.println("Gotconnectionfrom"+sc);
}elseif((key.readyOps()&SelectionKey.OP_READ)
==SelectionKey.OP_READ){
//Readthedata
SocketChannelsc=(SocketChannel)key.channel();
//Echodata
intbytesEchoed=0;
while(true){
echoBuffer.clear();
intnumber_of_bytes=sc.read(echoBuffer);
if(number_of_bytes<=0){
break;
}
echoBuffer.flip();
sc.write(echoBuffer);
bytesEchoed+=number_of_bytes;
}
System.out.println("Echoed"+bytesEchoed+"from"+sc);
it.remove();
}
}
}
}
staticpublicvoidmain(Stringargs[])throwsException{
if(args.length<=0){
System.err.println("Usage:javaMultiPortEchoport[portport...]");
System.exit(1);
}
intports[]=newint[args.length];
for(inti=0;i<args.length;++i){
ports[i]=Integer.parseInt(args[i]);
}
newMultiPortEcho(ports);
}
}

Compile the code, and then through like javaMultiPortEcho80058006 command to start it. Once the program runs successfully, launch a simple Telnet or other terminal emulator to connect the 8005 and 8006 interfaces. You'll see that the program echoes all the characters it receives -- and it does so through a Java thread.

3. Channels: promise and reality

In NIO, a channel can represent any object that can be read or written. Its purpose is to provide abstraction for files and jacket interfaces. NIO channels support a consistent set of methods, so that you don't have to worry about different objects when coding, whether it's standard output, a network connection, or the channel in use. This feature of the channel is a stream inherited from Java's basic I/O. Stream provides blocking IO; Channels support asynchronous I/O.

NIO is often recommended for its high performance, but more precisely for its responsiveness. In some cases NIO will perform worse than basic JavaI/O. For example, for a simple sequential read and write to a small file, the performance achieved simply by streaming might be two to three times faster than the corresponding event-oriented channel-based coding implementation. At the same time, a non-multiplex channel -- a separate channel per thread -- is much slower than multiple channels registering their selectors in the same thread.

Here are some questions to ask yourself as you consider whether to use a stream or a channel:

How many I/O objects do you need to read and write?
Do the different I/O objects have order directly, or do they all need to happen at the same time?
Does your I/O object need to last for a short period of time or does it exist for the entire declaration cycle of your process?
Is your I/O suitable for processing in a single thread or in several different threads?
Do network communications and local I/O look the same, or do they have different modes?
Such an analysis is a best practice for deciding whether to use streams or channels. Remember: NIO and nio.2 are not a replacement for basic I/O, but a complement to it.

4. Memory mapping -- good steel for the edge

The most significant performance improvement in NIO is memorymapping. Memory mapping is a system-level service that treats a segment of a file used in a program as memory.

Memory mapping has many potential impacts, more than I've provided here. At a higher level, it enables the performance of file access I/O to reach the speed of memory access. Memory access is often several orders of magnitude faster than file access. Listing 3 is a simple example of a NIO memory map.

Listing 3. Memory mapping in NIO


importjava.io.RandomAccessFile;
importjava.nio.MappedByteBuffer;
importjava.nio.channels.FileChannel;
publicclassmem_map_example{
privatestaticintmem_map_size=20*1024*1024;
privatestaticStringfn="example_memory_mapped_file.txt";
publicstaticvoidmain(String[]args)throwsException{
RandomAccessFilememoryMappedFile=newRandomAccessFile(fn,"rw");
//Mappingafileintomemory
MappedByteBufferout=memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE,0,mem_map_size);
//WritingintoMemoryMappedFile
for(inti=0;i<mem_map_size;i++){
out.put((byte)'A');
}
System.out.println("File'"+fn+"'isnow"+Integer.toString(mem_map_size)+"bytesfull.");
//Readfrommemory-mappedfile.
for(inti=0;i<30;i++){
System.out.print((char)out.get(i));
}
System.out.println("nReadingfrommemory-mappedfile'"+fn+"'iscomplete.");
}
}

In listing 3, this simple example creates A 20M file, example_memory_mapped_file.txt, populates it with the character A, and then reads the first 30 bytes. In practice, memory mapping is not only good at increasing the raw speed of I/O, it also allows multiple different readers and writers to work on the same file image at the same time. This technique is powerful and dangerous, but if used correctly, it can make your IO several times faster. Wall Street's trading operations are known to use memory-mapped technology in order to gain an advantage in seconds or even milliseconds.

5. Character encoding and search

The last NIO feature I'll cover in this article is charset, a package for converting different character encodings. Prior to NIO, Java implemented most of the same functionality with the getByte method built in. Charset is popular because it is more flexible than getBytes and can be implemented at a lower level for better performance. This is more valuable for searching for non-english languages that are sensitive to coding, order, and other language features.

Listing 4 shows an example of converting Unicode characters in Java to latin-1

Listing 4. Characters in NIO


Stringsome_string="ThisisastringthatJavanativelystoresasUnicode.";
Charsetlatin1_charset=Charset.forName("ISO-8859-1");
CharsetEncodelatin1_encoder=charset.newEncoder();
ByteBufferlatin1_bbuf=latin1_encoder.encode(CharBuffer.wrap(some_string));

Note that charsets and channels are designed to be used together so that programs can work properly when memory mapping, asynchronous I/O, and coding transformations work together.

Conclusion: there is certainly more to learn

The purpose of this article is to familiarize Java developers with some of the most important (and useful) features of NIO and nio.2. You can understand some of the other methods of NIO by building on some of the foundations of these examples; For example, what you've learned about channels can help you understand how NIO's Path handles symbolic links in the file system. You can also refer to the list of resources that I'll give you later, which contains some documentation for in-depth study of Java's new I/OAPI.


Related articles: