Hello, everyone, I am a little dish, a desire to do in the Internet industry Cai not dish little dish. Can soft can just, thumb up is soft, white piao is just! Dead ghost ~ read remember to give me a three even oh!
This paper mainly introduces
I/O system in Java
If necessary, you can refer to it
If helpful, do not forget thumb up pervious
WeChat public account has been opened, XiaoDai Liang remember, did not pay attention to the students remember to pay attention!
Preface:
Creating a good input/output (I/O) system is a difficult task for programming language designers
Java IO: The Java input/output system. Most programs need to process some input and produce some output from that input, so Java provides us with the java.io package
As a qualified program developer, we are not unfamiliar with IO. The knowledge system of Java IO is as follows:
After looking at the above figure, it is clear that there is so much support in the java.io package. As the saying goes, every change is the essence of everything. We only need to expand according to the source, and we believe that we can master the IO knowledge system well.
The File type
Read/write operations must deal with files, so if we want to master IO flow, we can start from the File.
The word “File” is neither singular nor plural. It can mean either a particular File or a set of files in a directory.
The list of
What can we do if we want to get a directory if we represent a set of files in a directory?
File has the API ready for us, and it’s not hard to guess what each API method does based on its return value type.
We know that there is a TestFile folder in our D disk directory. This folder contains the following files:
The name list
If we want to get a list of names in a given directory, we can use these two APIs:
list()
list(FilenameFilter filter)
By default, the list() method, which takes no arguments, lists all the file names in the specified directory. If we want to specify a list of directory names we can use another method:
We expected to get the name of the file with the test keyword, and that’s what we got.
File list
Sometimes we do a lot of things not just on a single file, but on an entire set of files. To generate this fileset, we need to use another API method for File:
listFiles()
listFiles(FilenameFilter filter)
listFiles(FileFilter filter)
Given this experience, it’s not hard to guess that listFiles() is a list of all files:
We’ve already got the fileset in the figure, and this method returns the same array, but an array of type File.
You are also smart enough to know how to get a file set with a specified keyword
Just like the file names listed above, what a clever little boy ~
But how is the parameter passed by the listFiles(FileFilter filter) method different from above? Let’s try:
It’s the same interface, and you need to override the accept() method, but this method has only one argument to File. Therefore, these two parameters are used for file filtering, the function is similar ~
Directory tools
Create a directory
The File class not only lets you work with existing directory files, but also lets you create something out of nothing!
The characteristics of a file are: name, size, last modified date, readability/write, type, etc
Then we should be able to get it through the API:
File does not provide a direct method to get a type, but we can get the type of a File by getting the full name of the File and then clipping to the suffix of the File:
A flip – hand operation, self-sufficient also can obtain the file type, is really a little clever ~
Above we are based on the existence of the file directory, so if we want to operate the file directory does not exist. Or if we accidentally entered the file directory name incorrectly, what would happen and would the operation process work?
The result is to throw an exception, which is normal, and to operate on a file directory that doesn’t exist is nonsense
So if we are not sure whether the file directory exists or not we can do something like this:
We can see two API methods that we haven’t seen before: exists() and mkdirs().
exists()
: Used to verify the existence of the file directorymkdirs()
: Used to create a directory
Through the above verification before operation, we successfully avoid the exception. The important thing to know here is that in addition to mkdir(), which can create directories, there is also a mkdir() which can create directories. What are the differences between the two methods besides the absence of an s?
mkdir()
: Only one level of directories can be createdmkdirs()
: You can create multiple levels of directories
In our current scenario, the Test directory does not exist, and the dir01 directory does not exist, so we need to create two directories. But if we use mkdir(), it won’t work. It won’t create. So we should use mkdirs() in this case.
The File type
File can be a File or a set of files, and the set of files can contain a File or a folder. If we want to read and write to a File, but accidentally operate on a folder, it will be awkward, so we can use ISDirectory to determine whether it is a folder:
Input and output
Above we talked about the basic operations of the File class, and then we entered the I/O module.
Inputs and outputs We often use the concept of a stream, such as an input stream and an output stream. This is an abstract concept that represents any data source object with the ability to produce data or any receiving object with the ability to receive data. The stream masks the actual I/O device looking for the details of how to process the data!
I/O can be divided into input and output parts.
The InputStream is divided into a byte InputStream (InputStream) and a character InputStream (Reader). Any class derived from InputStream or Reader implements the read() method, which reads a single byte or an array of bytes.
An OutputStream is divided into an OutputStream and a Writer. Any class that derives from an OutputStream or Writer implements the write() method, which is used to write to a single byte or an array of bytes.
So we can see the Java rule that all classes related to input should inherit from InputStream, and all classes related to output should inherit from OutputStream
InputStream
Used to represent classes that generate input from different data sources
What are the different data sources? The most common ones are: 1. Byte arrays 2. String objects 3. Files 4. “Pipes” (input at one end, output at the other end)
Each of these data sources has a corresponding InputStream subclass that can be manipulated:
class | function |
---|---|
ByteArrayInputStream | Allows an in-memory buffer to be used as an InputStream |
StringBufferInputStream | Have been abandonedConvert the String to InputStream |
FileInputStream | Used to read information from a file |
PipedInputStream | Generates the data used to write the related PipedOutputStream, implementedpipeliningThe concept of |
SequenceInputStream | Converts two or more InputStream objects to one InputStream |
FilterInputStream | An abstract class, asA decorator To provide useful functionality for other InputStreams |
OutPutStream
The class determines the destination to which the output will go: 1. Byte array 2. File 3. The pipe
Common OutputStream subclasses are:
class | function |
---|---|
ByteArrayOutputStream | Create a buffer in memory where all data sent to the stream will be placed |
FileOutputStream | Used to write information to a file |
PipedOutputStream | Any information written into it is automatically implemented as the output of the relevant PipeDinPutStreampipeliningThe concept of |
FilterOutputStream | An abstract class, asA decorator To provide useful functionality for other OutputStreams |
A decorator
From the above, we can see that both the input stream and the output stream have abstract classes FilterInputStream and FilterOutputStream. These classes act as decorators. I/O operations in Java require many different combinations of functions, which is why the Decorator pattern is used.
What is a decorator? A decorator must have the same interface as the object it decorates, but it can also extend the interface, which gives us quite a bit of flexibility, but it can also add complexity to the code.
FilterInputStream and FilterOutputStream are two classes used to provide a decorator class interface to control a particular InputStream (InputStream) and OutputStream (OutputStream).
FilterInputStream
InputStream is a byte InputStream, so the read data should be received as a byte array, as follows:
We use a byte array to receive the read value and convert it to a string type.
Now that we have the decorator FilterInputStream, can we use a subclass of the decorator to help us implement the read operation? Let’s look at the common FilterInputStream subclasses:
class | function |
---|---|
DataInputStream | Used with DataOutputStream, we can read basic data types (int, char, long) from streams in a portable manner. |
BufferedInputStream | Using it prevents you from having to do an actual write every time you read. It stands for buffer. |
The DataInputStream allows us to read data of different primitive types and String objects. With the corresponding DataOutputStream, we can migrate primitive types of data from one place to another via a data “stream”.
Before we get to BufferedInputStream let’s look at a set of test code:
There are three text files, of which Test01.txt is about 610M in size, and Test02 / Test03 are empty text files
So now we write the text with a regular InputStream + OutputStream and a decorated BufferedInputStream + BufferedOutputStream
Common combination:
Buffer combination:
It can be seen that the time consumption of the two methods is 4864 ms and 1275 ms respectively. Using normal combinations is equivalent to four times the buffer length, which is a dramatic difference if the file is larger! Surprised at the same time certainly also somewhat surprised, this is why?
If you read a file with the read() method, you need to visit the disk for every byte read, which is very inefficient. Even if the read(byte b[]) method is used to read more than one byte at a time, the disk operation will be frequent when the read file is large.
The BufferedInputStream API documentation explains that when a BufferedInputStream is created, an internal buffer array is created. When the bytes in the stream are read, the internal buffer can be filled again as needed from the included input stream, many bytes at a time. In other words, the Buffered class initializes by creating a large byte array and filling the byte array by reading multiple bytes from the underlying input stream at one time. When the program reads one or more bytes, it can fetch them directly from the byte array. When the memory byte is read out, it fills the buffer array again with the underlying input stream. So reading data from direct memory is much more efficient than accessing the disk every time.
BufferedInputStream/BufferedOutputStream not directly manipulate data source, but the other bytes for packaging, they are the processing flow.
The program saves the data in the BufferedOutputStream buffer and does not immediately save it to the file. The array in the buffer is saved to the file in the following cases:
- Buffer full
flush()
Clear bufferclose()
Close the stream
FilterOutputStream
The basic operations of an OutputStream are as follows:
You can write the value to the file by calling the write() method, but there are two things to note:
- The default mode for writing documents is overwrite
If we call this method twice, the text file should have two lines of public number, but in reality only one line. This is because the content written later will overwrite the content of the previous content. The solution is to add append = true to the constructor
- The difference between writing and reading is that when reading, an error will be reported if the file does not exist, but when writing, the file will be created for you by default if it does not exist
The decorator class FilterOutputStream also exists in OutputStream. Here are some common subclasses of the decorator class:
class | function |
---|---|
DataOutputStream | Used with DataInputStream to write primitive types (int, char, long, etc.) to streams in a portable manner |
BufferedOutputStream | Use it to avoid having to do the actual write every time data is sent, representingUse Buffers, you can callflush Clear buffer |
The DataOutputStream and BufferedOutputStream have been covered above, so I won’t repeat them here.
The Reader and Writer
In Java 1.1, there were major changes to the basic I/O stream library, adding Reader and Writer classes. In my previous limited knowledge, I would have mistakenly assumed that these two classes were created to replace InputStream and OutputStream, but this is not the case with my limited knowledge.
InputStream and OutputStream provide functionality for I/O in a byte-oriented form, while Reader and Writer provide functionality for I/O in a Unicode-compatible character-oriented form
The two coexist and the adapters – InputStreamReader and OutputStreamWriter are provided
InputStreamReader
Can put theInputStreamconvertReaderOutputStreamWriter
Can put theOutputStreamconvertWriter
The two are very similar, though not identical, and the comparison is as follows:
Byte stream | Characters of the flow |
---|---|
InputStream | Reader |
OutputStream | Writer |
FileInputStream | FileReader |
FileOutputStream | FileWriter |
ByteArrayInputStream | CharArrayReader |
ByteArrayOutputStream | CharArrayWriter |
PipedInputStream | PipedReader |
PipedOutputStream | PipedWriter |
Even the decorator class is almost similar:
Byte stream | Characters of the flow |
---|---|
FilterInputStream | FilterReader |
FilterOutputStream | FilterWriter |
BufferedInputStream | BufferedReader |
BufferedOutputStream | BufferedWriter |
PrintStream | PrintWriter |
The way to use Reader and Writer is also very simple:
Let’s also take a look at the use of BufferedReader and BufferedWriter for decorators
RandomAccessFile
RandomAccessFile works with files that are made up of records of known size, so we can use seek() to move records from one place to another and then read or modify the records. The size of records in a file is not necessarily the same, as long as we can determine which records are large and where they are in the file.
We can see from the figure that RandomAccessFile inherits not from InputStream and OutputStream, but from the somewhat unfamiliar DataInput and DataOutput.
It’s a bit of a maverick class. Let’s move on to its constructor:
We only cut part of the constructor here, after all, we only cut the key points
Looking at the constructor, we can see that there are four patterns defined:
r | Open text read-only, which means you can’t use write to manipulate the file |
---|---|
rw | Both read and write operations are allowed |
rws | Each time a write is performed, a synchronous flush is made to disk to refresh the content and metadata |
rwd | Each time a write is performed, a synchronous flush is made to disk to refresh the contents |
What’s the use of that? Put bluntly, the RandomAccessFile class wants everything. He can read and write
Essentially, RandomAccessFile works like a combination of DataInputStream and DataOutputStream, with the addition of methods such as getFilePoint () to find the current location of the file. Seek () is used to move to a new position within the file, and length() is used to determine the maximum size of the file. The second parameter is used to indicate whether we are “Random Read (R)” or “Both Read and Write (RW)”, but it does not support writing files alone. Let’s actually do this:
Get Read Only RandomAccessFile:
Get Readable and Writable RandomAccessFile
We first wrote four words from test to the File, then moved the head pointer three bits and then wrote four words from File. The result is testFile, because we moved the pointer to start writing in the fourth position.
ZIP
When we think of the word zip, we naturally think of compressed files, and yes, compressed files are also extremely important in Java I/O. Perhaps it would be better to say that file compression is also extremely important in our development.
The classes that require ZIP compression are provided in the Java built-in classes, and you can use the ZipOutuputStream and ZipInputStream in the java.util.zip package to compress and decomcompress files. Let’s first look at how to compress files ~
ZipOutputStream
ZipOutputStream is constructed as follows:
public ZipOutputStream(OutputStream out) {/* doSomething */}
We need to pass in an OutputStream object. So we can think of a compressed file as roughly equivalent to writing data to a compressed file, which may sound a bit confusing. Let’s first look at the APIs in ZipOutputStream:
methods | The return value | instructions |
---|---|---|
putNextEntry(ZipEntry e) | void | Start writing a new ZipEntry and move the position in the stream to the beginning of the entry value |
write(byte[] b, int off, int len) | void | Writes the byte array to the current ZIP entry data |
setComment(String command) | void | Sets the comment text for this ZIP file |
finish() | void | Complete writing to the ZIP OutputStream without closing its accompanying OutputStream |
Let’s demonstrate how to compress a file:
Scenario: We need to compress the TestFile folder in the D directory into the test.zip folder in the D directory
The specific operation logic is as follows:
By following the above steps, we can compress a file smoothly
ZipInputStream
Say that how to compress the file, that will naturally how to decompress the file!
public ZipInputStream(InputStream in) {/* doSomethings */}
ZipInputStream is similar to a compressed stream in that the constructor also needs to pass in an InputStream object, and the API is, of course, one-to-one:
methods | The return value | instructions |
---|---|---|
read(byte[] b, int off, int len) | int | Reads the location of the off offset, length of len bytes, in the destination b array |
avaiable() | int | Check if you have read the data specified by the current entry. If you have read it, return 0. Otherwise, return 1 |
closeEntry() | void | Close the current ZIP entry and position the stream to read the next entry |
skip(long n) | long | Skip the number of bytes specified in the current ZIP entry |
getNextEntry() | ZipEntry | Reads the next ZipEntry and moves the position in the stream to the beginning of the data to which the entry refers |
createZipEntry(String name) | ZipEntry | Create a new ZipEntry object with the specified name parameter |
Here’s how to unzip a file:
Don’t be intimidated by the length of the code. If you read it carefully, it’s easy to extract the file:
We get a ZipEntry from the getNExtentry () method, which is similar to a deep traversal, returning something like this:
Each time we traverse all the files in a directory, such as dir01, before we continue traversing the dir02 folder, so we don’t need to recurse to get all the files. After each file is fetched, the output stream is fetched through ZipFile, and then written to the extracted file. The general process is as follows:
New I/O
A new Javai /O library was introduced in the java.nio.* package for JDK1.4, with the simple purpose of increasing speed. In fact, the old I/O packages have been re-implemented using NIO to take advantage of this speed increase.
As long as the structure used is closer to the way the operating system performs I/O, the speed will naturally increase, hence the two concepts: channels and buffers.
How do we understand channels and buffers? We can think of the buffer as a little train in a coal mine, and the passage as the track of the train, which carries the full coal from the source to the other place. So instead of interacting directly with the channel, we’re interacting with the buffer, and sending the buffer to the channel. Channels either get data from or send data to the buffer.
The ByteBuffer is the only buffer that directly interacts with the channel and can store unprocessed bytes.
ByteBuffer buffer = ByteBuffer.allocate(1024);
The byteBuffer is typically created by specifying the size to be created by allocate(). At the same time, byteBuffer supports the creation of byteBuffer in 4
To better support the new I/O, three classes in the old I/O class library have been modified to generate a FileChannel. The modified classes are FileInputStream, FileOutputStream, and RandomAccessFile for both reading and writing. It is important to note here that these are streams of byte operations, because a stream of characters cannot be used to generate Channels, but Channels provide practical methods for generating readers and writers in Channels
Access to the channel
We have seen above that there are three classes that support generating channels. The methods for generating channels are as follows:
These are the three ways to create a channel, and the read and write operations are tested. Let’s take a look at the test code in the figure below and summarize:
getChannel()
The method will produce oneFileChannel
. We can send it read-writeByteBuffer
. We store bytes inByteBuffer
One way to do this is to useput()
Method populates them directly, filling in one or more bytes, or values of primitive data types. However, it can also be usedwray()
Method to take an array of bytes that already exist“Packaging”In the ByteBuffer. This way you don’t have to copy the underlying array, but treat it as the generated oneByteBufferI can call it memoryArrays supported by ByteBuffer- And we can see that
FileChannel
Using theposition()
Method, which can be moved around the fileFileChannel
In this case, we move it to the end and do the rest of the read and write operations. - For read-only access, we must explicitly use static
allocate()
Method to assignByteBuffer
. We can also use it if we want to get better speedallocateDirect()
To produce one that has a higher coupling with the operating system“Direct”Buffer. But this allocation is more expensive, and the implementation varies from operating system to operating system. - If we want to call
read()
toByteBuffer
To store the bytes, the buffer must be calledflip()
Method. This is used to tellFileChannel
And make it ready for someone else to read the bytes, and of course,This is also for maximum speed. Here we useByteBufferAfter receiving a byte, there is no buffer to continue with if neededread()
And then we have to callclear()
Method for eachread()
Method to prepare.
The channel is connected
Programmers tend to be lazy, and the above method of notizing FileChannel after reading it seems a bit cumbersome. So is there an easier way? There must be some, or I wouldn’t ask, right? That is, to connect one channel directly to another channel, which requires special methods transferTo() and transferFrom(). Specific use is as follows:
Using either method 1 or method 2, you can successfully write the file to test03.txt
END
I/O operations are an integral part of our daily development, so we need to master them as well!
Today you work harder, tomorrow you will be able to say less words for people!
I am Xiaocai, a man who studies with you. 💋
WeChat public account has been opened, XiaoDai Liang remember, did not pay attention to the students remember to pay attention!