25-IO(上)

1. File

1.1 概述

  • java.io.File:文件和文件目录路径的抽象表示形式,与平台无关
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流
  • 想要在 Java 程序中表示一个真实存在的文件或目录,那么必须有一个 File 对象;但是 Java 程序中的一个 File 对象,也可能没有一个真实存在的文件或目录
  • File 对象可以作为参数传递给流的构造器

1.2 构造器

  • File(File parent, String child)
  • File(String pathname)
  • File(String parent, String child)
  • File(URI uri)

1.3 路径

  • 路径表示方式
    • 绝对路径:包含盘符在内的文件或文件目录
    • 相对路径:相较于某个路径下,指明的路径
      • @Test 方法,是相对于当前 Module
      • main 方法,是相对于当前 Project
  • 路径分隔符
    • 路径中的每级目录之间用一个 [路径分隔符] 隔开
    • 路径分隔符和系统有关
      • Windows 和 DOS系统 默认使用 来表示
      • UNIX 和 URL 使用 / 来表示
    • Java程序支持跨平台运行,因此 [路径分隔符] 要慎用。为了解决这个隐患,File 类提供了一个常量:public static final String separator ← 根据操作系统,动态的提供分隔符

1.4 常用方法

  • 获取功能
    • public String getAbsolutePath():获取绝对路径
    • public String getPath():获取路径
    • public String getName():获取名称
    • public String getParent():获取上层文件目录路径。若无,返回 null
    • public long length():获取文件长度(字节数)。不能获取目录的长度
    • public long lastModified():获取最后一次的修改时间,毫秒值
    • public String[] list():获取指定目录下的所有文件或者文件目录的名称数组
    • public File[] listFiles():获取指定目录下的所有文件或者文件目录的 File 数组
  • 重命名功能
    • public boolean renameTo(File dest) 把文件重命名为指定的文件路径;如果 dest 已存在,false
  • 判断功能
    • public boolean isDirectory():判断是否是文件目录
    • public boolean isFile():判断是否是文件
    • public boolean exists():判断是否存在
    • public boolean canRead():判断是否可读
    • public boolean canWrite():判断是否可写
    • public boolean isHidden():判断是否隐藏
  • 创建功能
    • public boolean createNewFile():创建文件。若文件存在,则不创建,返回 false
    • public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建
    • public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
  • 删除功能
    • public boolean delete():删除文件或者文件夹
    • Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

File 类中涉及到关于文件或目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作,如果需要读取或写入文件内容,必须使用 IO 流来完成。后续 File 类的对象常会作为参数传递到流的构造器中,指明读取或写入的"端点"。

2. 流原理及分类

  • I/O 是 Input/Output 的缩写, I/O 技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
  • Java 程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。
  • java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
  • 流的方向是相对的
    • 输入 input:读取外部数据(磁 盘、光盘等存储设备的数据)到 程序(内存)中
    • 输出 output:将程序(内存) 数据输出到磁盘、光盘等存储设备中。
  • 可以从不同的角度对流类型(类/抽象类)进行分类
    • 按操作数据单位不同
      • 字节流(8 bit)
      • 字符流(16 bit)
    • 按数据流的流向不同
      • 输入流
      • 输出流
    • 按流的角色的不同
      ![](_v_images/20200713145219974_27691.png =400x)
      • 节点流(原始流):直接从数据源或目的地读写数据
      • 处理流(包裹流):不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

3. IO流体系

  • Java 的 IO 流共涉及 40 多个类,实际上非常规则,都是从如下 4 个抽象基类派生的。
  • 由这 4 个类派生出来的子类名称都是以其父类名作为子类名后缀

4. 节点流的使用

4.1 使用说明

4.1.1 构造器

  • InputStreamReader 是所有输入流的基类
  • FileInputStreamFileReader 则是 InputStreamReader 的典型实现
  • public class FileInputStream extends InputStream 构造器
    • public FileInputStream(String name) throws FileNotFoundException
    • public FileInputStream(File file) throws FileNotFoundException
  • public class FileReader extends InputStreamReader 构造器
    • public FileReader(String fileName) throws FileNotFoundException
    • public FileReader(File file) throws FileNotFoundException
  • OutputStreamWriter 也非常相似,是所有输出流的基类
  • 典型实现分别是:FileOutputStreamFileWriter
  • public class FileOutputStream extends OutputStream 构造器
    • public FileOutputStream(String name) throws FileNotFoundException
    • public FileOutputStream(String name, boolean append) throws FileNotFoundException
    • public FileOutputStream(File file) throws FileNotFoundException
    • public FileOutputStream(File file, boolean append) throws FileNotFoundException
  • public class FileWriter extends OutputStreamWriter 构造器
    • public FileWriter(String fileName) throws IOException
    • public FileWriter(String fileName, boolean append) throws IOException
    • public FileWriter(File file) throws IOException
    • public FileWriter(File file, boolean append) throws IOException

4.1.2 常用方法


4.1.3 Tips

  • 在读取文件时,必须保证该文件已存在,否则报异常
  • 在写入一个文件时,如果使用构造器 FileOutputStream(file),则目录下有同名文件将被覆盖。如果使用构造器FileOutputStream(file, true),则目录下的同名文件不会被覆盖, 会在文件内容末尾追加内容。
  • 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源
  • FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader
  • FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter
  • 字节流操作字节,比如:.mp3 / .avi / .rmvb / .mp4 / .jpg / .doc / .ppt
  • 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt / .java / .c / .cpp 等语言的源代码。尤其注意 .doc / .excel / .ppt 这些不是文本文件

4.2 字节流和字符流的区别

  • 字节流可以从所有格式的设备中读写数据,但字符流只能从文本格式的设备中读写数据
    • 字节流可以完成所有格式文件的复制
    • 字符流只能完成文本文件的复制,却无法完成视频、音频、图片等格式的文件的复制
    • 因为字节是不需要解码和编码的,将字节转化为字符才存在解码和编码的问题
  • 字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件


  • 提问:什么叫缓冲区?
    • 缓冲区可以简单地理解为一段特殊的内存区域
    • 某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能
    • 在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据
    • 如果想在不关闭时也可以将字符流的内容全部输出,则可以使用 Writer 类中的 flush() 完成
  • 提问:使用字节流好还是字符流好?
    • 先讲这样的一个概念,所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节流使用较为广泛
    • 字节流与字符流主要的区别是他们的的处理方式

字节流是最基本的,所有的 InputStreamOutputStream 的子类都是主要用在处理二进制数据,它是按字节来处理的。但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的 encode 来处理,也就是要进行字符集的转化。这两个之间通过 InputStreamReaderOutputStreamWriter 来关联,实际上是通过 byte[]String 来关联。

在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。在从字节流转化为字符流时,实际上就是 byte[] → String时,public String(byte bytes[], String charsetName)

有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的 lang。而在字符流转化为字节流时,实际上是 String → byte[] 时,byte[] String.getBytes(String charsetName) 也是一样的道理。至于 java.io 中还出现了许多其他的流,按主要是为了提高性能和使用方便,如BufferedInputStreamPipedInputStream 等。

4.3 为什么 FileReader 不能用来拷贝图片

截取:《Java语言程序设计(基础篇)》

摘自:https://www.cnblogs.com/xqry/p/6698138.html

FileReader 是输入字符流,拷贝文件没问题,但拷贝图片就有问题了。

假设是在 Windows 下,FileReader 用的是 GBK 码表,一个字符最多用2个字节代表。2 个字节就是 2 的 16 次方,即有 65536 个格子范围,但 GBK 码表并没有将这些格子都用完,当读到某个二进制,假设是12421(我这里用二进制的十进制说明,二进制写起来太长)对应有码值“中”,那就读到完整的 2 个字节,数据是完整的。

但如果是另一个数字 21232 没有对应字符(码值),FileReader 读到这样的数据对应码表,找不到对应的字符,就会返回一个未知字符所对应的数字,占1个字节(返回值就是测试代码中的 content)。既然字节大小读不完整,FileWriter 写的时候还能正确吗?数据就是这样丢失的。

我在说“中”的时候大家不要蒙圈,读图片为什么要谈到码表对应的汉字。汉字只是图片中二进制数据在码表上的对应字符,它可以是汉字以外的其它字符代表都可以,对于 GBK 码表没有用完的格子,FileReader 读到的 content 就不是真实的数据。

由此我们也能知道字节流为什么能读取完整,因为它不需要码表,读到啥就得到啥,不会因为码表上没有对应字符就丢弃。

打一个很好的比方,用 FileReader 读图片,就像用记事本打开图片,因为记事本一遇到二进制数据就拿码表来“翻译”,可码表并不是每个格子都用到,容易导致数据丢失。

4.4 测试代码

@Test
public void TestReaderAndWriter() {
    // 1. File
    File src = new File("src.txt");
    File dest = new File("dest.txt");
    // 2. FileReader, FileWriter
    FileReader fr = null;
    FileWriter fw = null;
    try {
        fr = new FileReader(src);
        fw = new FileWriter(dest);
        // 3. transfer
        int len;
        char[] cbuf = new char[1024];
        while((len = fr.read(cbuf)) != -1)
            fw.write(cbuf, 0, len);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 4. close
        if(fw != null)
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

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

@Test
public void copyByStream() {
    // 1. File
    File src = new File("src.png");
    File dest = new File("dest.png");
    // 2. FileReader, FileWriter
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        fis = new FileInputStream(src);
        fos = new FileOutputStream(dest);
        // 3. transfer
        int len;
        byte[] cbuf = new byte[1024];
        while((len = fis.read(cbuf)) != -1)
            fos.write(cbuf, 0, len);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 4. close
        if(fos != null)
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

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

5. 缓冲流

在我们学习字节流与字符流的时候,大家都进行过读取文件中数据的操作,读取数据量大的文件时,读取的速度会很慢,很影响我们程序的效率,那么,我想提高速度,怎么办?Java 中提高了一套缓冲流,它的存在,可提高 IO 流的读写速度。

因为缓冲区技术是为流技术存在的,所以建立缓冲区之前必须先有流对象。然后把流对象作为参数传给缓冲对象的构造器。注意,缓冲类是没有空参构造器的,它必须在流对象的前提下创建。

缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

  • 字节缓冲流
    • BufferedInputStream — 字节输入缓冲流
      • public BufferedInputStream(InputStream in)
      • public BufferedInputStream(InputStream in, int size)
    • BufferedOutputStream — 字节输出缓冲流
      • public BufferedOutputStream(OutputStream out)
      • public BufferedOutputStream(OutputStream out, int size)
  • 字符缓冲流
    • BufferedReader — 字符输入缓冲流
      • public BufferedReader(Reader in)
      • public BufferedReader(Reader in, int sz)
    • BufferedWriter — 字符输出缓冲流
      • public BufferedWriter(Writer out)
      • public BufferedWriter(Writer out, int sz)

缓冲区有:BufferedOutputStream/BufferedWriter 写入流的缓冲区和 BufferedInputStream/BufferedReader 读取流的缓冲区,因为 Writer 流对象和 Reader 流对象操作数据时要读一份写一份,而缓冲区能够把每次读入的数据存着,写的时候一次写出去。所以他们能够提高效率,原因是它底层会创建一个内部缓冲区数组。

BufferedInputStream 部份源码:

public class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    // The internal buffer array where the data is stored.
    protected volatile byte buf[];

    // This is the index of the next character to be read from the buf array.
    protected int pos;

    // The index one greater than the index of the last valid byte in the buffer.
    protected int count;

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

缓冲区对象也一样可以操作 write()flush()close(),并且当缓冲区 close() 时,它对应的流对象也会关闭。

特殊的是:BufferedWrite 缓冲区有 newLine(),可以跨平台使用。Windows 的换行是 ,而 Linux 是 ,为了方便程序员写入统一的换行命令,Java 就封装了这个方法。相对应的,BufferReader 缓冲区有 readLine(),可以读取一行的字符串,不含任何终止符。当读到文件末尾行以后,返回 null,可以作为循环的控制条件。

测试代码1:

public void test() {
    // 1. File
    File srcFile = new File("src.png");
    File destFile = new File("dest.png");
    // 2. 节点流
    FileInputStream fis = null;
    FileOutputStream fos = null;
    // 3. 处理流
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    try {
        fis = new FileInputStream(srcFile);
        fos = new FileOutputStream(destFile);
        bis = new BufferedInputStream(fis);
        bos = new BufferedOutputStream(fos);
        // 4. transfer
        byte[] buffer = new byte[1024];
        int len;
        while((len = bis.read(buffer)) != -1)
            bos.write(buffer, 0, len);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 5. close
        if(bos != null)
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        if(bis != null)
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        // fis.close(); // 处理流底层会去关闭节点流
        // fos.close();
    }
}

测试代码2:

public void test2() {
    // 1. File
    File srcFile = new File("BiTree.java");
    File destFile = new File("二叉树.java");
    // 2. 节点流
    FileReader fr = null;
    FileWriter fw = null;
    // 3. 处理流
    BufferedReader br = null;
    BufferedWriter bw = null;
    try {
        fr = new FileReader(srcFile);
        fw = new FileWriter(destFile);
        br = new BufferedReader(fr);
        bw = new BufferedWriter(fw);
        // 4. transfer
        /*
        char[] cbuf = new char[1024];
        int len;
        while((len = br.read(cbuf)) != -1)
            bw.write(cbuf, 0, len);
         */
        String line;
        while((line = br.readLine()) != null) {
            bw.write(line);
            bw.newLine();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 5. close
        if(br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

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

小结:

  1. 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  2. 当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性从文件中读取 8192个字节(8KB),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个 8192 个字节数组
  3. 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满, BufferedOutputStream 才会把缓冲区中的数据一次性写到文件里。使用 flush() 可以强制将缓冲区的内容全部写入输出流
  4. 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也 会相应关闭内层节点流
  5. flush() 的使用:手动将 buffer 中内容写入文件
  6. 如果是带缓冲区的流对象的 close(),不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

6. 转换流

[转换流] 提供了在字节流和字符流之间的转换

6.1 说明

  • 字节流中的数据都是字符时,转成字符流操作更高效
  • 很多时候我们使用 [转换流] 来处理文件乱码问题。实现编码和解码的功能
    • 编码:字符串 → 字节数组
    • 解码:字节数组 → 字符串
  • Java API 提供了两个转换流:InputStreamReaderOutputStreamWriter
  • public class InputStreamReader extends Reader
    • 实现将字节的输入流按指定字符集转换为字符的输入流
    • 需要和 InputStream 套接
    • 构造器
  • public class OutputStreamWriter extends Writer
    • 实现将字符的输出流按指定字符集转换为字节的输出流
    • 需要和 OutputStream 套接
    • 构造器
  • 转换流的编码应用
    • 可以将字符按指定编码格式存储
    • 可以对文本数据按指定编码格式来解读
    • 指定编码表的动作由构造器完成

6.2 测试代码

@Test
public void transfer() throws IOException {
    File srcFile = new File("utf-8.txt");
    File destFile = new File("gbk.txt");
    FileInputStream fis = new FileInputStream(srcFile);
    FileOutputStream fos = new FileOutputStream(destFile);
    // 具体选用哪个字符集读入,取决于 File 保存时使用的字符集
    InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
    // 读入 UTF-8 编码的 File 将其copy一份再以 GBK 编码存储起来
    OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
    char[] cbuf = new char[1024];
    int len;
    while((len = isr.read(cbuf)) != -1)
        osw.write(cbuf, 0, len);
    isr.close();
    osw.close();
}

6.3 字符编码

6.3.1 编码表的由来

计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识 别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。 这就是编码表。

6.3.2 常见的编码表

  • ASCII:美国标准信息交换码,用一个字节的 7 位可以表示
  • ISO8859-1:拉丁码表。欧洲码表,用一个字节的 8 位表示
  • GB2312:中国的中文编码表,最多两个字节编码所有字符
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多两个字节编码
  • Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示
  • UTF-8:变长的编码方式,可用 1~4 个字节来表示一个字符

对于可变长度的编码集:当本字节最高位是0,说明这个字符就只用一个字节编码;如果字节最高位是1,说明这个字节和下一个字节合一起是一个字符的编码。

Unicode 不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用 一个字节表示就够了,第二个问题是如何才能区别 Unicode 和 ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第三个,如果和 GBK 等双字节编码方式一样,用最高位是 1 或 0 表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode 在很长一段时间内无法推广,直到互联网的出现。

6.3.3 UTF-8

面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8 就是每次 8 个位传输数据,而 UTF-16 就是每次 16 个位。这是为传输而设计的 编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

Unicode 只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯 一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的 Unicode 编码是 UTF-8 和 UTF-16。

在标准 UTF-8 编码中,超出基本多语言范畴(BMP) 的字符被编码为 4 字节格式,但是在修正的 UTF-8 编码中,他们由 [代理编码对(surrogatepairs)] 表示,然后这些代理编码对在序列中分别重新编码。结果标准 UTF-8 编码中需要 4 个字节的字符,在修正后的 UTF-8 编码中将需要 8 个字节。

7. 标准输入、输出流

  • System 类字段摘要如下,其中 inout 分别代表了系统标准的输入和输出设备
    • static PrintStream err “标准”错误输出流
    • static InputStream in “标准”输入流;默认从键盘输入
    • static PrintStream out “标准”输出流;默认从控制台输出
  • 重定向:通过 System 类的如下两个方法对默认设备进行改变
    • static void setIn(InputStream in) 重新分配“标准”输入流
    • static void setOut(PrintStream out) 重新分配“标准”输出流

练习:从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序

public static void main(String[] args) {
    BufferedReader br = null;
    try {
        // System.in → 转换流 → BufferedReader
        br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while(true) {
            System.out.print("Enter String: ");
            line = br.readLine();
            if("e".equalsIgnoreCase(line) ||
                    "exit".equalsIgnoreCase(line)) break;
            String upperCase = line.toUpperCase();
            System.out.println(upperCase);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(br != null) {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

8. 打印流

实现将基本数据类型的数据格式转化为字符串输出

打印流:PrintStreamPrintWriter

  • 提供了一系列重载的 print()println(),用于多种数据类型的输出
  • PrintStreamPrintWriter 的输出不会抛出 IOException
  • PrintStreamPrintWriter 有自动 flush 功能
  • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter
  • System.out 返回的是 PrintStream 的实例
@Test
public void test() {
    PrintStream ps = null;
    try {
        FileOutputStream fos = new FileOutputStream(new File("u:\text.txt"));
        // 创建打印输出流,设置为自动刷新模式(写入换行符或字节'
'时都会刷新输出缓冲区)
        ps = new PrintStream(fos, true);
        if (ps != null) // 把标准输出流(控制台输出)改成文件
            System.setOut(ps);
        for (int i = 0; i <= 255; i++) {
            // 输出ASCII字符
            System.out.print((char) i);
            if (i % 50 == 0) // 每50个数据一行
                System.out.println(); // 换行
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (ps != null)
            ps.close();
    }
}
原文地址:https://www.cnblogs.com/liujiaqi1101/p/13340646.html