Summary of Java IO knowledge points

< ---------------------------------- Liao Xuefeng learns Java ---------------------------------------- >

1. File object

  • Java's standard library, java.io, provides File objects to manipulate files and directories
  • The File object has three types of paths, one is getPath(), which returns the path passed in by the constructor, the other is getAbsolutePath(), which returns the absolute path, and the other is getCanonicalPath, which is similar to the absolute path, but returns the normal path
  • The File object has a static variable to represent the system separator of the current platform:
System.out.println(File.separator); // Print '\' or '/' based on the current platform
  • When constructing a File object, even if the incoming File or directory does not exist, the code will not make an error, because constructing a File object will not cause any disk operation. Only when we call some methods of the File object can we really perform disk operation
  • Call isFile() to determine whether the File object is an existing File, and call isDirectory() to determine whether the File object is an existing directory
  • Determine the permission and size of the file:
boolean canRead(): Whether it is readable;
boolean canWrite(): Whether it is writable;
boolean canExecute(): Whether it is executable;
long length(): File byte size.

For a directory, executable indicates whether its subfolders and sub files can be listed

  • When the File object represents a File, you can create a new File through createNewFile() and delete the File with delete():
File file = new File("/path/to/file");
if (file.createNewFile()) {
    // File created successfully:
    // TODO:
    if (file.delete()) {
        // File deleted successfully:
    }
}
  • The File object provides createTempFile() to create a temporary File, and deleteOnExit() to automatically delete the File when the JVM exits
  • When the File object represents a directory, you can use list() and listfiles () to list the File and subdirectory names under the directory. listFiles() provides a series of overloaded methods to filter unwanted files and directories:
public class Main {
    public static void main(String[] args) throws IOException {
        File f = new File("C:\\Windows");
        File[] fs1 = f.listFiles(); // List all files and subdirectories
        printFiles(fs1);
        File[] fs2 = f.listFiles(new FilenameFilter() { // List only. exe files
            public boolean accept(File dir, String name) {
                return name.endsWith(".exe"); // Return true to accept the file
            }
        });
        printFiles(fs2);
    }

    static void printFiles(File[] files) {
        System.out.println("==========");
        if (files != null) {
            for (File f : files) {
                System.out.println(f);
            }
        }
        System.out.println("==========");
    }
}
  • If the File object represents a directory, you can create and delete the directory through the following methods:
boolean mkdir(): Create current File The directory represented by the object;
boolean mkdirs(): Create current File Object, and create the nonexistent parent directory if necessary;
boolean delete(): Delete current File Object. The current directory must be empty to delete successfully.
  • The Java standard library also provides a Path object, which is located in the java.nio.file package. The Path object is similar to the file object, but the operation is simpler:
public class Main {
    public static void main(String[] args) throws IOException {
        Path p1 = Paths.get(".", "project", "study"); // Construct a Path object
        System.out.println(p1);
        Path p2 = p1.toAbsolutePath(); // Convert to absolute path
        System.out.println(p2);
        Path p3 = p2.normalize(); // Convert to canonical path
        System.out.println(p3);
        File f = p3.toFile(); // Convert to File object
        System.out.println(f);
        for (Path p : Paths.get("..").toAbsolutePath()) { // You can traverse Path directly
            System.out.println("  " + p);
        }
    }
}

2. InputStream

  • InputStream is the most basic input stream provided by the Java standard library. It is located in the Java. IO package. The java.io package provides all synchronous IO functions
  • InputStream is an abstract class, which is a superclass of all input streams. One of the most important methods defined by this abstract class is int read(). The signature is as follows:
public abstract int read() throws IOException;
// This method will read the next byte of the input stream and return the int value represented by bytes (0 ~ 255). If you have read to the end, return - 1 to indicate that you can't continue reading
  • FileInputStream is a subclass of InputStream. As the name suggests, FileInputStream reads data from a file stream
  • Both InputStream and OutputStream close the stream through the close() method. Closing the flow will release the corresponding underlying resources
  • During the process of reading or writing IO streams, errors may occur. For example, a file cannot be read because it does not exist, a write failure because it does not have write permission, and so on. These underlying errors are automatically encapsulated into IOException exceptions and thrown by the Java virtual machine. Therefore, all code related to IO operations must handle IOException correctly
  • Using the new try(resource) syntax introduced in Java 7, you only need to write a try statement to let the compiler automatically close the resources for us:
public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        int n;
        while ((n = input.read()) != -1) {
            System.out.println(n);
        }
    } // Here the compiler automatically writes finally for us and calls close()
}

The compiler only depends on whether the object in try(resource =...) implements the java.lang.autoclosable interface. If so, it will automatically add a finally statement and call the close() method. Both InputStream and OutputStream implement this interface, so they can be used in try(resource)

  • Many streams support reading multiple bytes to the buffer at one time. For file and network streams, using the buffer to read multiple bytes at one time is often much more efficient. InputStream provides two overloaded methods to support reading multiple bytes:
int read(byte[] b): Read several bytes and fill in byte[]Array that returns the number of bytes read
int read(byte[] b, int off, int len): appoint byte[]Offset and maximum padding of the array
//When using the above method to read multiple bytes at a time, you need to define a byte [] array as the buffer,
//The read() method reads as many bytes as possible into the buffer, but does not exceed the size of the buffer.
//The return value of the read() method is no longer the int value of bytes, but returns how many bytes were actually read.
//If - 1 is returned, there is no more data

public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        // Define a 1000 byte buffer:
        byte[] buffer = new byte[1000];
        int n;
        while ((n = input.read(buffer)) != -1) { // Read buffer
            System.out.println("read " + n + " bytes.");
        }
    }
}
  • When calling the read() method of InputStream to read data, the read() method is Blocking, that is, you must wait for the read() method to return before executing the next line of code
  • ByteArrayInputStream can simulate an InputStream in memory:
public class Main {
    public static void main(String[] args) throws IOException {
        byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {
            int n;
            while ((n = input.read()) != -1) {
                System.out.println((char)n);
            }
        }
    }
}
  • ByteArrayInputStream actually turns a byte [] array into an InputStream in memory. Although there are few practical applications, it can be used to construct an InputStream during testing:
public class Main {
    public static void main(String[] args) throws IOException {
        byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {
        // You don't need to provide a FileInputStream. You can simulate it with ByteArrayInputStream
            String s = readAsString(input);
            System.out.println(s);
        }
    }

    public static String readAsString(InputStream input) throws IOException {
        int n;
        StringBuilder sb = new StringBuilder();
        while ((n = input.read()) != -1) {
            sb.append((char) n);
        }
        return sb.toString();
    }
}

3. OutputStream

  • OutputStream is the most basic output stream provided by the Java standard library. It is the superclass of all output streams. One of the most important methods defined by this abstract class is void write(int b). This method will write a byte to the output stream. Note that although the int parameter is passed in, only one byte will be written, that is, only the part of the byte represented by the lowest 8 bits of int will be written
  • OutputStream also provides a close() method to close the output stream to free up system resources. Note in particular: OutputStream also provides a flush() method, which is designed to actually output the contents of the buffer to the destination.

Generally speaking, the system will wait until the buffer is full before outputting it at one time. The function of the flush() method is to force the output of the contents of the buffer

  • Normally, we don't need to call this flush() method, because when the buffer is full, the OutputStream will be called automatically, and the flush() method will also be called automatically before calling the close() method to close the OutputStream. However, in situations requiring rapid response (such as chat services), we need to call the flush method manually
  • Take FileOutputStream as an example to demonstrate how to write several bytes to the file stream:
public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write(72); // H
    output.write(101); // e
    output.write(108); // l
    output.write(108); // l
    output.write(111); // o
    output.close();
}

public void writeFile() throws IOException {
    try (OutputStream output = new FileOutputStream("out/readme.txt")) {
        output.write("Hello".getBytes("UTF-8")); // Hello
    } // Here the compiler automatically writes finally for us and calls close()
}
  • The write() method of OutputStream is blocked
  • FileOutputStream can obtain output streams from files, which is a common implementation class of OutputStream. In addition, ByteArrayOutputStream can simulate an OutputStream in memory
  • When multiple autoclosable resources are operated at the same time, multiple resources can be written out simultaneously in the try(resource) {...} statement; separate. For example, read and write two files at the same time:
// Read input.txt and write output.txt:
try (InputStream input = new FileInputStream("input.txt");
     OutputStream output = new FileOutputStream("output.txt"))
{
    input.transferTo(output); // What is the function of transferTo?
}

4. Filter mode (decorator mode)

The functions of InputStream/OutputStream are not invariable. For example, when we want to add "cache", "signature", "encryption" and other functions to the Stream, the problem of "subclass explosion" will occur if we directly create various types of subclasses (that is, the number of subclasses is too large. It is conceivable that the Stream of each function is a subclass, and the combination between different functions is also a subclass, and the number of such combinations is too large). Therefore, Java advocates using Filter mode (decorator mode) to wrap the Stream object.

  • In order to solve the problem that dependent inheritance will cause the number of subclasses to get out of control, JDK first divides InputStream into two categories:
One is the basis for directly providing data InputStream,For example:
    FileInputStream
    ByteArrayInputStream
    ServletInputStream

One is to provide additional functions InputStream,For example:
	BufferedInputStream
	DigestInputStream
	CipherInputStream
  • When we need to add various functions to a "basic" InputStream, we first determine the InputStream that can provide data sources, such as FileInputStream:
InputStream file = new FileInputStream("test.gz");
  • Next, we hope that FileInputStream can provide buffering function to improve reading efficiency. Therefore, we wrap this InputStream with BufferedInputStream. The obtained packaging type is BufferedInputStream, but it is still regarded as an InputStream:
InputStream buffered = new BufferedInputStream(file);
  • Finally, assuming that the file has been compressed with gzip, we want to directly read the extracted content, and then we can package a GZIPInputStream:
InputStream gzip = new GZIPInputStream(buffered);
  • No matter how many times you wrap it, the object you get is always InputStream. We directly use InputStream to reference it:
  • Write FilterInputStream:
public class Main {
    public static void main(String[] args) throws IOException {
        byte[] data = "hello, world!".getBytes("UTF-8");
        try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) {
            int n;
            while ((n = input.read()) != -1) {
                System.out.println((char)n);
            }
            System.out.println("Total read " + input.getBytesRead() + " bytes");
        }
    }
}

class CountInputStream extends FilterInputStream {
    private int count = 0;

    CountInputStream(InputStream in) {
        super(in);
    }

    public int getBytesRead() {
        return this.count;
    }

    public int read() throws IOException {
        int n = in.read();
        if (n != -1) {
            this.count ++;
        }
        return n;
    }

    public int read(byte[] b, int off, int len) throws IOException {
        int n = in.read(b, off, len);
        if (n != -1) {
            this.count += n;
        }
        return n;
    }
}

Note that when multiple filterinputstreams are superimposed, we only need to hold the outermost InputStream, and when the outermost InputStream is closed (automatically closed at the end of the try(resource) block), the close() method of the inner InputStream will also be called automatically and finally called to the core "basic" InputStream, so there is no resource leakage

5. Directly read the contents of Zip compressed file

  • ZipInputStream is a FilterInputStream, which can directly read the contents of the zip package:

    The other JarInputStream is derived from ZipInputStream. Its main function is to directly read the MANIFEST.MF file in the jar file. In essence, the jar package is a zip package, but some fixed description files are attached.

  • Read zip file: create a ZipInputStream, usually pass in a FileInputStream as the data source, and then call getNextEntry() repeatedly until null is returned, indicating the end of the zip stream; a zipintry represents a compressed file or directory. If it is a compressed file, we will use the read() method to read it continuously until - 1 is returned:

try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) {
    ZipEntry entry = null;
    while ((entry = zip.getNextEntry()) != null) {
        String name = entry.getName();
        if (!entry.isDirectory()) {
            int n;
            while ((n = zip.read()) != -1) {
                ...
            }
        }
    }
}
  • Create Zip file: ZipOutputStream is a FilterOutputStream, which can be directly written to the zip package. We first create a ZipOutputStream, usually wrapping a FileOutputStream. Then, before writing a file, we call putNextEntry() first, then write byte[] data with write(). After finishing writing, we call closeEntry() to wrap up the file.
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(...))) {
    File[] files = ...
    for (File file : files) {
        zip.putNextEntry(new ZipEntry(file.getName()));
        zip.write(getFileDataAsBytes(file));
        zip.closeEntry();
    }
}

The above code does not consider the directory structure of the file. If you want to implement the directory hierarchy, the name passed in by new ZipEntry(name) should use a relative path

6. Read classpath resources

  • Reading files from classpath can avoid the problem of inconsistent file paths in different environments: if we put the properties file in classpath, we don't care about its actual storage path
  • The resource files in classpath always start with / at the beginning. We first get the current Class object and then call getResourceAsStream() * to read any resource file directly from classpath:
try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
    // TODO:
}
  • One thing to note when calling getResourceAsStream() is that it will return null if the resource file does not exist
  • Put the default configuration into the jar package, and then read an optional configuration file from the external file system. You can not only have the default configuration file, but also allow users to modify the configuration themselves:
Properties props = new Properties();
props.load(inputStreamFromClassPath("/default.properties"));
props.load(inputStreamFromFile("./conf.properties"));

7. Serialization

  • Serialization refers to turning a Java object into binary content, which is essentially a byte [] array
  • To serialize a Java object, you must implement a special java.io.Serializable interface. The Serializable interface does not define any methods, but is an empty interface. We call this empty interface "Marker Interface". The class that implements the Marker Interface only pastes a "marker" on itself without adding any methods
  • To change a Java object into a byte [] array, you need to use ObjectOutputStream. It is responsible for writing a Java object into a byte stream:
ublic class Main {
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
            // Write int:
            output.writeInt(12345);
            // Write String:
            output.writeUTF("Hello");
            // Write Object:
            output.writeObject(Double.valueOf(123.456));
        }
        System.out.println(Arrays.toString(buffer.toByteArray()));
    }
}
  • In contrast to ObjectOutputStream, ObjectInputStream is responsible for reading Java objects from a byte stream:
try (ObjectInputStream input = new ObjectInputStream(...)) {
    int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();
}
  • Possible exceptions thrown by readObject() are:
ClassNotFoundException: No corresponding Class;
InvalidClassException: Class No match.

about ClassNotFoundException,This is common on a computer Java The program puts one Java Object,
For example, Person After the object is serialized, it is transmitted to another computer on another computer through the network Java Procedures,
But this computer Java The program is not defined Person Class, so it cannot be deserialized.

about InvalidClassException,This is common in serialized Person Object defines a int Type age Field,
But when deserializing, Person Class defined age The field has been changed to long Type, so it leads to class incompatible.
  • During deserialization, the JVM directly constructs Java objects without calling the construction method. The code inside the construction method cannot be executed during deserialization
  • The object-based serialization and deserialization mechanism provided by Java itself has both security and compatibility problems. A better serialization method is realized through a general data structure such as JSON, which only outputs the contents of basic types (including String) without storing any code related information

8. Reader

  • Reader is another input stream interface provided by Java's IO library. The difference from InputStream is that InputStream is a byte stream, that is, read in bytes, while reader is a character stream, that is, read in char
  • java.io.Reader is a superclass of all character input streams. The read() method reads the next character in the character stream and returns the int represented by the character, ranging from 0 to 65535. If it has been read to the end, it returns - 1
  • FileReader is a subclass of Reader. It can open files and obtain readers. The default encoding of FileReader is related to the system (for example, if the default encoding of Windows is GBK, the UTF-8 encoded files will be garbled). To avoid garbled encoding, we need to specify the encoding when creating FileReader:
Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);
  • Use * * try (resource) * * to ensure that the Reader can be closed correctly whether there is an IO error or not
  • CharArrayReader can simulate a Reader in memory. Its function is actually to turn a char [] array into a Reader:
try (Reader reader = new CharArrayReader("Hello".toCharArray())) {
}
  • StringReader can directly use String as the data source, which is almost the same as CharArrayReader:
try (Reader reader = new StringReader("Hello")) {
}
  • What is the relationship between Reader and InputStream?

In addition to the special CharArrayReader and StringReader, the ordinary Reader is actually constructed based on InputStream, because the Reader needs to read the byte stream from InputStream, and then convert it to char according to the encoding settings to realize the character stream. If we look at the source code of FileReader, it actually holds a FileInputStream internally

  • InputStreamReader can convert any InputStream into a Reader (converter). The example code is as follows:
// Hold InputStream:
InputStream input = new FileInputStream("src/readme.txt");
// Convert to Reader:
Reader reader = new InputStreamReader(input, "UTF-8");

9. Writer

  • The Writer is an OutputStream with an encoding converter, which converts char to byte and outputs it
  • Writer is a superclass of all character output streams. Its main methods include:
Write one character (0)~65535): void write(int c);
All characters written to the character array: void write(char[] c);
write in String All characters represented by: void write(String s). 
  • FileWriter is a Writer that writes character streams to files. Its usage is similar to FileReader:
try (Writer writer = new FileWriter("readme.txt", StandardCharsets.UTF_8)) {
    writer.write('H'); // Write a single character
    writer.write("Hello".toCharArray()); // Write char []
    writer.write("Hello"); // Write String
}
  • CharArrayWriter can create a Writer in memory. Its function is actually to construct a buffer, write char, and finally get the written char [] array, which is very similar to ByteArrayOutputStream:
try (CharArrayWriter writer = new CharArrayWriter()) {
    writer.write(65);
    writer.write(66);
    writer.write(67);
    char[] data = writer.toCharArray(); // { 'A', 'B', 'C' }
}
  • StringWriter is also a memory based Writer, which is similar to CharArrayWriter. In fact, StringWriter maintains a StringBuffer internally and provides a Writer interface externally
  • In addition to CharArrayWriter and StringWriter, an ordinary Writer is actually constructed based on OutputStream. It receives char, automatically converts it into one or more byte s internally, and writes it to OutputStream. Therefore, OutputStreamWriter is a converter that converts any OutputStream into a Writer:
try (Writer writer = new OutputStreamWriter(new FileOutputStream("readme.txt"), "UTF-8")) {
    // TODO:
}

10. PrintStream and PrintWriter

  • PrintStream is a FilterOutputStream. It provides additional methods for writing various data types on the interface of OutputStream:
write in int: print(int)
write in boolean: print(boolean)
write in String: print(String)
write in Object: print(Object),Actually equivalent to print(object.toString())
...
  • The System.out.println() we often use is actually to print various data using PrintStream. Among them, System.out is the PrintStream provided by the system by default, representing standard output
  • System.err is the standard error output provided by the system by default
  • Compared with OutputStream, PrintStream not only adds a set of print()/println() methods to print various data types, which is more convenient, but also has an additional advantage that it does not throw ioexceptions, so we don't have to catch ioexceptions when writing code
  • As like as two peas, PrintStream always outputs byte data, while PrintWriter extends Writer interface. Its print()/println() method finally outputs char data.
public class Main {
    public static void main(String[] args)     {
        StringWriter buffer = new StringWriter();
        try (PrintWriter pw = new PrintWriter(buffer)) {
            pw.println("Hello");
            pw.println(12345);
            pw.println(true);
        }
        System.out.println(buffer.toString());
    }
}

11. Use the Files tool class

  • Starting from Java 7, it provides two tool classes, files and Paths, which can greatly facilitate us to read and write files. Files and Paths are classes in java.nio package. They encapsulate many simple methods to read and write files
  • To read all the contents of a file as a byte [], it can be written as follows:
byte[] data = Files.readAllBytes(Paths.get("/path/to/file.txt"));
  • If it is a text file, you can read all the contents of a file as String:
// Read with UTF-8 encoding by default:
String content1 = Files.readString(Paths.get("/path/to/file.txt"));
// Code can be specified:
String content2 = Files.readString(Paths.get("/path/to/file.txt"), StandardCharsets.ISO_8859_1);
// Read and return each line by line:
List<String> lines = Files.readAllLines(Paths.get("/path/to/file.txt"));
  • Write file:
// Write binary:
byte[] data = ...
Files.write(Paths.get("/path/to/file.txt"), data);
// Write text and specify encoding:
Files.writeString(Paths.get("/path/to/file.txt"), "Text content...", StandardCharsets.ISO_8859_1);
// Write text by line:
List<String> lines = ...
Files.write(Paths.get("/path/to/file.txt"), lines);
  • The Files tool class also has shortcut methods such as copy(), delete(), exists(), move(), etc. to operate Files and directories
  • The read-write method provided by Files is limited by memory. It can only read and write small Files, such as configuration Files, and cannot read several G large Files at a time. File stream is still used to read and write large Files, and some file contents are read-only at a time

Keywords: Java io

Added by kathas on Sun, 31 Oct 2021 03:36:24 +0200