In the application, there are typically two types of calculations involved: CPU and I/O calculations. For most applications, the amount of time spent waiting on I/O is a large percentage. It is usually necessary to wait for a slower disk or network connection to complete the I/O request before continuing the CPU calculation task. Therefore, improving the efficiency of I/O operations is of great help to the performance of the application. This article describes the Java language in terms of I/O operations, including basic Java I/O and Java NIO, with a focus on basic concepts and best practices.
Flow
The Java language provides several different levels of concepts to abstract I/O operations. The earliest concepts in Java I/O were streams, including input and output streams, which existed as early as JDK 1.0. In simple terms, a stream is a sequential sequence of bytes. The input stream is used to read the sequence, and the output stream constructs the sequence. The basic units manipulated by InputStream and OutputStream are bytes. Each time a single byte or byte array is read and written. It is tedious to manipulate data types from a byte hierarchy. Basic byte streams can be packaged with easier-to-use stream implementations. If you want to read or output Java's basic data types, you can use DataInputStream and DataOutputStream. The methods they provide, such as readfloat and writedouble, make it easy to work with basic data types. If you want to read or write to objects in Java, you can use ObjectInputStream and ObjectOutputStream. Together with the serialization mechanism of objects, they enable persistence and data transfer of the state of Java objects. The basic stream provides less control over the input and output. InputStream only provides sequential read, skip partial byte, and token/reset support, while outputstream can only output sequentially.
Use of Streams
Because the entities that correspond to I/O operations are limited resources in the system, they need to be managed properly. Each open stream needs to be properly closed to release resources. The principle followed is who opens who releases. If a stream is used only within a method body, it is ensured that the stream is closed correctly before the method returns by using the finally statement or the Try-with-resources statement in JDK 7. If a method is simply a consumer of a stream, there is no need to consider the closing of the stream. The typical scenario is that the output stream in HttpServletResponse is not required to be closed in the servlet implementation. If your code needs to be responsible for opening a stream, and you need to pass between different objects, consider using the Execute Around method mode. As shown in the following code:
1 Public voidUse (streamuser user) {2InputStream input =NULL;3 Try {4input =open ();5 user.use (input);6}Catch(IOException e) {7 User.onerror (e);8}finally {9 if(Input! =NULL) {Ten Try { One input.close (); A}Catch(IOException e) { - User.onerror (e); - } the } - } -}
As seen in the code above, the specialized classes are responsible for the opening and closing of the stream. The user of the stream Streamuser does not need to be concerned about the details of the release of the resource, only the convection operation is required.
In the process of using an input stream, you often encounter the need to reuse an input stream, that is, read the contents of an input stream multiple times. For example, through the Url.openconnection method to open a remote site connection of the input stream, the content of which you want to be processed several times. This requires that a InputStream object be passed across multiple objects. In order to ensure that each object using the flow can get the correct content, the convection needs to be processed. There are usually two ways to solve this, one is to use the InputStream tag support. If a stream supports tagging (judging by the marksupported method), you can add a tag through the Mark method at the beginning of the flow, and when the use of a stream is completed, the Reset method resets the flow's read position to the location of the last marker, where the flow begins. Once again, this input stream can be reused. Most implementations of the input stream are not supported for tagging. You can use Bufferedinputstream to wrap to support markup.
1 Privateinputstream Preparestream (InputStream ins) {2Bufferedinputstream buffered =Newbufferedinputstream (INS);3 Buffered.mark (integer.max_value);4 returnbuffered;5 } 6 Private voidResetstream (InputStream ins)throwsIOException {7 Ins.reset ();8 Ins.mark (integer.max_value);9}
As shown in the code above, the basic InputStream can be packaged with a bufferedinputstream using the Preparestream method. The Mark method adds a token at the beginning of the stream, allowing the read of Integer.max_value bytes. After each stream is finished, it is reset by the Resetstream method.
Another approach is to convert the contents of the input stream into a byte array, which in turn translates into another implementation bytearrayinputstream of the input stream. The advantage of this is that using byte arrays as parameters is much simpler than the input stream, and there is no need to consider resource-related issues. Alternatively, you can close the original input stream as early as possible without waiting for all operations that use the stream to complete. The idea of the two approaches is actually similar. Bufferedinputstream also creates a byte array inside to hold the content read from the original input stream.
1 Private byte[] Savestream (InputStream input)throwsIOException {2Bytebuffer buffer = bytebuffer.allocate (1024);3Readablebytechannel Readchannel =Channels.newchannel (input);4Bytearrayoutputstream output =NewBytearrayoutputstream (32 * 1024);5Writablebytechannel Writechannel =Channels.newchannel (output);6 while((readchannel.read (buffer)) > 0 | | buffer.position ()! = 0) {7 Buffer.flip ();8 writechannel.write (buffer);9 buffer.compact ();Ten } One returnOutput.tobytearray (); A}
The Savestream method in the preceding code saves a inputstream as a byte array.
Buffer
Because the data behind the stream is likely to be large, buffers are typically used to improve performance in the actual operation. The traditional implementation of buffers is done using arrays. For example, the classic implementation of replication from InputStream to OutputStream is to use a byte array as the intermediate buffer. The buffer class and its subclasses introduced in NiO can be easily used to create buffers of various basic data types. The buffer class and its subclasses provide a richer way to manipulate the data in relation to the array. The channels mentioned later also use the buffer class for data passing.
element additions and deletions on buffer are expanded around 3 attributes position, limit, and capacity, representing the current read and write locations of buffer, available read and write ranges, and capacity limits. The capacity limit is specified at the time of creation. The get/put methods provided by buffer have both relative and absolute forms. Relative to read and write position is relative to the value of position, and absolute read and write need to specify the starting ordinal. A common mistake in using buffer is that the value of these 3 elements is not taken into account during read and write operations, since most of the time the relative read and write operation is used, and the value of position may have changed long ago. Some of the things that should be noted include the need to call the clear method before the data is read into the buffer, and the flip method needs to be called before the data in the buffer is output.
1 Bytebuffer buffer = bytebuffer.allocate (+); 2 charbuffer charbuffer = buffer.ascharbuffer (); 3 String content = charbuffer.put ("Hello"). Put ("World").Flip (). toString (); 4 System.out.println (content);
The code above shows the use of the buffer subclass. You can first create a buffer view of other data types above the existing bytebuffer, followed by a number of methods of the buffer subclass that can be cascaded, and finally pay attention to the use of the flip method.
Characters and encodings
In the program, it is always unavoidable to deal with characters, after all, the character is directly visible to the user information. The encoding is directly related to character processing. Believe that a lot of people have been in the process of garbled problems and puzzles. To figure this out, you need to understand the concept of character sets and encodings. A character set, as the name implies, is a collection of characters. A character set contains characters that are typically related to regions and languages. Each character in a character set typically has an integer encoding that corresponds to it. Common character sets are ASCII, Iso-8859-1, Unicode, and so on. For each character in a character set, in order to represent it in a computer, you need to convert a sequence of bytes, that is, the encoding of that character. The same character set can have different encoding methods. If a sequence of bytes produced in an encoding format is decoded in a different encoding format, it is possible to get the wrong character, resulting in garbled behavior. So when converting a sequence of bytes into a string, you need to know the correct encoding format.
The Java.nio.charset package in NiO provides classes related to character sets that can be used for encoding and decoding. The Charsetencoder and Charsetdecoder allow fine-grained control over the encoding and decoding process, such as handling illegal input and unrecognized characters in character sets. The filtering of character content can be achieved through these two classes. For example, when the application is designed to support only a certain character set, if the user entered the contents of other character sets, the interface is garbled when displayed. In this case, the unrecognized content can be ignored when decoding.
1String input = "You are 123 good";2Charset Charset = Charset.forname ("Iso-8859-1");3Charsetencoder encoder =Charset.newencoder ();4 Encoder.onunmappablecharacter (codingerroraction.ignore);5Charsetdecoder decoder =Charset.newdecoder ();6Charbuffer buffer = Charbuffer.allocate (32);7 buffer.put (input);8 Buffer.flip ();9 Try {TenBytebuffer Bytebuffer =encoder.encode (buffer); OneCharbuffer Cbuf =Decoder.decode (bytebuffer); ASystem.out.println (CBUF);//Output 123 -}Catch(charactercodingexception e) { - e.printstacktrace (); the } -
In the above code, by using the encoding and decoding of the iso-8859-1 character set, you can filter out characters in the string that are not in this character set.
Java I/O also provides a class that handles character streams in addition to the byte stream, the Reader/writer class and its subclasses, and the base unit that they manipulate is the char type. The bridge between the byte and the character is the encoding format. The conversion between the two is done through the encoder. When you create an instance of a Reader/writer subclass, you should always use the two-parameter construction method, explicitly specifying the character set or codec used. If you do not explicitly specify that the default character set of the JVM is used, it is possible to generate errors on other platforms.
Channel
The channel, as the core concept in NiO, is much better designed than the previous flow. Many implementations of channel-related are interfaces rather than abstract classes. The abstraction level of the channel itself is also more reasonable. A channel represents a connection to an entity that supports I/O operations. Once the channel is opened, read and write operations can be performed without the need to process the input stream or output stream as if it were a stream. Channel operations use buffer instead of arrays, which is more convenient and flexible than flow. The introduction of channels improves the flexibility and performance of I/O operations, mainly in file operations and network operations.
File Channel
For file manipulation, file channel FileChannel provides the ability to efficiently transfer data between other channels, which is simpler and faster than traditional stream-and byte-array-based buffers. For example, the following to save the content of a Web page to a local file implementation.
1 New FileOutputStream ("Baidu.txt"); 2 FileChannel channel = output.getchannel (); 3 New URL ("http://www.baidu.com"); 4 InputStream input = url.openstream (); 5 Readablebytechannel readchannel = channels.newchannel (input); 6 channel.transferfrom (readchannel, 0, Integer.max_value);
Another function of the file channel is to lock some fragments of the file. When an exclusive lock is added to a fragment on a file, the other process must wait for the lock to be released before it can access this fragment of the file. The lock on the file channel is held by the JVM and is therefore suitable for use with other applications. For example, when multiple applications share a configuration file, if the Java program needs to update the file, you can first get an exclusive lock on the file, then update the operation, and then release the lock. This ensures that the file update process is not affected by other programs.
Another feature that has a significant performance boost is support for memory-mapped files. The FileChannel map method allows you to create a Mappedbytebuffer object that will be directly reflected in the contents of the file. This is especially useful for reading and writing large files.
Socket Channel
Improvements in socket channels provide support for non-blocking I/O and multiplexed I/O. Traditional flow I/O operations are blocking. At the time of the I/O operation, the thread is in a blocking state waiting for the operation to complete. Non-blocking I/O support is introduced in NIO, but is limited to socket I/O operations. All channel classes that inherit from Selectablechannel can use the Configureblocking method to set whether non-blocking mode is used. In non-blocking mode, the program can query whether there is data available for reading at the appropriate time. This is usually done through regular polling.
Multiplexing I/O is a new I/O programming model. The traditional socket server is handled by a new thread for each client socket connection that is created. Creating threads is a time-consuming operation, and some implementations take a thread pool. But a processing model that requests a thread is not ideal. The reason is that the time-consuming threads that are created may be in a waiting state for most of the time. The basic practice of multiplexing I/O is that one thread manages multiple socket connections. The thread is responsible for processing according to the status of the connection. Multiplexing I/O relies on the support of select or similar system calls provided by the operating system to select those socket connections that are already ready for processing. You can register multiple non-blocking I/O channels on a selector and declare the type of operation you are interested in. Each time you invoke the Select method of selector, you can select a collection of channels that are already ready for some of the operations that you are interested in, so that you can handle them accordingly. If the processing to be performed is more complex, the processing can be forwarded to other threads for execution.
The following is a simple server implementation that uses multiplexed I/O. When there is a client connection, the server returns a Hello world as a response.
1 Private Static classIoworkerImplementsRunnable {2 Public voidrun () {3 Try {4Selector Selector =Selector.open ();5Serversocketchannel channel =Serversocketchannel.open ();6Channel.configureblocking (false);7ServerSocket socket =Channel.socket ();8Socket.bind (NewInetsocketaddress ("localhost", 10800));9 Channel.register (selector, channel.validops ());Ten while(true) { One Selector.select (); AIterator Iterator =Selector.selectedkeys (). iterator (); - while(Iterator.hasnext ()) { -Selectionkey key =Iterator.next (); the Iterator.remove (); - if(!Key.isvalid ()) { - Continue; - } + if(Key.isacceptable ()) { -Serversocketchannel SSC =(Serversocketchannel) Key.channel (); +Socketchannel sc =ssc.accept (); ASc.configureblocking (false); at Sc.register (selector, sc.validops ()); - } - if(Key.iswritable ()) { -Socketchannel client =(Socketchannel) Key.channel (); -Charset Charset = Charset.forname ("UTF-8"); -Charsetencoder encoder =Charset.newencoder (); inCharbuffer Charbuffer = charbuffer.allocate (32); -Charbuffer.put ("Hello World"); to Charbuffer.flip (); +Bytebuffer content =Encoder.encode (charbuffer); - client.write (content); the Key.cancel (); * } $ }Panax Notoginseng } -}Catch(IOException e) { the e.printstacktrace (); + } A } the}
The above code gives only a very simple sample program that shows the basic use of multiplexed I/O. When developing complex Web applications, using some Java NIO Network application frameworks will help you do more with less. Currently, the two most popular frameworks are Apache Mina and Netty. After using the Netty, Twitter's search function has been boosted by up to 3 times times faster. Web application developers can use these two open source excellent frameworks.
Resources
- Java 6 i/o-related APIs & Developer Guides
- Top Ten New Things you Can does with NIO
- Building highly scalable Servers with Java NIO
Java I/O