Java Stream Stream Object (Practical Tips)

Table of contents

一、InputStream & OutputStream

1.1. InputStream and OutputStream are generally used

1.2. Special use

1.2.1. How to indicate that the file has been read? (DataInputStream)

1.2.2. Character reading/text data reading (Scanner)

1.2.3. Random reading and writing of files (RandomAccessFile)


一、InputStream & OutputStream


1.1. InputStream and OutputStream are generally used

InputStream has the following methods:

  1. int read(): Read one byte of data, returning -1 means it has been completely read.
  2. int read(byte[] b): Read up to b.length bytes of data into b, and return the actual number read; -1 means the reading is finished (this is like you carrying a basin to the canteen If you ask your aunt to prepare the meal, then the aunt will definitely fill it for you according to the amount of her meal. If she can fill it for you, she will try her best to fill it for you). This is also a more commonly used method in practice.
  3. int read(byte[] b, int off, int len): Read up to len - off bytes of data into b, start from off, and return the actual number read; -1 means the reading is complete.
  4. void close(): Close the byte stream (generally, the InputStream will be written in try(), so there is no need to release it manually~).

inputStream is just an abstract class. To use it, you still need a specific implementation class. For example, after the client and server accept, the specific implementation class of the stream object is obtained... But what we most commonly use is file reading, which is FileInputStream. .

OutputStream has the following methods:

  1. void write(int b): Write the specified bytes to this output stream.
  2. void write(byte[] b): Write all the data in the character array b into os.
  3. int write(byte[] b, int off, int len): Write the data starting from off in the character array b into os, write a total of len
  4. void close(): close the byte stream
  5. void flush(): We know that the speed of I/O is very slow. Therefore, in order to reduce the number of device operations, most OutputStreams will temporarily write the data to a designated area of ​​​​the memory until the data is written. Data is actually written to the device when the area is full or other specified conditions. This area is generally called the buffer. But one result is that part of the data we write is likely to be left in the buffer. The flush operation needs to be called at the end or at a suitable location to flush the data to the device.

OutputStream is also just an abstract class, and you need a specific implementation class to use it. We still only care about writing to the file, so use FileOutputStream

Ps: FileOutputStream has a constructor called new FileOutputStream(String path, boolean append). The first parameter is the file path, and the second parameter is whether to write appended to the end. If you want to append data to the end of the file, Just fill in true~

1.2. Special use

1.2.1. How to indicate that the file has been read? (DataInputStream)

Use the read() method to return an int value. If this value is -1, it means that the file has been completely read~

However, in actual projects, a kind of follow-up method is often used to indicate that the file reading is completed~ If we agree on the format of the data, it is an int (indicating the length of the payload) + payload, followed by data in the same format, then at this time, we You need to read this int through the readInt method in DataInputStream (this stream object is specially used to read numbers and byte streams, and must be used with DataOutputStream ) . This method is special in that after reading to the end of the file, continue reading. The EOFException exception will be thrown (in the past, when we read the end of the file, it returned -1, or null.), so here we can catch this exception through catch, indicating that the reading is completed~

Ps: It is worth noting that DataInputStream / DataOutputStream can facilitate the reading and writing of numbers (readInt, writeInt). The native InputStream / OutputStream does not provide digital reading and writing methods, and we need to convert them ourselves.

    public LinkedList<Message> loadAllMessageFromQueue(MSGQueue queue) throws IOException {
        //1.检查文件是否存在
        if(!checkQueueFileExists(queue.getName())) {
            throw new IOException("[MessageFileManager] 获取文件中所有有效消息时,发现队列文件不存在!queueName=" + queue.getName());
        }
        //2.获取队列中所有有效的消息
        synchronized (queue) {
            LinkedList<Message> messages = new LinkedList<>();
            try (InputStream inputStream = new FileInputStream(getQueueDataFilePath(queue.getName()))) {
                try (DataInputStream dataInputStream = new DataInputStream(inputStream)) {
                    int index = 0;
                    while(true) {
                        int messageSize = dataInputStream.readInt();
                        byte[] payload = new byte[messageSize];
                        int n = dataInputStream.read(payload);
                        if(n != messageSize) {
                            throw new IOException("[MessageFileManager] 读取消息格式出错!expectedSize=" + messageSize +
                                    ", actualSize=" + n);
                        }
                        //记录 offset
                        Message message = (Message) BinaryTool.fromBytes(payload);
                        if(message.getIsValid() == 0x0) {
                            index += (4 + messageSize);
                            continue;
                        }
                        message.setOffsetBeg(index + 4);
                        message.setOffsetEnd(index + 4 + messageSize);
                        messages.add(message);
                        index += (4 + messageSize);
                    }
                }
            } catch (EOFException e) {
                System.out.println("[MessageFileManager] 队列文件中有消息获取完成!queueName=" + queue.getName());
            }
            return messages;
        }
    }

1.2.2. Character reading/text data reading (Scanner)

It is very cumbersome and difficult to directly use InputStream to read character types. Therefore, we use a class that we are familiar with before to complete the work, which is the Scanner class.

Scanner is generally used with PrintWrite to read and write text format data, which greatly eliminates the need for InputStream/OutputStream to decode and convert byte data and text data using UTF-8.

For example one:

// 需要先在项目目录下准备好一个 hello.txt 的文件,里面填充 "你好中国" 的内容

public class Main {
    public static void main(String[] args) throws IOException {
        try (InputStream is = new FileInputStream("hello.txt")) {
           try (Scanner scanner = new Scanner(is, "UTF-8")) {
               while (scanner.hasNext()) {
                   String s = scanner.next();
                   System.out.print(s);
               }
           }
       }
   }
}

Example two: 

    public void writeStat(String queueName, Stat stat) {
        try (OutputStream outputStream = new FileOutputStream(getQueueStatFilePath(queueName))) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write(stat.totalCount + "\t" + stat.validCount);
            printWriter.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public Stat readStat(String queueName) {
        Stat stat = new Stat();
        try (InputStream inputStream = new FileInputStream(getQueueStatFilePath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

1.2.3. Random reading and writing of files (RandomAccessFile)

Previously, DataInputStream/DataOutputStream were used to receive FileInputStream/FileOutputStream for sequential reading and writing of files (either reading from the beginning to the end, or appending writes at the end...). The RandomAccessFile class can be used to perform operations at any specified location. Read/write operations!

This involves the concept of a cursor. In fact, when you write a file, wherever you write, there will be a cursor flashing at that location~

In RandomAccessFile, you can use the seek() method to specify the position of the cursor (unit is byte). For example, if you want to logically delete a certain section of memory in a file (no actual deletion, just read it first and mark it as invalid, and then Write back the file, the recycle bin has almost the same logic).

    public void deleteMessage(MSGQueue queue, Message message) throws IOException {
        //1.检查队列相关文件是否存在
        if(!checkQueueFileExists(queue.getName())) {
            throw new IOException("[FileDataCenter] 删除消息时,发现队列相关文件不存在!queueName=" + queue.getName());
        }
        synchronized (message) {
            //2.将要删除的消息文件读出来
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataFilePath(queue.getName()), "rw")) {
                randomAccessFile.seek(message.getOffsetBeg() - 4);
                int payloadSize = randomAccessFile.readInt();
                byte[] payload = new byte[payloadSize];
                int n = randomAccessFile.read(payload);
                if(n != payloadSize) {
                    throw new IOException("[FileDataCenter] 读取文件格式出错!path=" + getQueueDataFilePath(queue.getName()));
                }
                //3.将待删除的消息标记为无效(isValid = 0x0)
                Message toDeleteMessage = (Message) BinaryTool.fromBytes(payload);
                toDeleteMessage.setIsValid((byte) 0x0);
                //4.将消息写入文件
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(BinaryTool.toBytes(toDeleteMessage));
            }
            //5.更新统计文件
            Stat stat = readStat(queue.getName());
            stat.validCount -= 1;
            writeStat(queue.getName(), stat);
        }
    }

Ps:

RandomAccessFile has two constructors (actually one). RandomAccessFile(String name, String mode) is equivalent to RandomAccessFile(new File(name), String mode)

mode This parameter indicates the access mode~
➢ "r": Open the specified file in read-only mode. If you try to perform a write method on the RandomAccessFile, an IOException will be thrown.
➢ "rw": Open the specified file in reading and writing mode. If the file does not exist yet, attempts to create the file.
➢ "rws": Open the specified file in reading and writing mode. Relative to "rw" mode, it also requires that every update to the file's content or metadata be written synchronously to the underlying storage device.
➢ "rwd": Open the specified file in reading and writing mode. Relative to "rw" mode, it also requires that every update to the file content be written to the underlying storage device synchronously.

Guess you like

Origin blog.csdn.net/CYK_byte/article/details/132724326