JavaIO

41 阅读15分钟

文件基础知识

文件对于我们来说并不陌生,.java | .exe | .mp3 | .mp4 | .jpg | .txt都是文件的一种;说到底文件就是保存数据的地方,根据不同的文件类型(文件后缀名),保存不同种类的数据。如.mp3就用于保存音频类型的数据,.jpg用于保存图片类型的数据等。

文件在程序中是以文件流的形式进行传输的,如将磁盘中的文件,通过程序读取到内存中,这就需要将文件转化为文件流的形式;而文件数据在从磁盘(数据源)到程序(内存)的路径,可以被称为

输入流:数据源文件到内存的路径。

输出流:数据从程序(内存)到数据源(磁盘)的路径。


常用文件操作

首先是文件的创建相关操作,比较简单,代码演示如下

public static void main(String[] args) throws IOException {

    List<File> fileList = new ArrayList<>();

    /*
    根据路径构建 File 读对象
    */
    File file = new File("D:\\io-exer\\new1.txt");
    fileList.add(file);

    /*
    根据父目录文件 + 子路径构建
    */
    File fileFathered = new File("D:\\io-exer");// 父文件
    File file2 = new File(fileFathered, "new2.txt"); // 子路径
    fileList.add(file2);

    /*
    根据父路径 + 子路径构建
    */
    File file3 = new File("D:\\io-exer", "new3.txt"); // 父子路径
    fileList.add(file3);

    /*
    创建新文件
    */
    // 批量创建
    for (File fileItem : fileList) {

        if (fileItem.exists()) { // 判断文件对象对应的文件是否存在
            continue;
        }

        // 如果上级目录不存在,先创建目录
        boolean mkdir = true;
        if (!fileItem.getParentFile().exists()) {
            System.out.println(fileItem.getParentFile().getPath());
            mkdir = fileItem.getParentFile().mkdir(); // 创建目录,或者使用 mkdirs() 递归创建目录,这里只有一个上级目录,使用 mkdir() 创建
        }

        if (mkdir) {
            // 根据文件对象创建文件,已存在的文件不能创建;文件路径中有不存在的路径时无法创建
            boolean newFile = fileItem.createNewFile();
            if (newFile) {
                System.out.println("文件:" + fileItem.getName() + " 创建成功!");
                continue;
            }
            System.out.println("文件:" + fileItem.getName() + " 创建失败!");
            continue;
        }
        System.out.println("目录:" + fileItem.getParentFile().getAbsolutePath() + "创建失败:");
    }
}

获取文件相关信息

public static void main(String[] args) {
    File file = new File("D:\\io-exer\\new1.txt");

    System.out.println("文件名:" + file.getName()); // 得到文件名
    System.out.println("文件绝对路径:" + file.getAbsolutePath()); // 得到文件绝对路径
    System.out.println("父级目录:" + file.getParent()); // 得到文件父级目录
    System.out.println("文件大小(字节):" + file.length()); // 得到文件的大小(字节:一个中文3字节,一个英文字母1字节)
    System.out.println("文件是否存在:" + file.exists());// 判断文件是否存在
    System.out.println("是否为一个文件:" + file.isFile());// 判断是否为一个文件
    System.out.println("是否为一个目录:" + file.isDirectory());// 判断是否为一个目录
}

流的分类

Java 中的流根据不同的划分类型,可以分为如下几类:

按照流向主要分为Input输入流和Output输出流,合称IO流;输出流标识将内存中的数据输出到磁盘上保存起来,而输入流标识将磁盘中的数据读取到内存中进行处理。

按照数据单位的不同,分为字符流(按照字符)字节流(字节 8bit)

按照流的角色进行划分,分为节点流处理流\包装流

Java 中的IO流总共涉及几十个类,但都是由四个抽象基类派生的,如下

public static void main(String[] args) {
    /*
    字节流
    */
    InputStream inputStream; // 输入流
    OutputStream outputStream; // 输出流

    /*
    字符流
    */
    Reader reader; // 输入流
    Writer writer; // 输出流
}

主要分为字节流和字符流,字节流和字符流由分别划分了输入和输出流。由这四个基类派生出来的子类都是以其对应的基类名称作为自身类名的后缀的。如InputStream的子类FileInputStream

IO流体系结构图如下

StringReader一行开始,包括StringReader为节点流,以下为处理流。


FileInputStream

FileInputStream是字节输入流的一种,用于从文件中读取文件,代码演示如下

方式一

String filePath = "D:\\io-exer\\new1.txt";
FileInputStream fis = null;
try {
    fis = new FileInputStream(filePath);

    int readVal;

    /*
    从这个输入流中读取一个字节的数据。如果还没有可用的输入,则此方法会阻塞;若返回 -1,则读取完毕
    使用 read() 方法读取中文会出现乱码,因为 read() 每次读取 1 字节,而中文占用 3 字节。
    */
    while ((readVal = fis.read()) != -1) {
        System.out.print((char) readVal);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

以上方式每次从指定的文件中读取1字节,效率较低,且会有中文读取乱码的情况。

方式二

String filePath = "D:\\io-exer\\new1.txt";
FileInputStream fis = null;
try {
    fis = new FileInputStream(filePath);

    byte[] readBytes = new byte[1024]; // 保存读取的字节数
    int readLength; // 保存每次读取的字节长度

    /*
    从这个输入流中每次读取指定字节数组长度的字节数。如果还没有可用的输入,则此方法会阻塞;
    返回值为每次读取的字节长度,若返回 -1,则读取完毕。
    */
    while ((readLength = fis.read(readBytes)) != -1) {
        System.out.print(new String(readBytes, 0, readLength));
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

每次先将字节数据读取到字节数组中,在一定长度上能够避免中文乱码的情况,但是依然存在乱码的几率,因为受字节数组的容量影响,导致在读取中文时,若数组只剩下最后2个或1个字节的容量,中文字节数据不完整,出现乱码。


FileOutputStream

字节输出流,用于将字节数据输出到指定的文件中。

String filePath = "D:\\io-exer\\new2.txt";
FileOutputStream fos = null;
try {
    // 默认是覆盖的方式进行文件的写入,可以通过设置 append 参数设置为追加方式写入文件
    fos = new FileOutputStream(filePath, true);
    byte[] bytes = "飞雪连天射白鹿,笑书神侠倚碧鸳。\n".getBytes(StandardCharsets.UTF_8);

    // 将字节中的数据写出到指定文件中,如果文件不存在会自动创建,前提是目录存在。
    fos.write(bytes);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    System.out.println("写出成功!");
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

将字节数据写入指定的文件中。


文件拷贝

使用File | FileInputStream | FileOutputStream完成文件的拷贝

/**
 * 文件拷贝,将 src 路径下的文件拷贝被 dest 路径下
 *
 * @param src      【源文件】的绝对路径
 * @param dest     【目标地址】的绝对路径(必须为一个目录)
 * @param isappend 是否追加到目标文件
 * @return 返回是否拷贝成功
 */
public static boolean copy(String src, String dest, boolean isappend) {
    File srcFile = new File(src);
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        if (!srcFile.exists()) {
            return false;
        }
        File destFile = new File(dest); // 目标路径文件对象
        boolean isDirCreate = true;
        // 目标目录不存在
        if (!destFile.exists()) {
            isDirCreate = destFile.mkdirs(); // 递归创建目录,保证目录结构完整
        }
        // 目标目录创建成功,开始拷贝
        if (isDirCreate) {
            destFile = new File(destFile, srcFile.getName());// 根据源文件创建目标文件对象
            boolean isFileCreate = true;
            if (!destFile.exists()) {
                isFileCreate = destFile.createNewFile();// 创建文件
            }
            if (isFileCreate) {
                fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile, isappend);
                byte[] dataBytes = new byte[1024];
                int readLength;
                while ((readLength = fis.read(dataBytes)) != -1) {
                    fos.write(dataBytes, 0, readLength);
                }
                System.out.println("拷贝成功!");
                return true;
            }
        }
        System.out.println("目标文件创建失败!");
        return false;
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

FileReader

字符输入流,使用字符的形式从文件中读取数据。

public class FileReaderDemo {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            reader = new FileReader("D:\\io-exer\\new1.txt");
            char[] chars = new char[10];

            // 从文件中每次读取 chars.length 个字符到 chars 中,返回值为每次读取的字符个数,返回值为 -1 则读取完毕
            StringBuilder sb = new StringBuilder();
            int readLength;
            while ((readLength = reader.read(chars)) != -1) {
                sb.append(chars, 0, readLength);
            }
            System.out.println(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

FileWriter

字符输出流,使用字符形式将数据输出到指定文件中。

public class FileWriteDemo {
    public static void main(String[] args) {
        FileWriter writer = null;
        try {
            /*
            构造一个字符输出流,append 参数为 true(默认 false)代表追加写模式
             */
            writer = new FileWriter("D:\\io-exer\\new4.txt", true);

            // 将参数中的字符串整个写入文件中
            writer.write("飞雪连天射白鹿,笑书神侠倚碧鸳。");

            /*
            字符输出流需要刷新后才能完成文件的写入
             */
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

节点流和处理流

节点流能够从一个特点的数据源读取数据,如FileReader | FileWriter,这是针对字符形式的数据进行操作,如果是二进制数据,则可以使用FileInputStream | FileOutputStream进行操作,它们都是操作一个特点的文件,属于节点流。

而处理流(也叫包装流)则是位于已经存在的流之上,相较于普通的直接读写文件的节点流处理流能够对普通的节点流进行一层包装,使其功能更加强大(真正执行文件流操作的依然是内部包装的节点流,处理流只进行包装和增强)。

常用的处理流主要有BufferReader | BufferWriter,例如BufferReader内部属性如下

public class BufferedReader extends Reader {

    private Reader in;
.....

这说明BufferReader内部能够接收一个字符节点输入流,对其进行增强。


BufferedReader

处理流的一种,对字符节点输入流进行包装增强(操作字符文件,若使用BufferReader操作二进制文件有一定风险)。使用处理流后,关闭资源时,只需要关闭外层的处理流就行了,BufferReader在内部会执行内层流的关闭逻辑。

public class BufferReaderDemo {
    public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        try {
            FileReader reader = new FileReader("D:\\io-exer\\new1.txt");
            bufferedReader = new BufferedReader(reader);
            String line;

            /*
            按行读取文件中的字符数据(不包括任何行终止字符:包括 \n \r 等),如果读取完毕,返回 null
             */
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

BufferedWriter

字符输出的节点流,简单使用如下

public class BufferWriterDemo {
    public static void main(String[] args) {
        BufferedWriter bufferedWriter = null;
        try {
            FileWriter writer = new FileWriter("d:\\io-exer\\new5.txt", true);
            bufferedWriter = new BufferedWriter(writer);
            bufferedWriter.write("同是天涯沦落人,相逢何必曾相识。");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Buffer 字符文件拷贝

使用BufferReader | BufferWriter实现文件的拷贝

public class BufferCopy {

    /**
     * 文本文件的拷贝
     *
     * @param src      源文本文件
     * @param dest     目标目录
     * @param isappend 是否追加到目标文件
     * @return 返回是否拷贝成功的布尔值
     */
    public static boolean copy(String src, String dest, boolean isappend) {
        File srcFile = new File(src);
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            if (!srcFile.exists()) {
                return false;
            }
            File destFile = new File(dest); // 目标路径文件对象
            boolean isDirCreate = true;
            // 目标目录不存在
            if (!destFile.exists()) {
                isDirCreate = destFile.mkdirs(); // 递归创建目录,保证目录结构完整
            }
            // 目标目录创建成功,开始拷贝
            if (isDirCreate) {
                destFile = new File(destFile, srcFile.getName());// 根据源文件创建目标文件对象
                boolean isFileCreate = true;
                if (!destFile.exists()) {
                    isFileCreate = destFile.createNewFile();// 创建文件
                }
                if (isFileCreate) {
                    FileReader fis = new FileReader(srcFile);
                    FileWriter fos = new FileWriter(destFile, isappend);

                    // 处理流
                    bufferedReader = new BufferedReader(fis);
                    bufferedWriter = new BufferedWriter(fos);

                    String readLine;
                    while ((readLine = bufferedReader.readLine()) != null) {
                        bufferedWriter.write(readLine + "\n");
                    }
                    System.out.println("拷贝成功!");
                    return true;
                }
            }
            System.out.println("目标文件创建失败!");
            return false;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        BufferCopy.copy("d:\\io-exer\\new1.txt", "d:\\io-exer2", true);
    }
}

BufferedInputStream

针对字节数据的处理流

public class BufferedInputStreamDemo {
    public static void main(String[] args) {
        BufferedInputStream bufferedInputStream = null;
        try {
            FileInputStream fis = new FileInputStream("d:\\io-exer\\new5.txt");
            bufferedInputStream = new BufferedInputStream(fis);
            int readlength;
            byte[] bytes = new byte[1024];
            StringBuilder stringBuilder = new StringBuilder();

            /*
            每次从文件中读取 bytes.length 个字节,当返回值为 -1 时,代表读取完毕
            中文可能会存在乱码的情况。
             */
            while ((readlength = bufferedInputStream.read(bytes)) != -1) {
                for (int i = 0; i < readlength; i++) {
                    stringBuilder.append((char) bytes[i]);
                }
            }
            System.out.println(stringBuilder.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

BufferedOutputStream

针对字节数据的输出处理流

public class BufferedOutputStreamDemo {
    public static void main(String[] args) {
        BufferedOutputStream bufferedOutputStream = null;
        try {
            FileOutputStream fos = new FileOutputStream("d:\\io-exer\\new7.txt", true);
            bufferedOutputStream = new BufferedOutputStream(fos);

            // 将指定的字节数据写入到文件中
            bufferedOutputStream.write("故人西辞黄鹤楼,烟花三月下扬州。".getBytes(StandardCharsets.UTF_8));
            bufferedOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

Buffer 字节文件拷贝

/**
 * @description: BufferByteCopy
 * @author: dhj
 * @date: 2021/12/22 13:51
 * @version: v1.0
 */
public class BufferByteCopy {

    /**
     * 使用字节形式拷贝文件
     *
     * @param src    目标文件
     * @param dest   目标路径
     * @param append 是否对目标文件使用追加模式
     * @return 返回是否拷贝成功
     */
    public static boolean copy(String src, String dest, boolean append) {
        File srcFile = new File(src);
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            if (!srcFile.exists()) {
                return false;
            }
            File destFile = new File(dest); // 目标路径文件对象
            boolean isDirCreate = true;
            // 目标目录不存在
            if (!destFile.exists()) {
                isDirCreate = destFile.mkdirs(); // 递归创建目录,保证目录结构完整
            }
            // 目标目录创建成功,开始拷贝
            if (isDirCreate) {
                destFile = new File(destFile, srcFile.getName());// 根据源文件创建目标文件对象
                boolean isFileCreate = true;
                if (!destFile.exists()) {
                    isFileCreate = destFile.createNewFile();// 创建文件
                }
                if (isFileCreate) {
                    FileInputStream fis = new FileInputStream(srcFile);
                    FileOutputStream fos = new FileOutputStream(destFile, append);

                    // 处理流
                    bufferedInputStream = new BufferedInputStream(fis);
                    bufferedOutputStream = new BufferedOutputStream(fos);

                    byte[] bytes = new byte[1024];
                    int readLength;
                    long srcTotalByte = srcFile.length(); // 得到源文件文件总字节数
                    int kbConversionMbUnit = 1024 * 1024; // 字节单位转换数
                    // 开启一个线程检查剩余的字节
                    Thread checkRemainiingThread = new Thread(() -> {
                        try {
                            TimeUnit.SECONDS.sleep(1); // 等待流开始读取
                            while (!Thread.currentThread().isInterrupted()) {
                                System.out.println("剩余:" + (srcTotalByte - Num.totalReadlLength) / kbConversionMbUnit + " MB,速度:"
                                        + (Num.totalReadlLength - Num.perTotalReadlLength) / kbConversionMbUnit + "MB/s");
                                Num.perTotalReadlLength = Num.totalReadlLength; // 记录当前总读取字节数
                                TimeUnit.SECONDS.sleep(1); // 休眠 1 秒再查看剩余字节
                            }
                        } catch (Exception e) {
                            if (e instanceof InterruptedException) {
                                // 中断异常不用管,线程休眠状态下被中断的正常现象
                                return;
                            }
                            e.printStackTrace();
                        }
                    });
                    checkRemainiingThread.start();
                    long startTime = System.currentTimeMillis();
                    while ((readLength = bufferedInputStream.read(bytes)) != -1) {
                        bufferedOutputStream.write(bytes, 0, readLength);
                        bufferedOutputStream.flush();
                        Num.totalReadlLength += readLength;
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println("拷贝成功!耗时 " + ((endTime - startTime) / 1000) + " 秒");
                    checkRemainiingThread.interrupt();// 修改线程的中断标志
                    return true;
                }
            }
            System.out.println("目标文件创建失败!");
            return false;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    static class Num {
        // 记录读取的总字节数
        static long totalReadlLength = 0;

        // 记录上一次(秒)读取的总字节数
        static long perTotalReadlLength = 0;
    }

    public static void main(String[] args) {
        BufferByteCopy.copy("D:\\Centos-7\\CentOS-7.5-x86_64-DVD-1804.iso", "d:\\io-exer2", false);
    }
}

对象处理流

对象流实际上指的就是序列化和反序列化;序列化在保存数据时会保存数据的值和数据类型,反序列化在恢复数据时,会恢复数据的值和数据类型;简单来说,序列化是将对象的数据和数据类型保存到文件中的过程,反序列化是将文件恢复成对象的过程。

在Java中,要想让某个对象支持序列化机制,需要让其类实现以下两个接口之一

Serializable(一个标记接口)

Externalizable(该接口需要实现其中的方法,不推荐)

注意事项

读写顺序保持一致

序列和反序列化的对象需要实现Serializable接口

序列化的类中建议添加serialVersionUID,提高版本兼容性

被标记为transient的属性在对象被序列化的时候不会被保存(其值不会被保存)

序列化对象时,对象中的属性也需要实现Serializable接口

序列化具备可继承性,如果某类已经实现了序列化,则其子类默认实现了序列化


ObjectOutputStream

针对对象的序列化输出处理流

public class ObjectOutputStreamDemo {
    public static void main(String[] args) {
        ObjectOutputStream objectOutputStream = null;
        try {
            FileOutputStream fos = new FileOutputStream("d:\\io-exer\\dog.txt", false);
            objectOutputStream = new ObjectOutputStream(fos);
            Dog dog = new Dog("阿黄", 3, "小名");
            // 将对象序列化到文件中
            objectOutputStream.writeObject(dog);
             // 基本类型的包装类也能够进行序列化
            objectOutputStream.writeObject(Boolean.FALSE);
            objectOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

ObjectInputStream

针对对象的反序列化输入处理流。

序列化对象

public class Dog implements Serializable {

    private String dogName;
    private int dogAge;
    private String master;

    public Dog(String dogName, int dogAge, String master) {
        this.dogName = dogName;
        this.dogAge = dogAge;
        this.master = master;
    }

    public Dog() {
    }

    @Override
    public String toString() {
        return "Dog{" +
                "dogName='" + dogName + '\'' +
                ", dogAge=" + dogAge +
                ", master='" + master + '\'' +
                '}';
    }
}
/**
 * @description: ObjectInputStreamDemo
 * @author: dhj
 * @date: 2021/12/22 16:46
 * @version: v1.0
 */
public class ObjectInputStreamDemo {
    public static void main(String[] args) {

        ObjectInputStream objectInputStream = null;
        try {
            FileInputStream fis = new FileInputStream("d:\\io-exer\\dog.txt");
            objectInputStream = new ObjectInputStream(fis);

            /*
            读取指定文件中的对象序列化二进制数据,反序列化为对象。
            添加和读取的顺序需要一致,否则抛出 java.io.EOFException
             */
            Dog dog = (Dog) objectInputStream.readObject();
            boolean b = objectInputStream.readBoolean();
            System.out.println(b);
            System.out.println(dog.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

标准输入输出流

标准的输入输出指的是System.in(标准输入-键盘)| System.out(标准输出-显示器),对应的流类型分别为InputStream | PrintStream;与其他流没有什么区别。


转换流

转换流能够实现字节流和字符流的转换;例如InputStreamReader(Reader的子类)能够将字节输入流包装为字符输入流,同时指定转换的编码方式,防止乱码。


InputStreamReader

将字节输入流转换为字符输入流。

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        InputStreamReader inputStreamReader = null;
        try {
            InputStream in = new FileInputStream("d:\\io-exer\\new1.txt");
            // 将字节的输入流转换为字符的输入流,同时可以指定编码方式
            inputStreamReader = new InputStreamReader(in, StandardCharsets.UTF_8);
            char[] chars = new char[10];
            int readLength;
            while ((readLength = inputStreamReader.read(chars)) != -1) {
                System.out.print(String.valueOf(chars, 0, readLength));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭外层流即可
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

OutputStreamWriter

将字节的输出流转换为字符的输出流,可可以指定编码方式,防止乱码

public class OutputStreamWriterDemo {
    public static void main(String[] args) {
        OutputStreamWriter outputStreamWriter = null;
        try {
            FileOutputStream fos = new FileOutputStream("d:\\io-exer\\new9.txt", false);
            /*
            将字节的输出流转换为字符的输出流,指定输出的编码方式为 utf-8
             */
            outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8);

            outputStreamWriter.write("西风烈,长空雁叫霜晨月。\n霜晨月,马蹄声碎,喇叭声咽。\n" +
                    "雄关漫道真如铁,而今迈步从头越。\n从头越,苍山如海,残阳如血!");
            outputStreamWriter.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (outputStreamWriter != null) {
                try {
                    outputStreamWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

打印流

打印流只有输出流,没有输入流,这很好理解,因为打印针对的肯定就是输出。打印流主要包括PrintStream(字节打印)PrintWriter(字符打印)


PrintStream & PrintWriter

public class PrintStreamWriterDemo {
    public static void main(String[] args) {
        /*
        PrintStream
         */
        PrintStream printStream = null;

        /*
        PrintWriter
         */
        PrintWriter printWriter = null;
        try {
            printStream = System.out; // System.out 的编译类型就是 PrintStream

            // 创建字符打印流,指定字符集和打印的位置
            printWriter = new PrintWriter("d:\\io-exer\\new11.txt", "utf-8");
            // 默认输出位置为标准输出(显示器)
            printStream.print("日暮乡关何处是?烟波江上使人愁。");

            // 字符打印流
            printWriter.print("人间四月芳菲尽,山寺桃花始盛开。");

            // 可以设置指的输出位置,例如设置输出到文件(还可以设置 System.setIn(); 默认的输入位置)
            System.setOut(new PrintStream("d:\\io-exer\\new10.txt"));
            System.out.println("山回路转不见君,雪上空留马行处。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (printStream != null) {
                printStream.close();
            }
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }
}

Properties

Properties 是一个配置文件读取的集合类,可以用于配置文件的读取,在不使用 Properties 之前,读取配置文件的普通方式如下

public class PropertiesDemo {
    public static void main(String[] args) {
        BufferedReader bf = null;
        try {
            String path = PropertiesDemo.class.getClassLoader().getResource("my.properties").getPath();
            if (path != null) {
                bf = new BufferedReader(new FileReader(path));
                String line;
                while ((line = bf.readLine()) != null) {
                    String[] proLineArray = line.split("=");
                    System.out.println(proLineArray[0] + "|" + proLineArray[1]);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bf != null) {
                try {
                    bf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用 Properties 的情况下

public class PropertiesDemo2 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            String path = PropertiesDemo2.class.getClassLoader().getResource("my.properties").getPath();
            if (path != null) {
                fis = new FileInputStream(path);
            }
            Properties properties = new Properties();

            // 从指的输入流中加载配置文件
            properties.load(fis);
            // 从加载的配置文件中按照 key 取出 value
            System.out.println(properties.get("user"));
            // 将配置文件的中的数据打印到指定的输出流中
            properties.list(System.out);

            // 将集合中的数据通过指定的输出流输出
            properties.store(new PrintWriter("d:\\io-exer\\new12.txt", "utf-8"), null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}