Summary of Java IO principles

Java IO Overview

File related classes

Files are a common source or destination of data in Java applications.

In Java applications, files are a common data source or medium for storing data.

Read files through Java IO

  • FileInputStream

    Binary file input stream

    You can read one byte at a time from the beginning of the file to the end of the file

  • FileReader

    Text file input stream

    You can read one character at a time from the beginning of the file to the end of the file

You also don’t have to read the entire file at once; instead, you can read the bytes and characters in the file sequentially.

  • RandomAccessFile

    Read parts of a file by skipping

Write files through Java IO

  • FileOutputStream
  • FileWriter

Random access to files via Java IO

  • RandomAccess

    Random access doesn’t mean that you can read and write in truly random places, it just means that you can skip parts of a file and write simultaneously without requiring a specific order of access.

    This allows RandomAccessFile to overwrite parts of a file, append content to the end of it, or delete content from it, or of course read from anywhere in the file.

The Stream (flow)

A Java IO stream is a data stream that can either be read from or written to. Flows are often associated with data sources, data flow destinations, such as files, networks, and so on.

The characteristics of the flow

  1. Streams, unlike arrays, cannot read or write data through indexes (subscripts)

    In a stream, you can’t move data back and forth like an array, either, unless you use RandomAccessFile to process the file. A stream is simply a continuous stream of data.

  2. Java IO flows are usually byte – or character-based.

    1. Byte streams are usually named “stream”, such as InputStream and OutputStream

      With the exception of DataInputStream and DataOutputStream, which can also read and write int, long, float, and double values, streams can read or write only one raw byte ata time.

    2. Character streams are often named “Reader” or “Writer”

Byte stream

InputStream

If you are writing a component that needs to read input from a stream, try to make our component depend on an InputStream, rather than any of it’s subclasses (e.g. FileInputStream).

Doing so makes your code able to work with all types of input streams, instead of only the concrete subclass.

The java.io.InputStream class is the base class for all Java IO input streams. If you’re developing a component that reads data from a stream, try InputStream instead of any of its subclasses (such as FileInputStream).

Doing so allows your code to be compatible with any type of input stream rather than a certain type. (Better compatibility)

read()

Java Doc: Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255.

If no byte is available because the end of the stream has been reached, the value -1 is returned.

This method blocks until input data is available, the end of the stream is detected,or an exception is thrown.

Read data: Data is usually read using the read() method in InputStream.

The read() method returns an integer representing the contents of the bytes read(0 to 255). The read() method returns -1 when there is no more data to read at the end of the stream

 try (InputStream in = new FileInputStream(
            "D:\\gitRepository\\organized-learning\\deep-in-java\\stage-8\\helloworld.txt");
            OutputStream out = new FileOutputStream(
                "D:\\gitRepository\\organized-learning\\deep-in-java\\stage-8\\output.txt")) {
            
            int data;
            while((data = in.read()) ! = -1) { out.write(data); }}Copy the code
FileInputStream

Focus on constructors

 public FileInputStream(String name) throws FileNotFoundException {
        this(name ! =null ? new File(name) : null);
 }
 public FileInputStream(File file) throws FileNotFoundException {... }Copy the code
If the file itself does not exist, FileInputStream will be created with an error
File file = new File("c:\\data\\input-text.txt");
InputStream input = new FileInputStream(file);
// Or the incoming path
InputStream inputStream=new FileInputStream("c:\\data\\input-text.txt");
Copy the code
BufferedInputStream

BufferedInputStream provides a buffer for the input stream, which can speed up a lot of IO.

Instead of reading one byte at a time from the network or disk, you can read a chunk of data at a time. Especially when accessing a lot of disk data, caching often makes I/O much faster.

InputStream input = new BufferedInputStream(new FileInputStream("c:\\data\\input-file.txt"));

public synchronized int read(a) throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
}
Copy the code

The byte[] array is filled up at one time

OutputStream

Java.io.OutputStream is the base class for all output streams in Java IO.

write(int)

Used to write a single byte to the output stream.

The write(int) method of OutputStream writes as an argument an int variable containing data to be written.

Only the first byte of type int is written; the remaining bits are ignored. (Write the lower 8 bits, ignore the higher 24 bits).

write(byte[])
  • Write (byte[]) writes all the data in the byte array to the output stream.

  • Write (byte[], int offset, int length) Writes the length of byte data starting at offset to the output stream.

flush()

Flush all data written to OutputStream into the appropriate target medium.

For example, if the output stream is a FileOutputStream, the data written to it may not actually be written to disk. Even if all data is written to a FileOutputStream, it may remain in an in-memory buffer.

By calling flush(), you flush the data in the buffer to disk (or network, or any other form of target medium).

It does not guarantee that they are actually written to a physical device such as a disk drive.

FileOutputStream
File content overwrite && append
	OutputStream output = new FileOutputStream("c:\\data\\output-text.txt".true);//appends to file  

// Overwrite by default
OutputStream output = new FileOutputStream("c:\\data\\output-text.txt".false);  //overwrites file 
Copy the code
BufferedOutputStream

Similar to BufferedInputStream, BufferedOutputStream can provide buffers for output streams.

OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\\data\\output-file.txt"));

Characters of the flow

Reader

Reader is the base class for character input streams and has the following subclasses.

  • BufferedReader
  • InputStreamReader
  • FileReader
  • FilterReader
  • PushbackReader
  • PipedReader
  • StringReader
  • CharArrayReader

A Reader is similar to an InputStream except that it is character based rather than byte based. In other words, Reader is used to read text, and InputStream is used to read raw bytes.

Reader#read

The InputStream#read() method returns one byte, meaning that the return value is in the range of 0 to 255 (-1 when the end of the stream is reached);

The Reader#read() method returns a character in the range 0 to 65535 (-1 is also returned when the end of the stream is reached).

This does not mean that readers only read two bytes at a time from the data source, but one or more bytes at a time, depending on the encoding of the text.

InputStreamReader

The original: A Java Reader can be combined with an InputStream. If you have an InputStream and want to read characters from it, you can wrap it in an InputStreamReader.

A Reader can be combined with an InputStream. If you have an InputStream InputStream and you want to read characters from it, you can wrap that InputStream in an InputStreamReader.

Reader reader = new InputStreamReader(inputStream);
Copy the code

Writer

Writer is the parent of all character output streams.

The subclass:

  • BufferedWriter
  • CharArrayWriter
  • FilterWriter
  • OutputStreamWriter
  • FileWriter
  • PipedWriter
  • PrintWriter
  • StringWriter
write(int c)

The write(int C) method of Writer writes the lower 16 bits of the passed parameter to Writer and ignores the higher 16 bits.

public void write(int c) throws IOException {
    synchronized (lock) {
        if (writeBuffer == null){
            writeBuffer = new char[WRITE_BUFFER_SIZE];
        }
        writeBuffer[0] = (char) c;
        write(writeBuffer, 0.1); }}Copy the code
OutputStreamWriter

tips: A Java Writer can be combined with an OutputStream just like Readers and InputStream‘s. Wrap the OutputStream in an OutputStreamWriter and all characters written to the Writer are passed on to the OutputStream.

such as : Writer writer = new OutputStreamWriter(outputStream);

Similar to Reader and InputStream, a Writer can be combined with an OutputStream. Wrap the OutputStream in the OutputStreamWriter. All characters written to the OutputStreamWriter will be passed to the OutputStream. (Character stream -> byte stream).

Combining Streams

The original: You can combine streams into chains to achieve more advanced input and output operations. For instance, reading every byte one at a time from a file is slow. It is faster to read a larger block of data from the disk and then iterate through that block byte for byte afterwards.

We can integrate streams to enable more advanced input and output operations.

For example, reading a byte at a time is slow (each time triggering a disk access, network activity, or some other relatively expensive operation), so you can read a chunk of data at a time from disk, and then get bytes from the read chunk.

InputStream input = new BufferedInputStream(
                        new FileInputStream("d:\\data\\input-file.txt"));
Copy the code

Buffering can also be applied to OutputStream. We can also write large chunks of data to disk (or stream) in batches, which is implemented by BufferedOutputStream.

Serialization and deserialization

Classes corresponding to serialized and deserialized objects must implement the Serializable interface.

Core class: ObjectInputStream && ObjectOutputStream

  • ObjectInputStream allows you to read Java objects from an input stream without having to read one byte at a time. (deserialization)

    You can wrap InputStream in ObjectInputStream and then read objects from it.

  • ObjectOutputStream allows you to write objects to the output stream without having to write one byte at a time. (serialization)

    You can wrap an OutputStream in an ObjectOutputStream, and then write objects to that OutputStream.

Traditional Java file systems

File object API –java.io.File

  1. Check whether the file exists

  2. Get file length

  3. Delete the file

  4. Check whether the path corresponds to a file or directory

    • True indicates that the path exists and is a directory
    • If false is returned, it is possible that the path does not exist instead of pointing to the file.
  5. Read a list of files in a directory

        String  printInfoPrefix = "[" + TEST_SOURCE_FILE_PATH + "] Path file";
        File    file            = new File(TEST_SOURCE_FILE_PATH);
        boolean exists          = file.exists();
        String  existInfo       = exists ? "There" : "Doesn't exist.";
        // [D:\gitRepository\organized-learning\deep-in-java\stage-8\ helloWorld.txt
        System.out.println(printInfoPrefix + existInfo);

        System.out.println(printInfoPrefix + "Length :[" + file.length() + "] bytes");

        // true indicates that the path exists and is a directory,
        // If false is returned it is possible that the path does not exist instead of pointing to the file.
        String pathInfo = file.isDirectory() ? "Directory" : "File";
        System.out.println("[" + TEST_SOURCE_FILE_PATH + "] The path corresponds to one" + pathInfo);

        // Get the path of the previous layer
        File parentFile = file.getParentFile();
        // Read the file or directory directly corresponding to the path
        String[] childFileArr = parentFile.list();
        for (String childFile : childFileArr) {
            // Iterate over the print
            System.out.println(childFile);
        }
Copy the code

[D:\gitRepository\organized-learning\deep-in-java\stage-8\ helloWorld.txt] The file corresponding to the path exists [D:\gitRepository\organized-learning\deep-in-java\stage-8\helloworld.txt] The file corresponding to the path is [12612] bytes [D:\gitRepository\organized-learning\deep-in-java\stage-8\helloworld.txt] The path corresponds to a file

file.test file.txt helloworld.txt output.txt outputchar.txt pom.xml stage-8.iml stage8-lesson1

FileThe shortcomings of class

Prior to the Java SE 7 release, the java.io.File class was the mechanism used for file I/O, but it had several drawbacks.

  • Many methods didn’t throw exceptions when they failed, so it was impossible to obtain a useful error message. For example, if a file deletion failed, the program would receive a “delete fail” but wouldn’t know if it was because the file didn’t exist, the user didn’t have permissions, or there was some other problem.
  • The rename method didn’t work consistently across platforms.
  • There was no real support for symbolic links.
  • More support for metadata was desired, such as file permissions, file owner, and other security attributes.
  • Accessing file metadata was inefficient.
  • Many of the Filemethods didn’t scale. Requesting a large directory listing over a server could result in a hang. Large directories could also cause memory resource problems, resulting in a denial of service.
  • It was not possible to write reliable code that could recursively walk a file tree and respond appropriately if there were circular symbolic links.

File system API –java.io.FileSystem

  • java.io.WinNTFileSystem

File descriptor –java.io.FileDescriptor

public static void main(String[] args) throws Exception {
        displayFileDescriptor(FileDescriptor.in);
        displayFileDescriptor(FileDescriptor.out);
        displayFileDescriptor(FileDescriptor.err);
    }

    private static void displayFileDescriptor(FileDescriptor fileDescriptor)
        throws NoSuchFieldException, IllegalAccessException {
        Integer fd     = getFieldValue(fileDescriptor, "fd");
        Long    handle = getFieldValue(fileDescriptor, "handle");
        Boolean closed = getFieldValue(fileDescriptor, "closed");
        System.out.printf("FileDescriptor=[ fd: %d , handle : %d , closed: %s]\n",fd,handle,closed);
    }

    private static <T> T getFieldValue(FileDescriptor fileDescriptor, String fieldName)
        throws NoSuchFieldException, IllegalAccessException {
        Field field = FileDescriptor.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        return (T)field.get(fileDescriptor);
    }
Copy the code

File input/output streams

  • FileInputStream
  • FileOutputStream

File filter

java.io.FileFilter

** * Calculates the space occupied by a directory **@author ajin
 */
public class DirectorySpaceDemo {

    /** * root directory ** /
    private final File            rootDirectory;
    / * * * {@link Predicate}
     * */
    private final Predicate<File> filter;

    public DirectorySpaceDemo(File rootDirectory, FileFilter... fileFilters) {
        this.rootDirectory = rootDirectory;
        this.filter = new FilePredicate(fileFilters);
    }


    private class FilePredicate implements Predicate<File> {

        private final FileFilter[] fileFilters;

        public FilePredicate(FileFilter[] fileFilters) {
            this.fileFilters = fileFilters;
        }

        @Override
        public boolean test(File file) {

            for (FileFilter fileFilter : fileFilters) {
                if(! fileFilter.accept(file)) {return false; }}return true; }}private interface FilePredicateAdapter extends Predicate<File>, FileFilter {

        @Override
        default boolean accept(File pathname) {
            returntest(pathname); }}/** * Get path size for example: d:\\test occupies memory 128KB */
    public long getSpace(a) {
        if (rootDirectory.isFile()) {
            return rootDirectory.length();
        } else if (rootDirectory.isDirectory()) {
            File[] subFiles = rootDirectory.listFiles();
            long   space    = 0L;
            if (null == subFiles) {
                return space;
            }
            // Add files in the current directory
            space += Stream.of(subFiles).filter(File::isFile).filter(filter).map(File::length).reduce(Long::sum).orElse(
                0L);

            // Recurse the current subdirectory
            space += Stream.of(subFiles).filter(File::isDirectory).filter(filter).map(DirectorySpaceDemo::new).map(
                DirectorySpaceDemo::getSpace).reduce(Long::sum).orElse(0L);

            return space;
        }
        return -1L;
    }

    public static long calculateSpace(File file) {
        Objects.requireNonNull(file);

        if (file.isFile()) {
            return file.length();
        } else if (file.isDirectory()) {

            long filesSpace = 0L;

            File[] subFiles = file.listFiles();
            if (null == subFiles) {
                return filesSpace;
            }

            for (File subFile : subFiles) {
                filesSpace += calculateSpace(subFile);
            }
            return filesSpace;
        }
        return -1L;
    }

    public static void main(String[] args) {

        System.out.println(
            new DirectorySpaceDemo(new File(System.getProperty("user.home")), file -> file.getName().endsWith(".log"))
                .getSpace() / 1024); }}Copy the code

java.io.FileNameFilter

@FunctionalInterface
public interface FilenameFilter {
  
    boolean accept(File dir, String name);
}
Copy the code

Functional interfaces, nothing special.

The resources

  1. Lesson: Basic I/O

  2. Java IO Tutorial

  3. Java IO tutorial

  4. Section 1 Java I/O flows

  5. Java File system

  6. Legacy File I/O Code