The Foundation of Programming Language--Making JavaIO

Posted by Elusid on Sun, 19 May 2019 15:09:18 +0200

Keyword: IO Foundation, JUnit Life Cycle, Byte Stream, Character Stream, Character Coding, Object Stream, Serialization, Deserialization

Java I/O

Streams are a set of sequential, starting and ending bytes. It is the general name and abstraction of data transmission between device files.

Device files involved in IO include files, consoles, network links and so on. According to the direction of flow, device files at both ends can be divided into data source objects and receiving objects.

  • Data Source Objects: Ability to produce data
  • Receiver Object: Ability to Receive Data

In fact, IO streams shield the details of data processing in real devices. These processing methods, also called communication methods, can include sequence, random access, buffer, binary, character by character, byte by line, etc.

Byte stream and character stream . By default, io operates directly on bytes, mostly for reading or writing binary data. The base classes of these classes are InputStream or OutputStream. The purpose of character stream operation is to support Unicode encoding for character internationalization. A character occupies two bytes. The base class of these classes is Reader or Writer. java io has added character stream support after jdk1.1, which makes it convenient for us to operate character stream directly.

  • Selection of Character Class Library and Byte Class Library

The best way to do this is to try to use Reader and Writer as much as possible. Once the program code is not compiled successfully, we will find that we have to use byte-oriented class libraries.

According to the flow direction of data, the flow is divided into input stream and output stream, which refers to memory, memory input is read, output is write, I read O write.

Java provides classes for processing flows in different situations so that we can intuitively manipulate data, which are in the io package of java. Here's a look at the classes of java io packages, which are widely used Decoration mode . In the actual use process, the constructors involved in multi-tier application will inevitably have their own decorative pattern structure, including the decorated class (the original construction base class), the decorated superclass, the decorated specific class. Knowing to distinguish and understand in the use process will make you more flexible to use them.

  • Node streams: File, Piped, and Array (each class includes four streams of input and output and byte characters, respectively)
  • Processing flow: The rest are processing classes. They belong to decorative classes of node flow. Next, I have organized a table about processing flow.
Processing flow who Number Decorative function
Buffer Classes that begin with Buffered 4((IO/BC) Can be operated in a buffer
Conversion flow InputStreamReader/OutputStreamWriter 2 Byte streams can be converted into character streams
Basic types DataXXXStream 2(IO replaces XXX) Transferable basic types of data
Object flow ObjectXXXStream 2(IO replaces XXX) Transferable object type data (serialization)
Printing flow PrintStream/PrintWriter 2 Output streams containing print and println
Merging flow SequenceInputStream 1 Logically cascaded other input streams
Line Number Read-in LineNumberReader 1 Get a character read-in stream with line numbers
Push back the input stream PushbackInputStream/PushbackReader 2 You can push back or unread an input stream byte
String read-write stream StringWriter/StringReader 2 Readable and Writable Strings in Buffers

Be careful:

  1. Default naming rules: byte stream is input/output, character stream is read/write (Writer/Reader), and when there are input/output and read/write, it must be input Stream Reader/Output Stream Writer.
  2. The default is operation bytes. All classes of operation characters need to convert bytes into character streams before using them.
  3. LineNumberInputStream is obsolete because it is based on byte input streams and incorrectly assumes that bytes adequately represent characters. It has now been replaced by LineNumberReader.
  4. String Buffer Input Stream is obsolete because it fails to properly convert characters to bytes and has been replaced by String Reader.
  5. There is also a concept of filter stream, which includes four streams of input and output byte characters, which I did not express above. It is an abstract class that serves as the interface for "decorators", where "decorators" provide useful functions for other input and output character byte classes.

We know that before adding the java io class library to the character stream processing class, all the classes are oriented to byte stream. After jdk1.1, we added the support of character stream, according to the "open-close principle", so without changing the original class, we have the transformation stream: InputStreamReader and OutputStreamWriter. These two classes are the so-called "adapter class". InputStreamReader can do it. Convert InputStream to Reader, while OutputStream Writer can convert OutputStream to Writer. Byte stream and character stream have their own inheritance hierarchy, and through adapter classes, they can be effectively combined without changing the original classes.

Java I/O class libraries need a combination of different functions. The reason for the existence of filter class is that abstract class filter is the base class of all decorator classes. Decorator must have the same interface as its decorated object. FilterInputStream and FilterOutputStream are two classes used to provide decorator class interfaces to control specific input streams and output streams. Their names are not very intuitive, including DataInput/OutputStream, BufferedInput/OutputStream, LineNumberInputStream, PushInputStream, PrintStream, etc. These classes are detailed below. Detailed introduction. FilterInputStream and FilterOutputStream are derived from the base classes InputStream and OutputStream in the I/O class library, respectively. These two classes are necessary for the decorator (in order to provide a common interface for all objects being decorated).

File/File Stream/Random Access File

Node stream is directly connected with data source to perform IO operation. Files are the most common data source. The following is about file-related content.

1.File

File classes can represent either files or folder directories. Just show a piece of code directly.

package javaS.IO;

+import java.io.File;

/**
 * Class java.io.File based on disk IO operation
 *
 * It can represent files or folder directories.
 *
 * @author Evsward
 *
 */
public class FileS extends IOBaseS {
    @Test
    public void testFileMethods() throws IOException {
        logger.info("Start testing file methods.");
        File file = new File(root);
        if (!file.exists())
            /**
             * Create directory mkdir();
             */
            file.mkdir();

        if (file.isDirectory()) {
            File file1 = new File(root+"/UME.txt");
            File file2 = new File(root+"/HongXing.txt");
            /**
             * Create the file createNewFile();
             */
            file1.createNewFile();
            file2.createNewFile();
            File file3 = new File(root+"/Cinema");
            file3.mkdir();
            /**
             * List all files (including files and directories) under the file path
             */
            File[] files = file.listFiles();
            for (File f : files) {
                /**
                 * Determine whether the file path is a directory
                 */
                if (f.isDirectory()) {
                    logger.info("The directory in 'Files' is: " + f.getName());
                } else {
                    logger.info("The file in 'Files' is: " + f.getName());
                }
                logger.info("Whose path is: " + f.getAbsolutePath());
            }
        } else {
            logger.info("FileS is not a directory!");
        }
        logger.info("Complete testing file methods.");
        /**
         * Output:
         * 15:12:56[testFileMethods]: Start testing file methods.
         * 15:12:56[testFileMethods]: The file in 'Files' is: HongXing.txt
         * 15:12:56[testFileMethods]: Whose path is: /home/work/github/mainbase/resource/StudyFile/HongXing.txt
         * 15:12:56[testFileMethods]: The directory in 'Files' is: Cinema
         * 15:12:56[testFileMethods]: Whose path is: /home/work/github/mainbase/resource/StudyFile/Cinema
         * 15:12:56[testFileMethods]: The file in 'Files' is: UME.txt
         * 15:12:56[testFileMethods]: Whose path is: /home/work/github/mainbase/resource/StudyFile/UME.txt
         * 15:12:56[testFileMethods]: Complete testing file methods.
         */
    }
    
}

For the convenience of the following tests, we add a static method to the FileS class to initialize the directory:

    /**
     * Clear a file so that we can test it.
     * 
     * @param filePath
     * @throws IOException
     */
    public static void initEV(String filePath) throws IOException {
        File f = new File(filePath);
        if (f.exists())
            f.delete();
        f.createNewFile();
    }

File then supports filter functions, such as listing the names of all text files in a directory.

    /**
     * Test file directory filters, such as listing all "*. txt" under the directory
     */
    public void testFileFilter() {
        logger.info("start testing file filter.");
        String filterStr = "(./*)+(txt)$";
        File file = new File(root);
        String list[] = file.list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return Pattern.matches(filterStr, name);
            }

        });
        for (String s : list)
            logger.info(s);
    }
    
    /**
     * Output:
     * 
     * 12:57:17[testFileFilter]: start testing file filter.
     * 
     * 12:57:17[testFileFilter]: HuaYi.txt
     * 
     * 12:57:17[testFileFilter]: HongXing.txt
     * 
     * 12:57:17[testFileFilter]: UME.txt
     */

Normal String list[] = file.list(); we can all use it, which means listing all the file names in the directory. If we want to filter these file names to list the text file names we only want, we need to create an implementation class object of FilenameFilter interface through anonymous inner class, which must implement its method, accept method, which is a callback function. Each found file name calls back the method, and whether it can be determined by the Boolean return value of accept is the same. Strategy mode Because the implementation of accept method body is left to us to customize, this function has a funnel function. We just want to get the name of the text file, so we can define a regular expression to filter it.

For regular expressions in java, blog articles will be written in the future.

File other methods of testing:

    public void testFileTool() {
        File file = new File(root);
        PPrint.pprint(file.listFiles());
        // Turn to List
        @SuppressWarnings("unused")
        List<File> fl = Arrays.asList(file.listFiles());
        logger.info("file.length(): " + file.length());
        logger.info("file.getName(): " + file.getName());
        logger.info("file.getParent(): " + file.getParent());
        // file.renameTo(new File("resource/S"); //rename file
        logger.info("file.canRead(): " + file.canRead());
        logger.info("file.canWrite(): " + file.canWrite());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:SS:SSS");
        Date a = new Date(file.lastModified());
        logger.info("file.lastModified(): " + sdf.format(a));
    }

Output:

[
resource/StudyFile/access
resource/StudyFile/HuaYi.txt
resource/StudyFile/HongXing.txt
resource/StudyFile/Cinema
resource/StudyFile/UME.txt
]
13:59:20[testFileTool]: file.length(): 4096
13:59:20[testFileTool]: file.getName(): StudyFile
13:59:20[testFileTool]: file.getParent(): resource
13:59:20[testFileTool]: file.canRead(): true
13:59:20[testFileTool]: file.canWrite(): true
13:59:20[testFileTool]: file.lastModified(): 2017-11-30 13:55:00:000

A practical printout class for manipulating arrays or lists, which we often write ourselves in business code.

package javaS.IO;

import java.util.Arrays;
import java.util.Collection;

public class PPrint {
    public static String pFormat(Collection<?> c) {// generic method
        if (c.size() == 0)
            return "[]";
        StringBuilder result = new StringBuilder("[");
        for (Object elem : c) {
            if (c.size() != 1) {
                result.append("\n");
            }
            result.append(elem);
        }
        if (c.size() != 1) {
            result.append("\n");
        }
        result.append("]");
        return result.toString();
    }

    /**
     * Print a visual collection
     * 
     * @param c
     */
    public static void pprint(Collection<?> c) {
        System.out.println(pFormat(c));
    }

    /**
     * Print a visual array
     * 
     * @param c
     */
    public static void pprint(Object[] c) {
        System.out.println(pFormat(Arrays.asList(c)));
    }
}

2. file flow

File stream includes four kinds of input and output character bytes. First, we will look at file byte stream processing.

package javaS.IO;

+import java.io.BufferedOutputStream;

/**
 * Byte stream learning
 * 
 * Base classes based on byte I/O operations: InputStream and OutputStream
 * 
 * Corresponding cache classes: BufferedInputStream and BufferedOutputStream
 * 
 * The subject of entry and exit is "memory". Out of memory is to write to the file, in memory is to read the file.
 * 
 * @author Evsward
 *
 */
public class ByteStreamS extends IOBaseS {
    @Test
    /**
     * Using the output stream OutputStream.write, write the contents of memory to the device file (File: disk file here)
     */
    public void testWrite2OutputStream() throws IOException {
        OutputStream fos = new FileOutputStream(root+"/UME.txt");//Failure to find this file will automatically create (including paths)
        /**
         * String content in content
         */
        String content = "Ha ha ha\n Hey";
        fos.write(content.getBytes());// Write bytes directly
        fos.close();// Close the flow after operation

        /**
         * Add the content after the file, and add the second parameter true to the constructor
         */
        OutputStream fosadd = new FileOutputStream(root+"/UME.txt", true);
        fosadd.write(" Hello".getBytes());
        fosadd.close();
    }

    @Test
    /**
     * Use the input stream to read InputStream.read and read the device file (here the disk file is File) into the memory buffer.
     */
    public void testRead2InputStream() throws IOException {
        int bufferSize = 200;
        FileInputStream fis = new FileInputStream(root+"/UME.txt");
        byte buffer[] = new byte[bufferSize];
        int length;
        while ((length = fis.read(buffer, 0, bufferSize)) > -1) {
            String str = new String(buffer, 0, length);
            logger.info(str);
        }
        fis.close();// Close the flow after operation
        /**
         * Output:
         * 13:41:02[testInputStreamS]: To invite the moonlight in front of the moonbed
         */
    }
}
  • The Role of Buffered Buffer

The data stream can be processed from the data source and stored in the memory buffer. Then it can be operated with the underlying IO at one time, which can effectively reduce the frequency of direct IO operation and improve the speed of IO execution. The following is the use of buffer processing streams.

    /**
     * Buffered Input Stream, Buffered Output Stream, Buffered Reader, Buffered Writer,
     * One-time write to reduce the frequency of IO occupancy
     * Avoid dealing with hard disk every time, and improve the efficiency of data access.
     */
    @Test
    public void testWrite2BufferedOutputStream() throws IOException {
        // OutputStream is the base class
        OutputStream fosaddOnce = new FileOutputStream(root+"/UME.txt");
        OutputStream bs = new BufferedOutputStream(fosaddOnce);
        bs.write("A toast to the moon".getBytes());
        bs.flush();// Every time flush brushes in-memory data into an external file, it does not close the stream.
        bs.write("Abed, I see a silver light".getBytes());
        /**
         * close In addition to the function of closing the flow, the method also performs a flush before closing the flow.
         * Be sure to close Buffered Output Stream first, then FileOutput Stream, from outside to inside, and from inside to outside.
         */
        bs.close();
        fosaddOnce.close();// Both streams are closed
    }
  • Optimizing code using constructor sockets

We seldom use a single class to create stream objects, but provide the desired functionality by overlapping multiple objects. This is Decorator mode)

    public void testWrite2BufferedOutputStream() throws IOException {
        OutputStream bs = new BufferedOutputStream(new FileOutputStream(root+"/UME.txt"));
        bs.write("A toast to the moon".getBytes());
        bs.close();//Close it only once.
    }

Now let's look at the processing of character stream, which also includes the application of FileWriter/FileReader.

package javaS.IO;

+import java.io.BufferedReader;

/**
 * Character Stream Learning
 * 
 * Base Classes Based on Character I/O Operations: Reader and Writer
 * 
 * Corresponding cache classes: BufferedReader and BufferedWriter
 * 
 * @author Evsward
 *
 */
public class CharacterStreamS extends IOBaseS {
    @Test
    /**
     * OutputStreamWriter,Byte to character conversion bridge, the conversion process needs to specify the encoding character set, otherwise the default character set.
     */
    public void testWriter() throws IOException {
        // File output stream remains unchanged
        FileOutputStream fos = new FileOutputStream(root + "HongXing.txt");
        /**
         * Output stream Write classes (which are more than byte streams) are specifically used to write character streams, paying attention to the parameters of character encoding.
         * 
         * If only one parameter of fos is retained, the encoding defaults to the workspace default encoding, which is "UTF-8".
         * 
         * Byte encoding as character - > Go to http://www.cnblogs.com/Evsward/p/huffman.html#ascii encoding
         * 
         * To ensure uniform coding for writing and reading, specify the coding each time.
         * 
         * Two conversion streams provided in the output system are used to convert byte streams into character streams.
         */
        OutputStreamWriter osw = new OutputStreamWriter(fos);
        // Cache write class, corresponding to BufferedOutputStream
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("Tears splash when feeling, and birds are frightened when hating goodbye.");
        bw.close();
        osw.close();
        fos.close();

        /**
         * Final Edition: Shorten the close part
         */
        BufferedWriter bwA = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(root + "HongXing.txt", true), "UTF-8"));// Pay attention to the hierarchy and add the specified encoding parameters
        bwA.write("\n Beaming fire in March, home letters worth ten thousand dollars");
        bwA.close();
    }

    @Test
    /**
     * InputStreamReader,Byte to character conversion bridge, the conversion process needs to specify the encoding character set, otherwise the default character set.
     */
    public void testReader() throws IOException {
        FileInputStream fis = new FileInputStream(root + "HongXing.txt");
        /**
         * Output stream read classes (which are more than byte streams) are specifically designed to read character streams. Note that the parameters of character encoding should be the same as those of writing, otherwise it will scramble.
         * 
         * Two conversion streams provided in the input system are used to convert byte streams into character streams.
         */
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        BufferedReader br = new BufferedReader(isr);
        String str;// StringBuilder can also be used here to accumulate all the contents of the file, and finally output.
        while ((str = br.readLine()) != null) {
            logger.info(str);
        }
        br.close();// Display calling close method to close the flow
        isr.close();
        fis.close();

        /**
         * Final Edition: Shorten the close part
         */
        BufferedReader brA = new BufferedReader(
                new InputStreamReader(new FileInputStream(root + "HongXing.txt"), "UTF-8"));
        String strA;
        while ((strA = brA.readLine()) != null) {
            logger.info(strA);
        }
        brA.close();

        /**
         * Output: 15:04:07[testReader]: Tears splashed when feeling, and hate goodbye bird startled at 15:04:07[testReader]:
         * Beaming fire in March, home letters worth ten thousand dollars
         */
    }

    /**
     * File Encapsulation classes supporting character reading and writing are provided: FileWriter and FileReader
     * So you don't have to use InputStreamReader and OutputStreamWriter to convert every time.
     */

    @Test
    public void testFileWriter() throws IOException {
        FileWriter fw = new FileWriter(root + "HongXing.txt", true);
        fw.write("\n Du Fu's Spring Outlook");
        fw.close();
    }

    @Test
    public void testFileReader() throws IOException {
        FileReader fr = new FileReader(root + "HongXing.txt");
        // FileReader's direct read method is not as convenient as readLine, so the decorative BufferedReader can borrow its readLine for use.
        BufferedReader br = new BufferedReader(fr);
        String str;
        while ((str = br.readLine()) != null) {
            logger.info(str);
        }
        br.close();
        fr.close();
    }
    
}

Note: File provides encapsulation classes that support character reading and writing: FileWriter and FileReader, so you don't have to use InputStreamReader and OutputStreamWriter to convert each time.

3.RandomAccessFile

Random Access File is the meaning of entering a file at any location. It applies to a file consisting of records of known size. It has a look method that defines the location of the file. Therefore, it is important to remember the location and size of the file's content when performing Random Access File operations on the file, otherwise the result of content copy changes will occur.

package javaS.IO;

+import java.io.File;

/**
 * RandomAccessFile: There is a pointer seek, a class that operates anywhere on a file
 * 
 * @author Evsward
 *
 */
public class RandomAccessFileS extends IOBaseS {
    @Before
    public void testWrite2RAFile() throws IOException {
        FileS.initEV(root + "/access");// First empty the access file.
        RandomAccessFile raf = new RandomAccessFile(root + "/access", "rw");// rw opens files by reading and writing
        logger.info(raf.length());
        Student Jhon = new Student(1001, "Jhon", 26, 1.85d);
        Student Jack = new Student(1002, "Jack", 25, 1.75d);
        Jhon.write(raf);// After writing to a file, the pointer ends at the end of the current text
        // The current seek starts with seek(raf.length)
        logger.info(raf.length());
        Jack.write(raf);// Continue to write, the pointer continues to move to the end, equivalent to the addition
        logger.info(raf.length());
        raf.close();
    }

    @After
    public void testReadRAFile() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(root + "/access", "r");
        // When acquiring raf, seed is at the beginning of the file
        logger.info(raf.length());
        Student Lily = new Student();
        Lily.read(raf);
        logger.info(Lily);
        Lily.read(raf);
        logger.info(Lily);
        // The number of reads is limited. You must know in advance how many times to read, otherwise you will report to EOFException.
        Lily.read(raf);
        logger.info(Lily);
        raf.close();
        /**
         * Output: 16:14:30[testReadRAFile]: id:1001 name:Jhon age:26 height:1.85
         */
    }
}

Random Access File is like a database, which writes data one by one, and these data must be class objects, not basic type data, because this kind of object writes two methods, write and read, to write objects to Random Access File. These two methods are not inherited from anyone, they can be called by themselves.

public class Student extends IOBaseS{
    private long id;
    private String name;
    private int age;
    private double height;

    public Student() {
    }

    public Student(long id, String name, int age, double height) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public void write(RandomAccessFile raf) throws IOException {
        raf.writeLong(id);
        raf.writeUTF(name);// Write string by UTF encoding
        raf.writeInt(age);
        raf.writeDouble(height);
    }

    /**
     * Read strictly in writing order, which is also the meaning of ORM
     * 
     * @param raf
     * @throws IOException
     */
    public void read(RandomAccessFile raf) throws IOException {
        this.id = raf.readLong();
        this.name = raf.readUTF();
        this.age = raf.readInt();
        this.height = raf.readDouble();
    }
}

According to the structure of the class, the fields are written to any position of the file in order, and read out in the same order.

  • Random Access File Additional Content
    When appending, we need to use the seek method to locate the cursor at the end of the file. If we do not relocate, we will overwrite the original data by default from the start.
/**
     * Additional content
     */
    public void testWriteAppend2RAFile() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(root + "/access", "rw");// rw opens files by reading and writing
        Student Mason = new Student(1003, "Mason", 26, 1.82d);// The "Mason" here is one more character than the two data above.
        // Additional content needs to be adjusted to raf.length first, and then to start adding content.
        raf.seek(raf.length());
        Mason.write(raf);
        logger.info(raf.length());
        raf.close();
    }
  • Random AccessFile inserts data anywhere
    @Test
    public void insert() {
        Student Hudson = new Student(1005, "Hudson", 45, 1.76d);
        try {
            insert(root + "/access", 26, Hudson);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * Insert data at the specified location in Random Access File, first put the data behind the location into the buffer, then write it back after inserting the data.
     * 
     * @param file
     *            The path to find the file is the string type
     * @param position
     *            In fact, it is difficult to locate this location when external calls are made, because if the length of the data is uncertain, it will cause confusion if the data is not broken up properly.
     * @param content
     */
    public void insert(String file, long position, Student s) throws IOException {
        /**
         * Create a temporary file
         * 
         * Delete it after use
         */
        File tempFile = File.createTempFile("temp", null);
        tempFile.deleteOnExit();
        FileOutputStream fos = new FileOutputStream("temp");
        /**
         * Cache the data after the insertion location to a temporary file
         */
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        raf.seek(position);
        byte[] buffer = new byte[20];
        while (raf.read(buffer) > -1) {
            fos.write(buffer);
        }
        raf.seek(position);
        /**
         * Write inserts to Random AccessFile
         */
        s.write(raf);
        /**
         * Write back cached data from temporary files to Random Access File
         */
        FileInputStream fis = new FileInputStream("temp");
        while (fis.read(buffer) > -1) {
            raf.write(buffer);
        }
        fos.close();
        fis.close();
        raf.close();
        tempFile.delete();//Delete temporary file tempFile
    }

When inserting data, Random Access File does not provide a relevant method. Since writing data in an old location will overwrite the original data, we need to cache the data after the insertion location into a temporary file, and then insert the contents of the temporary file after the inserted data.

  • JUnit Life Cycle
    Complete JUnit execution sequence:
  1. @BeforeClass –> @Before –> @Test –> @After –> @AfterClass
  2. @ BeforeClass and @AfterClass are executed only once and must be static void
  3. @ Ignore skips this method when executing the entire class

Next, we define a complete test process: first initialize a blank file, then add two rows of data Jhon and Jack, then insert Hudson between them, and finally read out the file data. The verification results are output as follows:

    /**
     * output: 
     * 17:10:31[testWrite2RAFile]: 0 17:10:31[testWrite2RAFile]: 26
     * 17:10:31[testWrite2RAFile]: 52 17:10:31[testReadRAFile]: 94
     * 17:10:31[testReadRAFile]: id:1001 name:Jhon age:26 height:1.85
     * 17:10:31[testReadRAFile]: id:1005 name:Hudson age:45 height:1.76
     * 17:10:31[testReadRAFile]: id:1002 name:Jack age:25 height:1.75
     */

II. Data Output Stream / Data Input Stream

This pair of classes can be written directly to the basic type data of java (without String), but can not be directly viewed as a binary file after writing. DataOutputStream / DataInputStream is a commonly used filter stream class. If the serialization of an object is to convert the entire object into a byte sequence, DataOutputStream / DataInputStream is to serialize the field into binary data. Let's look at the code.

package javaS.IO;

+import java.io.BufferedInputStream;

public class DataStream extends IOBaseS {

    @Test
    /**
     * DataOutputStream,You can write java basic type data directly (without String), but after writing it is in the form of a binary file, you can not view it directly.
     * 
     * Text file is a special form of binary file, which is realized by dump. Please go to the relevant content.
     * http://www.cnblogs.com/Evsward/p/huffman.html#Binary dump
     */
    public void testWrite2DataOutputStream() throws IOException {
        OutputStream fosaddOnce = new FileOutputStream(root + "/UME.txt");
        OutputStream bs = new BufferedOutputStream(fosaddOnce);
        DataOutputStream dos = new DataOutputStream(bs);
        dos.writeInt(22);
        dos.writeShort(1222222222);
        dos.writeLong(20L);
        dos.writeByte(3);
        dos.writeChar(42);
        dos.close();
        bs.close();
        fosaddOnce.close();
        /**
         * Final Edition: The close stage above should be closed three times from inside to outside, which is more troublesome. The standard writing method of decorative mode is adopted directly below to connect objects.
         * Socket object: The innermost must be the node flow, and all the layers outside it are the processing flow.
         * FileOutputStream:It belongs to the node flow. Other node flows include pipes and arrays. The rest is the processing flow.
         * BufferedOutputStream:Buffer technology (also part of processing flow) DataOutputStream: Processing flow
         */
        DataOutputStream dosA = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(root + "/UME.txt")));
        dosA.writeInt(22);
        dosA.writeShort(65538);// Data Output Stream does not check whether the data crosses the boundaries. The data crossing the boundaries is intercepted in binary mode, and only the data within the boundaries are retained.
        dosA.writeLong(20L);
        dosA.writeByte(3);
        dosA.writeChar(42);
        dosA.writeDouble(3.1415926);
        dosA.close();// Close only once.
    }

    @Test
    /**
     * To read binary files through DataInputStream processing stream, it is necessary to read the contents of basic types of java files in the order of writing. Otherwise, there will be random code or inaccurate information.
     */
    public void testRead2DataInputStream() throws IOException {
        DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(root + "/UME.txt")));
        logger.info(dis.readInt());
        /**
         * Even if the tree is saved into the crossing tree 65538, no error will be reported, because the excess part will not be saved, only the excess part will be saved.
         * short Types occupy 16 bits of space, so the 65538 is converted to binary numbers, the part beyond 16 bits is automatically truncated, only the data within 16 is retained, so it becomes 2.
         */
        logger.info(dis.readShort());
        logger.info(dis.readLong());
        logger.info(dis.readByte());
        logger.info(dis.readChar());
        logger.info(dis.readDouble());
        dis.close();
        /**
         * Output: 
         * 13:39:03[testDataInputStream]: 22 
         * 13:39:03[testDataInputStream]: 2 
         * 13:39:03[testDataInputStream]: 20 
         * 13:39:03[testDataInputStream]: 3
         * 13:39:03[testDataInputStream]: *
         * 13:39:03[testDataInputStream]: 3.1415926
         */
    }

}

The annotations are quite complete, and there is no more elaboration here.

Object streaming/serialization

In the process of data transmission, binary files will be used by default, because the bottom recognition mode of computer is binary, which does not depend on any running environment or programming language, so it is the basis of data transmission across platforms and networks. Serialization can directly convert a java object into a byte sequence, and can completely restore the byte sequence to the original object in the future. This process can even be carried out through the network, which means that serialization mechanism can automatically compensate for the differences between different operating systems.

If an object of a class is to be serialized successfully, it must implement the Serializable interface, which looks like Cloneable interface Similarly, they are just an empty identification interface.

The following paragraph to describe object serialization is really enlightening.

When you create an object, it will always exist as long as you need it, but it will never continue to exist when the program terminates. Although it is certainly meaningful to do so, there are still some situations where it would be very useful if an object could still exist and preserve its information without running the program. In this way, the object will be rebuilt the next time the program runs and has the same information as it did the last time the program ran. Of course, you can achieve the same effect by writing information into files or databases, but in the object-oriented spirit of making everything object, it would be very convenient to declare an object as "persistent" and to deal with all the details for us.

Compared with the heavyweight persistence of database storage, object serialization can achieve lightweight persistence.

Persistence means that the life cycle of an object does not depend on whether the program is executing or not. It can survive between calls of the program.

Lightweight is because you can't simply define an object with a keyword and let the system automatically maintain other details. Instead, objects must be serialized and deserialized visually in the program.

If you need a more rigorous persistence mechanism, consider tools like Hibernate. TODO: There will be an in-depth article on Hibernate.

The meaning of object serialization:

  • java-enabled remote method calls RMI, which makes objects that survive on other computers use as if they survive on the machine.
  • For Java beans, it is necessary to save the configuration of their status information in the design phase and restore it later when the program starts. The specific work is done by object serialization.
+package javaS.IO;

/**
 * Serializable For markup interfaces, objects representing this class can be serialized.
 * 
 * @author Evsward
 *
 */
public class Student extends IOBaseS implements Serializable {
    /**
     * Declarations in classes
     * 
     * transient Variables with static will not be serialized
     */

    /**
     * Sequence number: Avoid repetitive serialization
     * If the serial Version UID is modified, deserialization will fail.
     * When a program attempts to serialize an object, it checks whether the object has been serialized. Only when the object has never been serialized (in this virtual machine), will the system convert the object into a byte sequence and output it.
     */
    private static final long serialVersionUID = -6861464712478477441L;
    private long id;
    private String name;
    private int age;
    private transient double height;

    public Student() {
    }

    public Student(long id, String name, int age, double height) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("id:");
        sb.append(this.id);
        sb.append(" ");
        sb.append("name:");
        sb.append(this.name);
        sb.append(" ");
        sb.append("age:");
        sb.append(this.age);
        sb.append(" ");
        sb.append("height:");
        sb.append(this.height);
        return sb.toString();
    }

    /**
     * This is equivalent to rewriting the ObjectOutputStream.writeObject method, which is called when ObjectOutputStream writes to the object.
     * 
     * Function: Data can be encrypted in a customized way during serialization
     * 
     * Reference source code:
     * 
     * public final void writeObject(Object obj) throws IOException {
            if (enableOverride) {// If it is found that the parameter Object has overridden the method, the overridden method is executed, otherwise the local method is continued.
                writeObjectOverride(obj);
                return;
            }
            try {
                writeObject0(obj, false);
            } catch (IOException ex) {
                if (depth == 0) {
                    writeFatalException(ex);
                }
                throw ex;
            }
        }
     * 
     * 
     * readObject The analysis of the method is the same as the above.
     * 
     * @param out
     * @throws IOException
     */
    private final void writeObject(ObjectOutputStream out) throws IOException {
        logger.info("Start writing data to Object.");
        out.writeLong(this.id);
        /**
         * The following writeObject is in the StringBuffer source code:
         * 
         * readObject is called to restore the state of the StringBuffer from a stream.
            private synchronized void writeObject(java.io.ObjectOutputStream s)
                    throws java.io.IOException {
                java.io.ObjectOutputStream.PutField fields = s.putFields();
                fields.put("value", value);
                fields.put("count", count);
                fields.put("shared", false);
                s.writeFields();
            }
         */
        out.writeObject(new StringBuffer(name));
        out.writeInt(this.age);
        out.writeDouble(this.height);// After rewriting here, the transient s settings are ignored.
    }

    private final void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        logger.info("Start reading data to Object.");
        this.id = in.readLong();
        /**
         * The following readObject is in the StringBuffer source code:
         * 
         *  readObject is called to restore the state of the StringBuffer from a stream.
            private void readObject(java.io.ObjectInputStream s)
                    throws java.io.IOException, ClassNotFoundException {
                java.io.ObjectInputStream.GetField fields = s.readFields();
                value = (char[])fields.get("value", null);
                count = fields.get("count", 0);
            }
         */
        this.name = ((StringBuffer) in.readObject()).toString();
        this.age = in.readInt();
        this.height = in.readDouble();
    }

}

This class has a lot of content, because in order to better analyze, I pasted the source code of jdk into the comment area. But it doesn't matter. At the end of the article, there will be the source code location. Interested students can download the source code to see. In this class, we rewrite the readObject and writeObject methods. When external ObjectXXXStream calls, it first finds whether there are readObject and writeObject methods rewritten in the object class, and if there are no, it calls its own default implementation method.

package javaS.IO;

+import java.io.FileInputStream;

/**
 * Research object serialization (cross-platform, cross-network basis)
 * 
 * Memory - > Disk / Network
 * 
 * Java Object - > binary file
 * 
 * @author Evsward
 *
 */
public class ObjectStreamS extends IOBaseS {
    /**
     * ObjectOutputStream:Object flow (object serialization), unlike DataOutputStream, is the basic type of operation.
     * Testing Serialized Object Storage Architecture
     */
    @Test
    public void testWriteSerialObject() throws IOException {
        FileS.initEV(root + "/access");// Clear the access file first.
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(root + "/access"));
        Student Lu = new Student(2001, "Luxa", 31, 1.81d);
        // Different serialized object data can be written, but the order of writing is recorded
        oos.writeObject(Lu);
        oos.close();
        /**
         * access Content: Since a binary file is written, the opening is scrambled.
         * 
         * ¬í^@^Esr^@^PjavaS.IO.Student Ç.2<95>׳^?^B^@^DI^@^CageD^@^FheightJ^@^BidL^@^Dnamet^@^RLjava/lang/String;xp^@^@^@^_?üõÂ<8f>\(ö^@^@^@^@^@^@^GÑt^@^DLuxa
         */
    }

    /**
     * ObjectInputStream:Object deserialization
     * Read binary files (serial files)
     * 
     * @throws IOException
     * @throws ClassNotFoundException
     */
    @Test
    public void testReadSerialObject() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(root + "/access"));
        // You can read data from different objects, but in writing order.
        Student s = (Student) ois.readObject();
        logger.info(s);
        ois.close();
        /**
         * ①Output:
         * 
         * 10:24:08[testReadSerialObject]: id:2001 name:Luxa age:31 height:1.81
         * 
         * ②If the height attribute variable is declared transient, it will not be written in the serialization process as the initial value. Output is:
         * 
         * 10:29:34[testReadSerialObject]: id:2001 name:Luxa age:31 height:0.0
         */
    }

}

transient variables can identify fields that are not serialized, and these fields may carry sensitive information, such as passwords. But after we rewrote the writeObject and readObject methods, the keyword didn't work well.

Suggestions for the use of serialization:

  • Careful implementation of Serializable interfaces requires that the timeliness and consistency of uuid be maintained at all times. Inheritance between serialized and non-serialized classes should be avoided in the structural framework.
  • Consider implementing custom serialization forms, as we did in the code above.
  • The protective method of writing readObject. Adding exception handling, the terminal of invalid serialized object throws exception during deserialization.

PrintStream/PrintWriter

First, what is the standard input stream for java? It's InputStream, right. So what is the standard output stream of java? Is it Output Steam? No! It's PrintStream.

Because the standard input and output stream is the definition of the System class, there are three fields in the System. In is the InputStream type, corresponding to the standard input stream, err and out are PrintStream objects, and out is the standard output stream. The return value of our commonly used System.out.println(data) method is the PrintStream object. The default output of this stream is in the console, or it can redirect the output location:

System.setOut(new PrintStream(new FileOutputStream(file)));
System.out.println("These contents can only be found in file Object file to see oh!");

PrintWriter is the character operation version of PrintStream. PrintStream operates on byte streams. If you want to operate on character streams, you can use PrintWriter. Let's look directly at the comments on the code.

package javaS.IO;

+import java.io.FileOutputStream;

public class PrintStreamS extends IOBaseS {

    /**
     * The use of print streams is very similar to FileWriter, but it supports more methods and has a wealth of construction methods.
     */
    @Test
    public void testPrintStream() throws IOException {
        FileS.initEV(root + "HongXing.txt");
        // PrintStream p = new PrintStream(root + "HongXing.txt");
        // PrintStream p = new PrintStream(new File(root + "HongXing.txt"));
        // PrintStream p = new PrintStream(new FileOutputStream(root +
        // "HongXing.txt"), true, "UTF-8");
        PrintStream p = System.out;// Data source switch to console, standard output, equivalent to System.out.xxx();
        p.append("Pale Moon rises");
        p.println("Moisten things silently");
        p.print("When spring comes");
        p.write("Enemy without enemy".getBytes());
        p.flush();// Brush in memory data to data source
        System.out.write("asdfas".getBytes());
        p.close();

        /**
         * Output:
         * 
         * The moistening moonlight on the sea is silent
         * 
         * When spring comes, there is no rivalry.
         */
    }

    /**
     * PrintWriter Two differences with PrintStream:
     * 
     * write One method is to write bytes and the other is to write characters.
     * 
     * Generally speaking, PrintStream is used more.
     */
    @Test // If you forget to write the comment, executing JUnit will result in an error initialization Error
    public void testPrintWriter() throws IOException {
        FileS.initEV(root + "HongXing.txt");
        // PrintWriter p = new PrintWriter(root + "HongXing.txt");
        // PrintWriter p = new PrintWriter(new File(root + "HongXing.txt"));
        // The second parameter is autoflush, and println, printf, and format automatically execute flush if true.
        // PrintWriter p = new PrintWriter(new FileOutputStream(root + "HongXing.txt"), true);
        System.setOut(new PrintStream(new FileOutputStream(root + "HongXing.txt")));// Output redirection, from the default console to the file, is also the basic idea of the log system.
        PrintWriter p = new PrintWriter(System.out, true);// Change PrintWriter's printing position to standard output
        p.append("Pale Moon rises");
        p.println("Moisten things silently");
        p.print("When spring comes");
        p.write("Enemy without enemy");// This is the only difference from PrintStream.
        p.flush();// PrintWriter also supports BRUSH-IN operations
        p.close();
    }

    /**
     * Test Standard Input and Output
     */
    public void testStandardIO() throws IOException {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        String str;
        while ((str = stdin.readLine()) != null && str.length() != 0)
            logger.info(str);
    }
}

Sequence Input Stream

There are two constructors for merging streams:

  • Objects of two InputStream types are passed in
  • Pass in a collection of enumerated InputStream-type objects and combine them for operation

After merging streams, the operation can be read and write to another file or print to the console. Look at the code below:

package javaS.IO;

+import java.io.FileInputStream;

public class SequenceInputStreamS extends IOBaseS {

    /**
     * Merge two read-in byte streams
     * 
     * @throws IOException
     */
    @Test
    public void testSequenceInputStream() throws IOException {
        // buffer space should be set to the second power to effectively segment, otherwise, a Chinese character will be partitioned and displayed incompletely in the middle.
        int bufferSize = 16;
        InputStream is1 = new FileInputStream(root + "UME.txt");
        InputStream is2 = new FileInputStream(root + "HongXing.txt");
        SequenceInputStream sis = new SequenceInputStream(is1, is2);// The construction parameter must be InputStream
        byte[] buffer = new byte[bufferSize];
        while (sis.read(buffer, 0, bufferSize) != -1) {
            // Start reading merged data streams, where you can do anything about these data streams (read and write to any file or print to the console)
            String str = new String(buffer, 0, bufferSize);
            logger.info(str);// Print to console
        }
        is1.close();
        is2.close();
        sis.close();
    }

    @Test
    public void testMergeEnumInputStream() throws IOException {
        // In fact, it can merge different types of data. However, if it is an object stream, it involves deserialization when reading. It is difficult to find the segmentation points with other data.
        InputStream is1 = new FileInputStream(root + "UME.txt");
        InputStream is2 = new FileInputStream(root + "HongXing.txt");
        InputStream is3 = new FileInputStream(root + "HuaYi.txt");
        ArrayList<InputStream> list = new ArrayList<InputStream>();
        list.add(is1);
        list.add(is2);
        list.add(is3);
        Iterator<InputStream> it = list.iterator();// Pass in an iterator to create an enumeration
        SequenceInputStream sis = new SequenceInputStream(new Enumeration<InputStream>() {
            @Override
            public boolean hasMoreElements() {
                return it.hasNext();
            }

            @Override
            public InputStream nextElement() {
                return it.next();
            }
        });
        int bufferSize = 32;
        byte[] buffer = new byte[bufferSize];
        while (sis.read(buffer, 0, bufferSize) != -1) {
            // Start reading merged data streams, where you can do anything about these data streams (read and write to any file or print to the console)
            String str = new String(buffer, 0, bufferSize);
            logger.info(str);// Print to console
        }
        is1.close();
        is2.close();
        is3.close();
        sis.close();
    }
}

LineNumberReader

LineNumberReader is a line number that can be added to the stream of characters read in. Let's look at the code below.

package javaS.IO;

import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;

import org.junit.Test;

public class LineNumberReaderS extends IOBaseS {

    @Test
    public void testLineNumberReader() throws IOException {
        FileReader fr = new FileReader(root + "UME.txt");
        // The construction parameter is Reader
        LineNumberReader lnr = new LineNumberReader(fr);
        lnr.setLineNumber(1);// Set the line number from 2.
        String str;
        while ((str = lnr.readLine()) != null) {
            // Core method: lnr.getLineNumber(), get line number
            logger.info("Line number:" + lnr.getLineNumber() + " Content:" + str);
        }
        fr.close();
        lnr.close();
        /**
         * Output:
         * 
         * 12:11:27[testLineNumberReader]: Line Number: 2 Contents: Raise a glass to invite the moon
         * 
         * 12:11:27[testLineNumberReader]: Line number: 3 Contents: Bright moonlight in front of bed
         */
    }
}

Array IO/PushbackInputStream/PushbackReader

Push a single character back into the input stream. In this paper, the method of reading array is adopted, which is also divided into bytes and characters. The byte array stream is ByteArray InputStream and the character array stream is CharArray Reader.

package javaS.IO;

import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.PushbackReader;

import org.junit.Test;

public class PushBackS extends IOBaseS {
    @Test
    public void testPushbackInputStream() throws IOException {
        String content = "Superman VS Batman";
        // Construct parameters as an array of bytes
        ByteArrayInputStream bais = new ByteArrayInputStream(content.getBytes());
        // The construction parameter is an InputStream object.
        PushbackInputStream pbis = new PushbackInputStream(bais);
        pbis.unread("Ssdfasdf".getBytes(), 0, 1);// Push S to the front of the source string
        // pr.unread('S'); // Here'S'is operated on by integer values
        int n;
        String str = "";
        while ((n = pbis.read()) != -1) {
            str += (char) n;
            // pbis.unread(n); if you push back the character you just read, it will die.
        }
        logger.info(str);
        pbis.close();
        bais.close();
        /**
         * Output:
         * 
         * 12:32:48[testPushBackInputStream]: SSuperman VS Batman
         */
    }

    @Test
    /**
     * PushbackInputStream Character Stream Version
     */
    public void testPushbackReader() throws IOException {
        // Construct parameter as Reader object, read with character array
        PushbackReader pr = new PushbackReader(new CharArrayReader("go go Gan.".toCharArray()));
        pr.unread("Ssdfasdf".toCharArray(), 0, 1);// Push S to the front of the source string
        // pr.unread('S'); // Here'S'is operated on by integer values
        int n;
        String str = "";
        while ((n = pr.read()) != -1) {
            str += (char) n;
            // pr.unread(n); if you push back the character you just read, it will die.
        }
        logger.info(str);
        pr.close();
        /**
         * Output:
         * 
         * 12:45:55[testPushbackReader]: Sgo go Gan.
         */
    }

}

8. StringWriter / StringReader

There's not much to say, that is, read-write operations on a string are seldom used alone, because String can be used directly to replace them, but when a stream is needed, it can be combined with other IO streams.

package javaS.IO;

+import java.io.IOException;

public class StringIOS extends IOBaseS {

    @Test
    public void testStringWriter() throws IOException {
        StringWriter sw = new StringWriter();
        sw.write("Hello");
        sw.append("A");
        sw.close();
        logger.info(sw);
        
        StringReader sr = new StringReader("Hello");
        int c;
        StringBuilder sb = new StringBuilder();
        while ((c = sr.read()) != -1) {
            sb.append((char)c);
        }
        logger.info(sb);
        /**
         * Output:
         * 
         * 12:56:47[testStringWriter]: HelloA
         * 
         * 12:56:47[testStringWriter]: Hello
         */
    }
}

Summary of java IO Foundation

  • With regard to node flows, the above section shows the use of file character byte input and output streams and various streams of arrays.

    This article only has no pipeline related content, pipeline part will be studied in NIO.

  • For processing streams, we show buffers, object streams, basic data streams, transformation streams, print streams, merge streams, line number read-in streams, push-back input streams, and string read-write streams.

Reference material

  • Java Programming Thought
  • <effective java>
  • JDK API Document

Source location

Evsward's github

For more information, please go to Wake-up Blog Garden

Topics: Java encoding Junit network