14【IO流基础】


上一篇13【线程等待、状态、线程池、File类】


下一篇15【IO流增强】

目录【JavaSE零基础系列教程目录】


文章目录

  • 14【IO流基础】
  • 一、IO概述
    • 1.1 IO流简介
    • 1.2 IO流的分类
  • 二、字节流
    • 2.1 字节输出流
      • 2.1.1 FileOutputStream类
        • 1)构造方法
        • 2)其他方法
        • 3)数据的追加
        • 3)写出换行
    • 2.2 字节输入流
      • 2.2.1 FileInputStream类
        • 1)构造方法
        • 2)读取字节数据
        • 3)其他方法
    • 2.3 字节流练习
  • 三、字符流
    • 3.1 字符输入流
      • 3.1.1 FileReader类
        • 1)构造方法
        • 2)其他方法
    • 3.2 字符输出流
      • 3.2.1 FileWriter类
        • 1)构造方法
        • 2)写出字符数据
        • 3)续写和换行
        • 4)关闭和刷新
    • 3.3 拷贝文件问题
      • 3.3.1 字符流拷贝文件
        • 1)字符流拷贝文本文件:
        • 2)字符流拷贝非文本文件:
      • 3.3.2 字节流拷贝文件
        • 1)字节流拷贝文本文件
        • 2)字节流拷贝非文本文件
        • 3)字节流读取字符
  • 四、IO异常的处理
    • 4.1 JDK7前处理
    • 4.2 JDK7的处理
      • 4.2.1 JDK7处理异常的方式
      • 4.2.2 AutoCloseable接口
  • 五、属性集
    • 5.1 概述
    • 5.2 Properties类
      • 5.2.1 构造方法
      • 5.2.2 基本的存储方法
      • 5.2.3 加载流
  • 记得点赞~!!!


14【IO流基础】

一、IO概述

1.1 IO流简介

I(Input)O(Output):中文翻译为输入输出,我们知道计算机的数据不管是软件、视频、音乐、游戏等最终都是存储在硬盘中的,当我们打开后,由CPU将硬盘中的数据读取到内存中来运行。这样一个过程就产生了I/O(输入/输出)

14【IO流基础】_第1张图片

水的流向我们成为水流,数据的流动成为数据流,我们简称为流;数据的流向根据流向的不同,我们分为输入(Input)流和输出(Output)流,简称输入输出流,或称IO流;

1.2 IO流的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从输入设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到输出设备上的流。

根据操作数据单位的不同分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位(主要操作文本数据),读写数据的流。

在Java中描述流的底层父类:

抽象基类 输入流 输出流
字节流 字节输入流(InputStream) 字节输出流(OutputStream)
字符流 字符输入流(Reader) 字符输出流(Writer)

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

二、字节流

字节流有两个顶层接口父类,分别是字节输入流(InputStream)和字节输出流(OuputStream)

14【IO流基础】_第2张图片

2.1 字节输出流

OutputStream是所有字节输出的顶层父类,该父类提供如下公共方法:

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b) :将指定的字节输出流。

Tips:close方法,当完成流的操作时,必须调用此方法,释放系统资源。

2.1.1 FileOutputStream类

FileOutputStream是OutputStream中一个常用的子类,他可以关联一个文件,用于将数据写出到文件。

1)构造方法

FileOutputStream的构造方法如下:

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

Tips:当创建一个流对象时,需要指定一个文件路径,如果该文件以及存在则会清空文件中的数据,如果不存在则创建一个新的文件;

示例代码:

package com.dfbz.demo01;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_FileOutputStream的构造方法 {
    public static void main(String[] args) throws FileNotFoundException {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileOutputStream fos1 = new FileOutputStream(file);

        // 使用文件名称创建流对象
        FileOutputStream fos2 = new FileOutputStream("b.txt");
    }
}

2)其他方法

  • write(int b) :每次可以写出一个字节数据;
  • write(byte[] b):写出一个字节数组的数据;
  • write(byte[] b, int off, int len) :从字节数组中的off位置开始写出,写出len个字节;

  • 示例代码1-写出单个字节:
package com.dfbz.demo01;


import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_写出单个字符 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("000.txt");
        // 写出数据
        fos.write(97); // 写出第1个字节(a)
        fos.write(98); // 写出第2个字节(b)
        fos.write(99); // 写出第3个字节(c)
        // 关闭资源
        fos.close();
    }
}

Tips:虽然参数为int类型四个字节,但是只会保留一个字节的信息写出

  • 示例代码2-写出字节数组:
package com.dfbz.demo01;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_写出字节数组 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象(如果有这个文件将清空这个文件的数据)
        FileOutputStream fos = new FileOutputStream("000.txt");
        // 字符串转换为字节数组
        byte[] b = "我是中国人".getBytes();
        // 写出字节数组数据
        fos.write(b);
        // 关闭资源
        fos.close();
    }
}
  • 示例代码3-指定位置写出数据
package com.dfbz.demo01;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_指定位置写出 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("000.txt");

        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();

        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos.write(b,2,2);

        // 关闭资源
        fos.close();
    }
}

需要注意的是,在UTF-8编码下,一个中文占用3个字节,GBK编码下一个中文占用2个字节,因此在使用字节流来精确操作字符数据时将会变得非常麻烦,好在Java提供了一系列的字符流来帮助我们更加便捷的操作字符数据;

  • 示例代码:
package com.dfbz.demo01;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_字节流写出字符数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("000.txt");

        // 字符串转换为字节数组
        byte[] b = "我是中国人".getBytes();

        // "我"字占用3个字节,"是"也占用3个字节,写出2下标~4下标位置的数据将会是乱码
        fos.write(b,2,2);

        // 关闭资源
        fos.close();
    }
}

图解:

14【IO流基础】_第3张图片

修改写出的位置:

package com.dfbz.demo01;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_字节流写出字符数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("000.txt");

        // 字符串转换为字节数组
        byte[] b = "我是中国人".getBytes();

        /*
            "我"字占用3个字节,"是"也占用3个字节,写出2下标~4下标位置的数据将会是乱码
            0,3-->我
            3,3-->是
            6,3-->中
            9,3-->过
            12,3-->人
         */
        fos.write(b,6,3);

        // 关闭资源
        fos.close();
    }
}

但是上述代码仅仅是在UTF-8编码环境下才能运行成功,如果文件的编码换成了GBK,那么又会变得乱码,关于字符问题,我们可以使用后面学习的字符流来解决这个问题,让我们操作的单位不再是字节,而是字符!

14【IO流基础】_第4张图片

3)数据的追加

经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?

  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:

package com.dfbz.demo01;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06_数据的追加 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("abc.txt",true);

        // 字符串转换为字节数组
        byte[] buf = "abcde".getBytes();

        fos.write(buf);

        // 关闭资源
        fos.close();
    }
}

3)写出换行

在Windows操作系统中,我们在文本编辑器中,输入一个回车实际上是做了两个动作第一个动作是让光标到下一行,第二个动作则是回到这一行的开头;这两个动作分别对应着换行符(\n)和回车符(\r)

  • 换行符:下一行(newline),ascii为10。
  • 回车符:回到一行的开头(return),ascii为13。

因此在Windows系统中,换行符号是\r\n

代码使用演示:

package com.dfbz.demo01;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo07_写出换行符 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("000.txt");
        // 定义字节数组
        byte[] words = {97, 98, 99, 100, 101};
        // 遍历数组
        for (int i = 0; i < words.length; i++) {
            // 写出一个字节
            fos.write(words[i]);
            // 写出一个换行, 换行符号转成数组写出
            fos.write("\n".getBytes());
        }
        // 关闭资源
        fos.close();
    }
}

运行程序,查看文件内容:

14【IO流基础】_第5张图片

如果只输入\r,那么文件的内容应该是:

edcba

如果只输入\n,那么文件的内容应该是:

a
 b
  c
   d
    e

但是多次测试发现,换行只写一个\r或者是\n也能到达上述效果;这是因为市面上大部分的文本编辑工具都做了优化,如果一行的后面只有一个\r或者\n那么文本编辑工具会自动帮我们添上缺少的\n\r;但是为了规范起见,在windows系统中写出回车符最好还是使用\r\n

另外,不同的操作系统针对回车符也是不一样的标准:

  • Windows系统里,每行结尾是 回车+换行 ,即\r\n
  • Unix系统里,每行结尾只有 换行 ,即\n
  • Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。

还好我们可以通过System.getProperty("line.separator")可以获取操作系统的换行符;这样就可以做到跨操作系统了;

2.2 字节输入流

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流中读取一个字节数据,将读取到的数据返回;读取到文件的末尾返回-1
  • public int read(byte[] buf): 从输入流中读取一个字节数组的数据,返回实际读取到的有效字节数量;读取到文件的末尾返回-1
  • public int read(byte[] buf, int off, int len) :从输入流中读取len个字节,然后将读取到的字节从off位置开始放置到字节数组中,len不可以超出字节数组的长度;读取到文件的末尾返回-1
  • public int available():返回输入流中剩余的有效字节个数,如果没有有效字节(读取到末尾)则返回0
  • public long skip(long n):跳过输入流的n个字节
  • public void mark(int readlimit):对当前的位置进行标记,传递的readlimit参数是无意义的;
  • public void reset():将此流重新定位到最后一次对此输入流调用 mark 方法时的位置
  • public boolean markSupported():测试这个输入流是否支持mark和reset方法。 InputStream的markSupported方法返回false。

2.2.1 FileInputStream类

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

1)构造方法

  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

Tips:当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

  • 构造举例,代码如下:
package com.dfbz.demo02_字节输入流;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_FileInputStream构造方法 {
    public static void main(String[] args) throws FileNotFoundException {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);

        // 使用文件名称创建流对象
        FileInputStream fos2 = new FileInputStream("b.txt");
    }
}

2)读取字节数据

将000.txt文件的内容改为abcde

  • 示例代码1-读取单个字节:
package com.dfbz.demo02_字节输入流;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_读取单个字符 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileInputStream fis = new FileInputStream("000.txt");
        // 读取数据,返回一个字节
        int read = fis.read();
        System.out.println((char) read);            // a

        read = fis.read();
        System.out.println((char) read);            // b

        read = fis.read();
        System.out.println((char) read);            // c

        read = fis.read();
        System.out.println((char) read);            // d

        read = fis.read();
        System.out.println((char) read);            // e

        // 读取到末尾,返回-1
        read = fis.read();
        System.out.println(read);                   // -1

        // 再读还是-1
        read = fis.read();
        System.out.println(read);                   // -1

        // 关闭资源
        fis.close();
    }
}

运行结果:

14【IO流基础】_第6张图片

循环改进读取方式,代码使用演示:

package com.dfbz.demo02_字节输入流;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_使用循环读取数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileInputStream fis = new FileInputStream("000.txt");
        // 定义变量,保存数据
        int data;
        // 循环读取,只要读取的不是-1就继续读
        while ((data = fis.read()) != -1) {
            System.out.println((char) data);
        }
        // 关闭资源
        fis.close();
    }
}

Tips:虽然读取了一个字节,但是会自动提升为int类型。

  • 示例代码2-读取一个字节数组
package com.dfbz.demo02_字节输入流;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_读取一个字节数组的数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象.
        FileInputStream fis = new FileInputStream("000.txt"); // 文件中为abcde
        // 定义变量,作为有效个数
        int len ;
        // 定义字节数组,作为装字节数据的容器
        byte[] data = new byte[2];
        // 循环读取
        while ((len = fis.read(data)) != -1) {
            // 每次读取后,把数组变成字符串打印
            System.out.println(new String(data));
        }
        // 关闭资源
        fis.close();
    }
}

运行结果:

14【IO流基础】_第7张图片

发现d出现了两次,这是由于最后一次读取时,只读取到了一个有效字节“e”,替换了原数组的0下标的“c”,图解分析如下:

14【IO流基础】_第8张图片

我们在转换的时候不能全部转换,而是只转换有效的字节,所以要通过len(实际读取到的字节个数) ,获取有效的字节,来决定到底转换多少个字节;

  • 示例代码:
package com.dfbz.demo02_字节输入流;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_使用len来写出有效字节 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象.
        FileInputStream fis = new FileInputStream("abc.txt"); // 文件中为abcde
        // 定义变量,作为有效个数
        int len;
        // 定义字节数组,作为装字节数据的容器
        byte[] b = new byte[2];
        // 循环读取
        while ((len = fis.read(b)) != -1) {
            // 每次读取后,把数组的有效字节部分,变成字符串打印
            System.out.println(new String(b, 0, len));//  len 每次读取的有效字节个数
        }
        // 关闭资源
        fis.close();
    }
}
  • 示例代码3-将从字节流中读取到的数据按位置排列到字节数组中
package com.dfbz.demo02_字节输入流;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06_使用len来读取数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象.
        FileInputStream fis = new FileInputStream("000.txt"); // 文件中为abcde


        // 定义字节数组,作为装字节数据的容器
        byte[] data = new byte[100];

        // 从输入流中读取10个字节,将读取到的数据从字节数组的1索引位置开始放置
        int len = fis.read(data, 1, 10);

        System.out.println("实际读取到的数据: " + len);         // 5
        System.out.println(new String(data));               // abcde
        System.out.println(new String(data, 0, len));       // abcd

        // 关闭资源
        fis.close();
    }
}

运行示例:

14【IO流基础】_第9张图片

3)其他方法

  • 示例代码:
package com.dfbz.demo02_字节输入流;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo07_字节流的其他操作 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("000.txt"); // 文件中为abcde

        System.out.println((char)fis.read());           // a
        System.out.println((char)fis.read());           // b

        fis.mark(1);

        System.out.println((char)fis.read());           // c
        System.out.println((char)fis.read());           // d

        /*
           InputStream的mark/reset方法是留给其他子类的,FileInputStream并不支持这两个方法,因此会出现异常:
         Exception in thread "main" java.io.IOException: mark/reset not supported
         */
        fis.reset();
        System.out.println((char)fis.read());           // c

        // 关闭资源
        fis.close();
    }

    /**
     * skip方法
     * @throws Exception
     */
    public static void test2() throws Exception {
        FileInputStream fis = new FileInputStream("000.txt"); // 文件中为abcde

        // 跳过一个字节(a)
        fis.skip(1);
        System.out.println((char)fis.read());       // b
        System.out.println((char)fis.read());       // c

        // 跳过一个字节(d)
        fis.skip(1);
        System.out.println((char)fis.read());        // e
        System.out.println(fis.read());             // -1

        // 往前面跳2个字节()
        fis.skip(-2);
        System.out.println((char)fis.read());             //d
        System.out.println((char)fis.read());             //e

        // 关闭资源
        fis.close();
    }

    /**
     * available方法
     * @throws Exception
     */
    public static void test1() throws Exception {
        FileInputStream fis = new FileInputStream("1010.txt"); // 文件中为ab

        System.out.println(fis.available());            // 2
        System.out.println(fis.read());                 // 97

        System.out.println(fis.available());            // 1
        System.out.println(fis.read());                 // 98

        System.out.println(fis.available());            // 0
        System.out.println(fis.read());                 // -1

        System.out.println(fis.available());            // -1
        System.out.println(fis.read());

        // 关闭资源
        fis.close();

    }
}

2.3 字节流练习

FileOutputStream 主要按照字节方式写文件,例如:我们做文件的复制,首先读取文件,读取后在将该文件另写一份保存到磁盘上,这就完成了备份

  • 示例代码:
package com.dfbz.demo02;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo06 {
    public static void main(String[] args) throws IOException {
        // 1.创建输入流,用于读取文件
        FileInputStream fis = new FileInputStream("D:\\apache-tomcat-8.0.43.zip");
        // 2. 创建输出流,用于写出文件
        FileOutputStream fos = new FileOutputStream("D:\\apache-tomcat-8.0.43_bak.zip");

        // 3. 定义数组大小(一次性读取多少个字节)
        byte[] b = new byte[1024];

        // 4. 定义变量,接受每次实际读取到的数据长度
        int len;

        // 5. 循环读取并写出
        while ((len = fis.read(b))!=-1) {
            // 将数据数组中的数据写入关联的指定文件
            fos.write(b, 0 , len);
        }

        // 6. 释放资源
        fos.close();
        fis.close();
    }
}

三、字符流

计算机都是按照字节进行存储的,我们之前学习过编码表,通过编码表可以将字节转换为对应的字符,但是世界上有非常多的编码表,不同的编码表规定的单个字符所占用的字节可能都不一样,例如**在GBK编码表中一个中文占2个字节,UTF8编码表则占3个字节;**且一个中文字符都是由多个字节组成的,为此我们不能再基于字节的操作单位来操作文本文件了,因为这样太过麻烦,我们希望基于字符来操作文件,一次操作读取一个“字符”而不是一个“字节”,这样在操作文本文件时非常便捷;

3.1 字符输入流

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read():每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1
  • public int read(char[] buf): 每次读取一个字符数组的数据读取到buf中,返回读取到的有效字符个数,读取到末尾时,返回-1
  • public int read(char buf[], int off, int len):每次读取len个字符

3.1.1 FileReader类

java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

Tips:Windows系统的中文编码默认是GBK编码表。idea中默认是UTF-8

1)构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

  • 构造举例,代码如下:
package com.dfbz.demo01_字符输出流;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_FileReader构造方法 {
    public static void main(String[] args) throws FileNotFoundException {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);

        // 使用文件名称创建流对象
        FileReader fr2 = new FileReader("b.txt");
    }
}

2)其他方法

  • public int read():每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1
  • public int read(char buf[]):每次读取一个字符数组的数据读取到buf中,返回读取到的有效字符个数,读取到末尾时,返回-1
  • public int read(char cbuf[], int offset, int length):从输入流中读取len个字符,然后将读取到的字符从字符数组的off位置开始放置,len不可以超出字符数组的长度;读取到文件末尾返回-1

  • 示例代码1-读取单个字符:
package com.dfbz.demo01_字符输出流;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_读取单个字符 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("001.txt");

        // 定义变量,保存数据
        int data;

        // 循环读取
        while ((data = fr.read()) != -1) {
            // 以数值的方式输出
//            System.out.println(b);

            // 以字符的方式输出
            System.out.println((char) data);
        }
        // 关闭资源
        fr.close();
    }
}

输出结果:

我
是
中
国
人

Tips:虽然读取了一个字符,但是会自动提升为int类型,输出时注意转换为char。

  • 示例代码2-读取字符数组:
package com.dfbz.demo01_字符输出流;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_读取一个字符数组的数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("001.txt");
        // 定义变量,保存有效字符个数
        int len;
        // 定义字符数组,作为装字符数据的容器
        char[] buf = new char[2];
        // 循环读取
        while ((len = fr.read(buf)) != -1) {
            System.out.println(new String(buf));
        }
        // 关闭资源
        fr.close();
    }
}

输出结果:

我是
中国
国人

示例代码3-获取有效的字符改进:

package com.dfbz.demo01_字符输出流;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_使用len来写出有效字符 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("001.txt");
        // 定义变量,保存有效字符个数
        int len;
        // 定义字符数组,作为装字符数据的容器
        char[] buf = new char[2];
        // 循环读取
        while ((len = fr.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }
        // 关闭资源
        fr.close();
    }
}

输出结果:

我是
中国
人
  • 示例代码4-将从字符流中读取到的数据按位置排列到字符数组中:
package com.dfbz.demo01_字符输出流;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_使用len来读取数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("001.txt");

        // 准备一个字符数组来接收字符流读取的数据l
        char[] buf = new char[100];

        // 从输入流中读取10个字符,将读取到的数据从字符数组的3索引位置开始放置数据
        int len = fr.read(buf, 3, 10);

        System.out.println("读取到的有效字符: " + len);             // 5
        System.out.println(new String(buf));                    // 我是中国人
        System.out.println(new String(buf, 0, len));    		// 我是

        // 关闭资源
        fr.close();
    }
}

运行程序,查看效果:

14【IO流基础】_第10张图片

3.2 字符输出流

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf) 写入字符数组。
  • abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str) 写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush() 刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。

3.2.1 FileWriter类

java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

1)构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

  • 构造举例,代码如下:
package com.dfbz.demo02_字符输入流;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_FileWriter的构造方法 {
    public static void main(String[] args) throws IOException {
        // 使用File对象创建流对象
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);

        // 使用文件名称创建流对象
        FileWriter fw2 = new FileWriter("b.txt");
    }
}

2)写出字符数据

  • 示例代码1-写出单个字符数据:
package com.dfbz.demo02_字符输入流;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_写出单个字符数据 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("002.txt");

        // 写出数据
        fw.write(97); // 写出第1个字符
        fw.write('b'); // 写出第2个字符
        fw.write('C'); // 写出第3个字符
        fw.write(22909); // 写出第4个字符,中文编码表中22909对应一个汉字"好"。

      	/*
        【注意】关闭资源时,与FileOutputStream不同。
      	 如果不关闭,数据只是保存到缓冲区,并未保存到文件。
        */
         fw.close();
    }
}

Tips:未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。

  • 示例代码2-写出字符数组:
package com.dfbz.demo02_字符输入流;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_写出字符数组 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("002.txt");

        // 字符串转换为字节数组
        char[] chars = "我是中国人".toCharArray();

        // 写出字符数组
        fw.write(chars); // 我是中国人

        // 写出从索引2开始,2个字节。索引2是'中',两个字节,也就是'中国'。
        fw.write(chars,2,2); // 中国

        // 关闭资源
        fw.close();
    }
}

文件内容:

我是中国人中国

3)续写和换行

操作类似于FileOutputStream。

package com.dfbz.demo02_字符输入流;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_续写和换行 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象,可以续写数据
        FileWriter fw = new FileWriter("002.txt", true);
        // 写出字符串
        fw.write("我是");
        // 写出换行
        fw.write("\r\n");
        // 写出字符串
        fw.write("中国人");
        // 关闭资源
        fw.close();
    }
}

4)关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

代码使用演示:

package com.dfbz.demo02_字符输入流;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_flush_close方法 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("002.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();

        // 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();
    }
}

Tips:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。

3.3 拷贝文件问题

3.3.1 字符流拷贝文件

字符流只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时可以使用字符流,其他情况使用字节流;

准备一个text.txt文档:

先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。

宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。

侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。

将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。

亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。

先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。

愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。

今当远离,临表涕零,不知所言。

1)字符流拷贝文本文件:

package com.dfbz.demo03_字符流注意事项;

import java.io.FileReader;
import java.io.FileWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_字符流拷贝文本 {
    public static void main(String[] args) throws Exception {

        FileReader fr = new FileReader("text.txt");
        FileWriter fw = new FileWriter("text_copy.txt");

        // 准备一个字符数组接收字符数据
        char[] data = new char[1024];

        int len;

        while ((len = fr.read(data)) != -1) {
            fw.write(data,0,len);
        }

        fr.close();
        fw.close();
    }
}

打开text_copy.txt文件发现内容正常;

2)字符流拷贝非文本文件:

package com.dfbz.demo03_字符流注意事项;

import java.io.FileReader;
import java.io.FileWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_字符流拷贝非文本 {
    public static void main(String[] args) throws Exception {
        FileReader fr = new FileReader("100.png");
        FileWriter fw = new FileWriter("100_copy.png");

        // 准备一个字符数组接收字符数据
        char[] data = new char[1024];

        int len;

        while ((len = fr.read(data)) != -1) {
            fw.write(data,0,len);
        }

        fr.close();
        fw.close();
    }
}

打开100_copy.png发现文件损坏;

  • 得出结论:字符流可以拷贝文本文件,但不可以拷贝非文本文件
  • 原因:因为字符流在拷贝文件过程中,就先会将读取到的数据转换为字符,而非文本文件内容本质上并不是一个字符,因此要转换为对应的字符会出现乱码,而最终将乱码写入到新的文件中,造成新的文件损坏;

3.3.2 字节流拷贝文件

由于拷贝文件时不需要将文本转换为对应的字符,而是直接基于字节的搬运,因此字节流既可以拷贝文本文件,又可以拷贝非文本文件

1)字节流拷贝文本文件

package com.dfbz.demo03_拷贝文件;

import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_字节流拷贝文本文件 {
    public static void main(String[] args) throws Exception {

        FileInputStream fis = new FileInputStream("text.txt");
        FileOutputStream fos = new FileOutputStream("text2.txt");

        int len;
        byte[] data = new byte[1024];

        while ((len = fis.read(data)) != -1) {
            fos.write(data,0,len);
        }

        fis.close();
        fos.close();
    }
}

2)字节流拷贝非文本文件

package com.dfbz.demo03_拷贝文件;

import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_字节流拷贝非文本文件 {
    public static void main(String[] args) throws Exception {

        FileInputStream fis = new FileInputStream("100.png");
        FileOutputStream fos = new FileOutputStream("101.png");

        int len;
        byte[] data = new byte[1024];

        while ((len = fis.read(data)) != -1) {
            fos.write(data,0,len);
        }

        fis.close();
        fos.close();
    }
}

3)字节流读取字符

我们指定UTF-8的字符占用3个字节,GBK的字符占用2个字节,那么固定编码表的情况下(假设为UTF8),我们将字节数组大小定义为3来读取,是不是就可以很完美的读取所有的字符呢?

  • 示例代码:
package com.dfbz.demo03_拷贝文件;

import java.io.FileInputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_字节流读取文本 {
    public static void main(String[] args) throws Exception {

        FileInputStream fis = new FileInputStream("text.txt");

        int len;
        byte[] data = new byte[3];      //定义一个大小为3的字节数组

        while ((len = fis.read(data)) != -1) {
            /*
                一次读取三个字节,刚好是一个汉字.可以解决中文乱码问题,这种方法是能解决此问题  
                但如果中间含有一个字节的符号  例如, 1 + 回车符等等...就会出现乱码问题
                字节流不适用于读取文本文件
            */
            System.out.print(new String(data,0,len));
        }

        fis.close();
    }
}

运行代码,查看控制台:

先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。

����������������������������������������������������������������������������������������������������������������������������

������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。

����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。

����������������������������������������������������������������������������������������������������������������������������������������������������������������������������

���������������������������������������������

发现还是出现乱码问题;画图分析:

14【IO流基础】_第11张图片

四、IO异常的处理

4.1 JDK7前处理

之前的入门练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try...catch...finally 代码块,处理异常部分,代码使用演示:

package com.dfbz.demo;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_JDK7之前的标准处理方式 {
    public static void main(String[] args) {
        // 声明变量
        FileWriter fw = null;
        try {
            //创建流对象
            fw = new FileWriter("fw.txt");
            // 写出数据
            fw.write("我是中国人"); //我是中国人
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 不管是否出现异常都需要关闭流,释放资源
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4.2 JDK7的处理

4.2.1 JDK7处理异常的方式

Java7提供的try-with-resources语句,是异常处理的一大利器。该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

格式:

try (创建流对象语句,如果多个,使用';'隔开) {
	// 读写数据
} catch (IOException e) {
	e.printStackTrace();
}
  • 示例代码:
package com.dfbz.demo;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_JDK7的标准处理方式 {
    public static void main(String[] args) {
        /*
            在try()中的创建的流对象不管try代码中是否出现异常都会调用流对象的close方法来是否资源,触发创建流的时候失败了(null)则不会调用流的close方法
         */
        try (
                // 创建流对象
                FileReader fr = new FileReader("aaa.txt");
                FileWriter fw = new FileWriter("bbb.txt");
        ) {

            // 执行逻辑
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

4.2.2 AutoCloseable接口

在JDK7中,声明在try()中的类必须实现AutoCloseable接口,在资源处理完毕时,将自动的调用AutoCloseable接口中的close方法,如果没有实现AutoCloseable接口的类将不能写在try()中;

public interface AutoCloseable {
    void close() throws Exception;
}

测试AutoClouseable:

package com.dfbz.demo;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_AutoCloseable接口 {
    public static void main(String[] args) {
        
        // 不管try中的代码块是否出现异常都会调用TestAutoCloseable的close方法,除非TestAutoCloseable创建的时候出现异常了(创建失败了)
        try (
                TestAutoCloseable testAutoCloseable = new TestAutoCloseable();
        ) {
//            int i = 1 / 0;
            testAutoCloseable.test();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


class TestAutoCloseable implements AutoCloseable {

    public TestAutoCloseable() {
//        int i = 1 / 0;              // 如果创建TestAutoCloseable出现异常了(创建失败了),则不会调用close方法
    }

    public void test() {
        System.out.println("测试方法");
    }

    @Override
    public void close() throws Exception {
        System.out.println("close方法调用啦!");
    }
}

查看控制台运行:

14【IO流基础】_第12张图片

我们可以看到四个父接口都继承了AutoCloseable,也就是说Java中的所有流都实现了AutoCloseable接口:

14【IO流基础】_第13张图片

五、属性集

5.1 概述

java.util.Properties 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties 方法就是返回一个Properties对象。

5.2 Properties类

5.2.1 构造方法

  • public Properties() :创建一个空的属性列表。

5.2.2 基本的存储方法

  • public Object setProperty(String key, String value) : 保存一对属性。
  • public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
  • public Set stringPropertyNames() :所有键的名称的集合。
package com.dfbz.demo;

import java.io.FileNotFoundException;
import java.util.Properties;
import java.util.Set;

public class Demo01_Properties基本用法 {
    public static void main(String[] args) throws FileNotFoundException {
        // 创建属性集对象
        Properties properties = new Properties();
        // 添加键值对元素
        properties.setProperty("filename", "a.txt");
        properties.setProperty("length", "209385038");
        properties.setProperty("location", "D:\\a.txt");

        // 打印属性集对象
        System.out.println(properties);
        // 通过键,获取属性值
        System.out.println(properties.getProperty("filename"));
        System.out.println(properties.getProperty("length"));
        System.out.println(properties.getProperty("location"));

        // 遍历属性集,获取所有键的集合
        Set<String> names = properties.stringPropertyNames();

        // 打印键值对
        for (String name : names) {
            System.out.println(name + " -- " + properties.getProperty(name));
        }
    }
}

输出结果:

输出结果:
{filename=a.txt, length=209385038, location=D:\a.txt}
a.txt
209385038
D:\a.txt
filename -- a.txt
length -- 209385038
location -- D:\a.txt

5.2.3 加载流

  • public void load(InputStream inStream): 从字节输入流中读取键值对。

参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。文本数据格式:

username=root
password=123
[email protected]

加载代码演示:

package com.dfbz.demo;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_load方法加载流 {
    public static void main(String[] args) throws IOException {
        // 创建属性集对象
        Properties prop = new Properties();

        // 加载文本中信息到属性集
        prop.load(new FileInputStream("prop.txt"));

        // 遍历集合并打印
        Set<String> names = prop.stringPropertyNames();
        for (String name : names ) {
            System.out.println(name+" -- "+prop.getProperty(name));
        }
    }
}

输出结果:

username=root
password=123
[email protected]

Tips:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。


上一篇13【线程等待、状态、线程池、File类】


下一篇15【IO流增强】

目录【JavaSE零基础系列教程目录】


记得点赞~!!!

你可能感兴趣的:(#,《JavaSE系列》,IO流,字节流,字符流,IO,JavaSE)