После прочтения этого Java IO перестает быть сложным

Java задняя часть .NET Шаблоны проектирования

1. Система ввода-вывода

Система ввода-вывода Java кажется многоклассовой и кажется очень сложной, но на самом деле ввод-вывод включает слишком много факторов. При разработке классов, связанных с вводом-выводом, авторы не учитывают один и тот же аспект, поэтому у людей может возникнуть ощущение беспорядка, и существуют шаблоны проектирования, которые усложняют использование этих классов ввода-вывода.

Класс ввода-вывода определенно предназначен для решения операций, связанных с вводом-выводом. сеть, диск. Классы, связанные с сетевыми операциями, находятся вjava.netПод пакетом это не входит в рамки резюме этой статьи. Когда дело доходит до дисков, вы можете думать о файлах, а операции с файлами являются типичными операциями ввода-вывода. Понятие «поток» было введено в Java, что означает любой объект, способный создавать источник данных или способный получать источник данных. Источники данных можно представить как источники воды, такие как морская вода, речная вода, вода из озера, стакан воды и так далее. Передачу данных можно представить как транспортировку воды.В древние времена для транспортировки воды использовались ведра, а для транспортировки воды использовались бамбуковые трубы.Сейчас для транспортировки воды используются стальные трубы.Разные способы транспортировки соответствуют разным характеристикам транспортировки.

С точки зрения источника данных или объекта операции классы ввода-вывода можно разделить на:

  • 1, файл (файл): FileInputStream, FileoutputStream, FileReader, FileWriter
  • 2, массив ([]):
    • 2.1, байтовый массив (byte[]): ByteArrayInputStream, ByteArrayOutputStream
    • 2.2, массив символов (char[]): CharArrayReader, CharArrayWriter
  • 3. Конвейерные операции: PipedInputStream, PipedOutputStream, PipedReader, PipedWriter
  • 4. Базовые типы данных: DataInputStream, DataOutputStream
  • 5. Операции буферизации: BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter
  • 6. Печать: PrintStream, PrintWriter
  • 7. Сериализация и десериализация объектов: ObjectInputStream, ObjectOutputStream
  • 8. Преобразование: InputStreamReader, OutputStreWriter
  • 9,НитьУстарело в Java 8:StringBufferInputStream, StringBufferOutputStream, StringReader, StringWriter

Узел-источник данных также может подвергаться вторичной обработке для упрощения использования данных, поэтому его также можно разделить на поток узла и поток обработки, что включает в себя шаблоны проектирования, которые будут описаны в специальной статье позже.

按操作对象划分.jpg
С точки зрения режима передачи данных или режима транспортировки классы ввода-вывода можно разделить на:

  • 1. Байтовый поток
  • 2. Поток символов

Поток байтов передается единицами по одному байту, например, стакан воды. Поток символов передается несколькими байтами, например, вычерчивается ведро воды, а ведро воды можно разделить на несколько чашек воды.

Разница между потоком байтов и потоком символов:

Поток байтов считывает один байт, а поток символов считывает один символ (символ соответствует разным байтам в зависимости от кодировки, например, кодировка UTF-8 — 3 байта, китайская кодировка — 2 байта). Потоки байтов используются для обрабатывать двоичные файлы (изображения, MP3, видеофайлы), а потоки символов используются для обработки текстовых файлов (которые можно рассматривать как специальные двоичные файлы, которые используют определенную кодировку и могут быть прочитаны людьми). Короче говоря, байты — это то, что видит компьютер, символы — это то, что видят люди.

Разделение потока байтов и потока символов можно увидеть на следующем рисунке.

按字节和字符划分.png

Бесспорно, что существует действительно много классов, связанных с Java IO, но не все из них будут использоваться.Мы обычно используем несколько классов, связанных с файлами, таких как самые основные классы чтения и записи для файлов, начинающихся с File, и чтение файлов и полосы записи.Класс буфера начинается с Buffered, а классы, связанные с сериализацией и десериализацией объектов, начинаются с Object.

2. Классы ввода-вывода и связанные с ними методы

Хотя классов ввода-вывода много, самыми основными являются четыре абстрактных класса: InputStream, OutputStream, Reader, Writer. Самый простой метод — это метод чтения read() и метод записи write(). Конкретная реализация метода зависит от подклассов, которые наследуют эти четыре абстрактных класса, ведь мы обычно используем объекты подклассов. Некоторые методы в этих классах являются (собственными) локальными методами, поэтому исходный код Java отсутствует Вот анализ исходного кода Java IO, который я считаю хорошим.портал, в соответствии с приведенной выше идеей, сначала посмотрите на основные методы подкласса, а затем посмотрите на новые методы в подклассе, я думаю, вы можете это понять, я только суммирую часто используемые классы, упомянутые выше.

Давайте сначала посмотрим на введение в методы в InputStream и OutStream, поскольку все они являются абстрактными классами и большинство из них являются абстрактными методами, поэтому я не буду публиковать исходный код!Обратите внимание, что чтение и запись здесь на самом деле являются получением (вводом) данных и выводом данных.

Класс InputStream

метод Введение метода
public abstract int read() читать данные
public int read(byte b[]) Поместите прочитанные данные в массив байтов, этот метод фактически реализован в соответствии со следующим методом, off равен 0, len длина массива
public int read(byte b[], int off, int len) Прочитайте данные длины len байтов из позиции off и поместите их в массив байтов, а поток равен -1, чтобы определить, заканчивается ли чтение (обратите внимание, что хотя здесь читается байт, он возвращает 4 байта типа int, из конечно тут есть причина, не буду здесь вдаваться в подробности, рекомендую эту статью,Ссылка на сайт)
public long skip(long n) Пропустить указанное количество байт, не читая, подумать о просмотре фильма и пропустить начало и конец
public int available() Возвращает количество байтов, доступных для чтения
public void close() После прочтения закройте поток и освободите ресурсы
public synchronized void mark(int readlimit) Отметьте позицию чтения. Вы можете начать чтение отсюда в следующий раз. Прежде чем использовать его, проверьте, поддерживает ли его текущий поток. Вы можете использовать метод markSupport(), чтобы судить
public synchronized void reset() Сбросить позицию чтения на позицию, отмеченную последней меткой
public boolean markSupported() Определите, поддерживает ли текущий поток отмеченные потоки, и используйте его в сочетании с двумя вышеуказанными методами.

Класс выходного потока

метод Введение метода
public abstract void write(int b) Запишите байт, вы можете видеть, что параметр здесь имеет тип int, соответствующий описанному выше методу чтения, 32 бита типа int, записываются только младшие 8 бит, а старшие 24 бита будут отброшены.
public void write(byte b[]) Записывает все байты в массив, аналогично соответствующему методу read() выше, и фактически вызывается следующий метод.
public void write(byte b[], int off, int len) Запустите массив байтов из выключенной позиции и запишите байты длины len
public void flush() Принудительно очистить, записать буферизованные данные
public void close() Закройте поток вывода.После закрытия потока данные больше не могут быть выведены.

Глядя на методы классов Reader и Writer, вы обнаружите, что они очень похожи на методы двух абстрактных базовых классов выше.

Читательский класс

метод Введение метода
public int read(java.nio.CharBuffer target) читать байты в символьный буфер
public int read() прочитать один символ
public int read(char cbuf[]) Читать символы в указанный массив символов
abstract public int read(char cbuf[], int off, int len) Считывает len символов из позиции off в массив char
public long skip(long n) пропустить количество символов указанной длины
public boolean ready() Аналогично методу available() выше
public boolean markSupported() Определите, поддерживает ли текущий поток отмеченные потоки
public void mark(int readAheadLimit) Отметьте позицию чтения. Вы можете начать чтение отсюда в следующий раз. Прежде чем использовать его, проверьте, поддерживает ли его текущий поток. Вы можете использовать метод markSupport(), чтобы судить
public void reset() Сбросить позицию чтения на позицию, отмеченную последней меткой
abstract public void close() Закройте поток, чтобы освободить связанные ресурсы

Класс писателя

метод Введение метода
public void write(int c) написать персонаж
public void write(char cbuf[]) написать массив символов
abstract public void write(char cbuf[], int off, int len) Записывает len количество символов из позиции выключения массива символов
public void write(String str) написать строку
public void write(String str, int off, int len) Записывает len количество символов из выключенной позиции строки
public Writer append(CharSequence csq) Добавить, чтобы вдохнуть последовательность символов
public Writer append(CharSequence csq, int start, int end) Append записывает часть последовательности символов, начиная с позиции start и заканчивая концом позиции
public Writer append(char c) Append записывает 16-битный символ
abstract public void flush() Принудительно очистить, записать буферизованные данные
abstract public void close() Закройте поток вывода.После закрытия потока данные больше не могут быть выведены.

Далее мы будем использовать их подклассы напрямую, а затем введем новые методы, которые не ниже в использовании.

1. Прочитайте ввод из консоли

import java.io.*;

public class IOTest {
    public static void main(String[] args) throws IOException {
        // 三个测试方法
//        test01();
//        test02();
        test03();
    }

    public static void test01() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一个字符");
        char c;
        c = (char) bufferedReader.read();
        System.out.println("你输入的字符为"+c);
    }

    public static void test02() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一个字符,按 q 键结束");
        char c;
        do {
            c = (char) bufferedReader.read();
            System.out.println("你输入的字符为"+c);
        } while (c != 'q');
    }

    public static void test03() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一行字符");
        String str = bufferedReader.readLine();
        System.out.println("你输入的字符为" + str);
    }
}

Что касается вывода на консоль, мы фактически используем его все время,System.out.println(), out на самом деле является ссылкой на объект класса PrintStream.Конечно, класс PrintStream также имеет метод write(), но мы используем метод print() и метод println() чаще, потому что эти два метода могут выводить больше типы содержимого, такие как печать. Объект, который фактически вызывает метод объекта toString().

2. Запись и чтение бинарных файлов

Обратите внимание на путь к файлу здесь, вы можете изменить его в соответствии со своей ситуацией.Хотя суффикс файла здесь txt, файл является двоичным файлом и не может быть просмотрен напрямую.

@Test
    public void test04() throws IOException {
        byte[] bytes = {12,21,34,11,21};
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test.txt");
        // 写入二进制文件,直接打开会出现乱码
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }

    @Test
    public void test05() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        // 读取写入的二进制文件,输出字节数组
        while ((c = fileInputStream.read()) != -1) {
            System.out.print(c);
        }
    }

3. Запись и чтение текстовых файлов

Метод write() и метод append() не похожи на имена методов. Один из них предназначен для перезаписи содержимого, а другой — для добавления содержимого. Метод append() также реализуется методом write(), поэтому нет никакой разницы, то есть метод append() может напрямую записывать значение null, в то время как метод write() должен записывать значение null в виде строки, поэтому между ними нет существенной разницы.Следует отметить, что кодировка файла здесь не указана, и может возникнуть проблема искажения символов.

@Test
    public void test06() throws IOException {
        FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt");
        fileWriter.write("Hello,world!\n欢迎来到 java 世界\n");
        fileWriter.write("不会覆盖文件原本的内容\n");
//        fileWriter.write(null); 不能直接写入 null
        fileWriter.append("并不是追加一行内容,不要被方法名迷惑\n");
        fileWriter.append(null);
        fileWriter.flush();
        System.out.println("文件的默认编码为" + fileWriter.getEncoding());
        fileWriter.close();
    }

    @Test
    public void test07() throws IOException {
        FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt", false); // 关闭追加模式,变为覆盖模式
        fileWriter.write("Hello,world!欢迎来到 java 世界\n");
        fileWriter.write("我来覆盖文件原本的内容");
        fileWriter.append("我是下一行");
        fileWriter.flush();
        System.out.println("文件的默认编码为" + fileWriter.getEncoding());
        fileWriter.close();
    }

    @Test
    public void test08() throws IOException {
        FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        fileReader.close();
        bufferedReader.close();
    }

    @Test
    public void test09() throws IOException {
        FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
        int c;
        while ((c = fileReader.read()) != -1) {
            System.out.print((char) c);
        }
    }

Классы преобразования InputStreamReader и OutputStreamWriter можно использовать для указания кодировки файла с использованием классов преобразования потоков байтов и потоков символов, а также использовать классы, связанные с буфером, для чтения каждой строки файла.

@Test
    public void test10() throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test2.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "GBK"); // 使用 GBK 编码文件
        outputStreamWriter.write("Hello,world!\n欢迎来到 java 世界\n");
        outputStreamWriter.append("另外一行内容");
        outputStreamWriter.flush();
        System.out.println("文件的编码为" + outputStreamWriter.getEncoding());
        outputStreamWriter.close();
        fileOutputStream.close();
    }

    @Test
    public void test11() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test2.txt");
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK"); // 使用 GBK 解码文件
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
    }

4. Скопируйте файлы

Здесь автор провел некоторые тесты.Влияние буферизации на время копирования файла не используется.Суть копирования файла заключается в чтении и записи файла. Буферизованные потоки — это потоки обработки, которые являются украшением потоков узлов.

Примечание. Время здесь проверено на моем ноутбуке ASUS, просто чтобы проиллюстрировать преимущества использования буферизации для чтения и записи файлов.

@Test
    public void  test12() throws IOException {
        // 输入和输出都使用缓冲流
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        BufferedInputStream inBuffer = new BufferedInputStream(in);
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        BufferedOutputStream outBuffer = new BufferedOutputStream(out);
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = inBuffer.read(bs)) != -1) {
            outBuffer.write(bs, 0, len);
        }
        System.out.println("复制文件所需的时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 200 多毫秒
        inBuffer.close();
        in.close();
        outBuffer.close();
        out.close();
    }


    @Test
    public void  test13() throws IOException {
        // 只有输入使用缓冲流
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        BufferedInputStream inBuffer = new BufferedInputStream(in);
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = inBuffer.read(bs)) != -1) {
            out.write(bs, 0, len);
        }
        System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 500 多毫秒
        inBuffer.close();
        in.close();
        out.close();
    }

    @Test
    public void test14() throws IOException {
        // 输入和输出都不使用缓冲流
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        int len = 0;
        byte[] bs = new byte[1024];
        long begin = System.currentTimeMillis();
        while ((len = in.read(bs)) != -1) {
            out.write(bs, 0, len);
        }
        System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间 700 多毫秒
        in.close();
        out.close();
    }

    @Test
    public void test15() throws IOException {
        // 不使用缓冲
        FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
        FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
        int len = 0;
        long begin = System.currentTimeMillis();
        while ((len = in.read()) != -1) {
            out.write(len);
        }
        System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 160000 毫秒,约 2 分多钟
        in.close();
        out.close();
    }

Что касается сериализации и десериализации, вот блог, который я писал ранее,портал. Резюме: Существует много классов Java IO, но если вы поймете всю систему и освоите ключевые методы, вам будет намного легче учиться После прочтения этой статьи вы думаете, что Java IO не так сложен, как вы думаете? Вы можете оставить сообщение ниже и обсудить с нами.

Добро пожаловать в общедоступную учетную запись WeChat, указанную ниже, и здесь есть различные учебные материалы, которыми можно поделиться бесплатно!

编程心路