IO的基本概述

  • IO流概述
    • IO流用来处理设备之间的数据传输,比如常见存储文件
    • 上传文件和下载文件
  • IO流前奏
    • 讲解IO流之前为什么先讲解异常和File类呢?
    • 因为File表示的是IO流将来要操作的文件,所以我们需要学习File类。
    • 而常见操作文件无非就是上传文件和下载文件,在这个操作的过程中可能出现问题,
    • 出现问题后,我们需要对对应的代码进行处理。所以我们需要学习异常。
  • IO流整体学习知识体系
    • IO流概述
      • IO流用来处理设备之间的数据传输
      • Java对数据的操作是通过流的方式
      • Java用于操作流的对象都在IO包中
    • IO流分类
      • a:按照数据流向
        • 输入流 读入数据 输硬盘 —-> 内存
        • 输出流 写出数据 内存 —-> 硬盘
      • b:按照数据类型
        • 字节流
        • 字符流
        • 什么情况下使用哪种流呢?
        • 如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。
        • 如果你什么都不知道,就用字节流
      • 字节流和字符流
        • 字节流
          • 字节输入流 InputStream 读
          • 字节输出流 OutputStream 写
        • 字符流
          • 字符输入流 Reader 读
          • 字符输出流 Writer 写
        • 上面的4个流对象都是抽象类,我们不能直接使用,我们需要使用子类

IO流的概念

  • Java的IO流是实现输入/输出的基础
    • 它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出源抽象表述为”流”。
    • Java在java.io包定义多个流类型来实现输入和输出
  • 流:可以理解成为一组有顺序的、有起点、有终点的动态数据集合
    • 有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
  • 流有输入和输出,输入时是流从数据源流向程序。输出时是流从程序传向数据源,而数据源可以是内存,文件,网络或程序等。

流的分类

  • 根据流的流向以及操作的数据单元不同,将流分为了四种类型,每种类型对应一种抽象基类。

  • 这四种抽象基类分别为:InputStream,Reader,OutputStream以及Writer。

  • 四种基类下,对应不同的实现类,具有不同的特性。在这些实现类中,又可以分为节点流和处理流。下面就是整个由着四大基类支撑下,整个IO流的框架图。

输入流和输出流

  • 根据数据流向不同分为:输入流和输出流。
    • 输入流: 只能从中读取数据,而不能向其写入数据。
    • 输出流:只能向其写入数据,而不能从中读取数据。

节点流和处理流

  • 按照流的角色来分,可以分为节点流和处理流。

  • 可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被成为低级流。

  • 处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能,处理流也被称为高级流

    1
    2
    3
    4
    //节点流,直接传入的参数是IO设备
    FileInputStream fis = new FileInputStream("test.txt");
    //处理流,直接传入的参数是流对象
    BufferedInputStream bis = new BufferedInputStream(fis);
    • 当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。使用处理流的一个明显好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装节点流的变化,程序实际所访问的数据源也相应地发生变化。

    • 实际上,Java使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。

IO流基类概述

  • IO流基类概述
    • a:字节流的抽象基类:
      • InputStream ,OutputStream。
    • b:字符流的抽象基类:
      • Reader , Writer。
      • 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
      • 如:InputStream的子类FileInputStream。
      • 如:Reader的子类FileReader。
  • 字节流和字符流:
    • 字节流:以字节为单位,每次次读入或读出是8位数据。可以读任何类型数据。
    • 字符流:以字符为单位,每次次读入或读出是16位数据。其只能读取字符类型数据。
  • 输出流和输入流:
    • 输出流:从内存读出到文件。只能进行写操作。
    • 输入流:从文件读入到内存。只能进行读操作。
    • 注意:这里的出和入,都是相对于系统内存而言的。
  • 节点流和处理流:
    • 节点流:直接与数据源相连,读入或读出。
    • 处理流:与节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。
    • 为什么要有处理流?直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。

字节和字符

什么是字节

  • 关于字节,我们在学习8大基本数据类型中都有了解,每个字节(byte)有8bit组成,每种数据类型又几个字节组成等。

什么是字符

  • 字符流出现的原因
    • 字符流出现的原因:由于字节流操作中文不是特别方便,所以,java就提供了字符流。
    • 字符流: 字符流 = 字节流 + 编码表
  • 字符流的由来:
    • 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。
  • 关于字符
    • 可能代表一个汉字或者英文字母。

区别

  • 字节流和字符流和用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同。
  • 字节流和字符流的区别:
    • 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
    • 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据,比如文本内容。

unicode编码

  • Java采用unicode编码,2个字节来表示一个字符。
    • Java采用unicode来表示字符,一个中文或英文字符的unicode编码都占2个字节。
    • 但如果采用其他编码方式,一个字符占用的字节数则各不相同。
  • 这点与C语言中不同,C语言中采用ASCII。在大多数系统中,一个字符通常占1个字节,但是在0~127整数之间的字符映射,unicode向下兼容ASCII。
  • 可能有点晕,举个例子解释下。
    • 例如:Java中的String类是按照unicode进行编码的,当使用String(byte[] bytes, String encoding)构造字符串时,encoding所指的是bytes中的数据是按照那种方式编码的,而不是最后产生的String是什么编码方式,换句话说,是让系统把bytes中的数据由encoding编码方式转换成unicode编码。
    • 如果不指明,bytes的编码方式将由jdk根据操作系统决定。

如何获取字节

  • getBytes(String charsetName)使用指定的编码方式将此String编码为 byte 序列,并将结果存储到一个新的 byte 数组中。如果不指定将使用操作系统默认的编码方式,我的电脑默认的是GBK编码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class YC {
    public static void main(String[] args){
    String str = "你好hello";
    int byte_len = str.getBytes().length;
    int len = str.length();
    System.out.println("字节长度为:" + byte_len);
    System.out.println("字符长度为:" + len);
    System.out.println("系统默认编码方式:" + System.getProperty("file.encoding"));
    }
    }

    输出结果
    字节长度为:9
    字符长度为:7
    系统默认编码方式:GBK
  • 为什么字节和字符长度不同

    • 在 GB 2312 编码或 GBK 编码中,一个英文字母字符存储需要1个字节,一个汉字字符存储需要2个字节。
    • 在UTF-8编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节。
    • 在UTF-16编码中,一个英文字母字符存储需要2个字节,一个汉字字符储存需要3到4个字节(Unicode扩展区的一些汉字存储需要4个字节)。
    • 在UTF-32编码中,世界上任何字符的存储都需要4个字节。
  • 简单来讲,一个字符表示一个汉字或英文字母,具体字符与字节之间的大小比例视编码情况而定。有时候读取的数据是乱码,就是因为编码方式不一致,需要进行转换,然后再按照unicode进行编码。

String中编解码问题

  • String(byte[] bytes, String charsetName):通过指定的字符集解码字节数组
  • byte[] getBytes(String charsetName):使用指定的字符集合把字符串编码为字节数组
  • 编码:把看得懂的变成看不懂的: String -- byte[]
  • 解码:把看不懂的变成看得懂的: byte[] -- String

字节流

  • 只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。
  • 流使用完毕后必须调用close()方法关闭流对象

InputStream

  • InputStream 是所有的输入字节流的父类,它是一个抽象类,主要包含三个方法:

    1
    2
    3
    4
    5
    6
    //读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。
    int read()
    //读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1。
    int read(byte[] buffer)
    //读取length个字节并存储到一个字节数组buffer,从off位置开始存,最多len, 返回实际读取的字节数,如果读取前以到输入流的末尾返回-1。
    int read(byte[] buffer, int off, int len)

OutputStream

  • OutputStream 是所有的输出字节流的父类,它是一个抽象类,主要包含如下四个方法:

    1
    2
    3
    4
    5
    6
    7
    8
    //向输出流中写入一个字节数据,该字节数据为参数b的低8位。
    void write(int b) ;
    //将一个字节类型的数组中的数据写入输出流。
    void write(byte[] b);
    //将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。
    void write(byte[] b, int off, int len);
    //将输出流中缓冲的数据全部写出到目的地。
    void flush();

FileInputStream

  • 构造方法

    • FileOutputStream(File file)
    • FileOutputStream(String name)
  • FileInputStream读取数据一次一个字节

    • int read():一次读取一个字节
    • read(数组)
    • read(数组,数组起始位置,存的个数)
    • available():返回与之关联的文件的字节数

    起始位置 + 存的个数之和 要小于数组的长度,否者出现数组下标越界的异常

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public static void main(String[] args) throws Exception {
    File file = new File("C:\\Users\\fulsun\\day16\\a.txt");
    if (!file.exists()) {
    file.createNewFile();
    }

    // 1. 创建流对象
    // 读取文件Inputstream,可能出现“文件找不到异常”
    FileInputStream fis = new FileInputStream(file);
    // 调用系统资源,创建了一个a.txt文件
    FileOutputStream fos = new FileOutputStream(file);

    // 2. 读/写
    // 写
    String str = "Hello.";
    // 把字符串转换成字节数组
    byte[] bytes = str.getBytes();
    // 调用方法写数据
    fos.write(bytes);

    //读
    int i;
    while ((i = fis.read()) != -1) {
    System.out.print((char) i);
    }

    // 3. 关闭流对象 作用: 通知系统释放关于该文件的资源
    fos.close();
    fis.close();
    }

FileOutputStream

write()方法

  • public void write(int b):写一个字节

  • public void write(byte[] b):写一个字节数组

  • public void write(byte[] b,int off,int len):写一个字节数组的一部分

    起始位置 + 存的个数之和 要小于数组的长度,否者出现数组下标越界的异常

  • FileOutputStream写出数据实现换行和追加写入

    • FileOutputStream fos = new FileOutputStream(destFile,false);//默认覆盖
    • FileOutputStream fos2 = new FileOutputStream(destFile,true);//追加
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) throws IOException {
    // public FileOutputStream(String name, boolean append)
    // 表示追加写入
    FileOutputStream fos = new FileOutputStream("c.txt" , true) ;
    // 写数据
    for(int x = 0 ; x < 5 ; x++) {
    byte[] bytes = ("呵呵" + x + "\r\n").getBytes() ;
    fos.write(bytes) ;
    }
    // 释放资源
    fos.close() ;
    }

字节流复制操作

字节流复制文本文件

  • 一次读取一个字节

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static void main(String[] args) throws IOException {
    /**
    * 复制文本文件:
    * 读和写
    * 分析:
    * 1: 创建两个对象一个是字节输入流对象,一个是字节输出流对象
    * 2: 一次读取一个字节,一次写一个字节
    * 3: 释放资源
    */
    // 创建两个对象一个是字节输入流对象,一个是字节输出流对象
    FileInputStream fis = new FileInputStream("FileOutputStreamDemo.java") ;
    FileOutputStream fos = new FileOutputStream("copyFile.java") ;
    // 一次读取一个字节,一次写一个字节
    int by = 0 ;
    while((by = fis.read()) != -1){
    fos.write(by) ;
    }
    // 释放资源
    fos.close() ;
    fis.close() ;
    }
  • 一次读取一个字节数组复制文件

  • //创建了一个和文件大小一样的缓冲区,刚刚好
    // byte[] buf = new byte[fis.available()];
    
    1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    ```java
    public static void main(String[] args) throws IOException {
    /**
    * 需求: 使用字节流复制文本文件,采用字节输入流的第二种读取数据的方式
    */
    // 创建字节输入流对象和字节输出流对象
    FileInputStream fis = new FileInputStream("FileOutputStreamDemo.java") ;
    FileOutputStream fos = new FileOutputStream("copyFile2.java") ;
    // 一次读取一个字节数组复制文件
    byte[] bytes = new byte[1024] ;
    int len = 0 ; // 作用: 记录读取到的有效的字节个数
    while((len = fis.read(bytes)) != -1){
    fos.write(bytes, 0, len) ;
    }
    // 释放资源
    fos.close() ;
    fis.close() ;
    }

字节流复制MP3

  • 一次读取一个字节

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static void main(String[] args) throws IOException {
    // 需求: 使用字节流复制mp3文件
    // 创建字节输入流和字节输出流对象
    FileInputStream fis = new FileInputStream("C:\\a.mp3") ;
    FileOutputStream fos = new FileOutputStream("D:\\a.mp3") ;
    // 复制文件
    int by = 0 ;
    while((by = fis.read()) != -1){
    fos.write(by) ;
    }
    // 释放资源
    fos.close() ;
    fis.close() ;
    }
  • 一次读取一个字节数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static void main(String[] args) throws IOException {
    /**
    * 需求: 使用字节流复制mp3文件一次读取一个字节数组
    */
    // 创建对象
    FileInputStream fis = new FileInputStream("C:\\a.mp3") ;
    FileOutputStream fos = new FileOutputStream("D:\\a.mp3") ;
    // 一次读取一个字节数组复制文件
    byte[] bytes = new byte[1024] ;

    int len = 0 ;
    while((len = fis.read(bytes)) != -1){
    fos.write(bytes, 0, len) ;
    }
    // 释放资源
    fos.close() ;
    fis.close() ;
    }

复制MP3效率

  • 基本流一次读取一个字节复制文件 时间是:88670毫秒

  • 基本流一次读取一个字节数组复制文件 时间是:130毫秒

  • 高效流一次读取一个字节复制文件 时间是:990毫秒

  • 高效流一次读取一个字节数组赋值文件 时间是:50毫秒

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public static void copyFile_4() throws IOException {
    // 创建高效流对象
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\a.avi")) ;
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\a.avi")) ;
    // 一次读取一个字节
    int by = 0 ;
    while((by = bis.read()) != -1){
    bos.write(by) ;
    }

    // 一次读取一个字节数组
    byte[] bytes = new byte[1024] ;
    int len = 0 ;
    while((len = bis.read(bytes)) != -1){
    bos.write(bytes, 0, len) ;
    }
    // 释放资源
    bos.close() ;
    bis.close() ;
    }

ObjectInputStream

  • 对象输入流是用来恢复之前序列化存储的对象,对象输入流可以确保每次从流中读取的对象能匹配Java虚拟机中已经存在的类,根据需求使用标准机制加载类。
  • 另外只有支持Serializable或者Externalizable接口的类可以从流中读取出来。对象输入流继承了InputStream中字节读取方法

常用方法

方法名 描述
boolean readBoolean() 读出布尔类型数据
byte readByte() 读取一个8比特字节
char readChar() 读取一个16比特的字符
double readDouble() 读取一个64比特的double类型数据
float readFloat() 读取一个32比特的float类型数据
void readFully(byte[] buf) 将流中所有的字节读取到buf字节数组中
void readFully(byte[] buf, int off, int len) 从流中读取len个字节数据到buf中,第一个字节存放在buf[off],第二个字节存放在buf[off+1],以此类推
int readInt() 读取一个32比特的int类型数据
long readLong() 读取一个64比特的long类型数据
Object readObject() 从流中读取一个对象数据,包括对象所属的类,该类的签名,类中非瞬态和非静态的字段值以及所有非超类型的字段值
short readShort() 读取一个16比特的short类型数据
int readUnsignedByte() 读取一个非负的8比特字节,转换为int类型返回
int readUnsignedShort() 读取非负的16比特的short类型数据,转换为int类型返回
String readUTF() 读取一个按UTF-8编码的String类型的数据

反序列化对象

  • Person类

    1
    2
    3
    4
    5
    6
    public class Person implements Serializable{
    public String name;
    public int year;
    public Date birth;
    //....
    }
  • 序列化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;

    import model.Person;

    public class ObjectTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
    FileInputStream fis = new FileInputStream("G:\\person.obj"); //构造文件输入流
    ObjectInputStream ois = new ObjectInputStream(fis); //用文件输出流初始化对象输入流

    Person p1 = (Person)ois.readObject(); //依次读出对象
    Person p2 = (Person)ois.readObject();

    System.out.println("p1的内容为:"+p1.toString());
    System.out.println("p2的内容为:"+p2.toString());
    }
    }

ObjectOutputStream

  • 对象输出流用来持久化对象的,可以将对象数据写入到文件,

  • 如果流是网络流,则可以将对象传输给其他用户进行通信。

  • 只有支持Serializable接口的对象支持写入到流,每个序列化对象被编码,包括类的名称和类的签名,以及类的对象中的字段值,以及arrays变量,以及从初始化对象引用的任何其他对象的闭包。

  • 对象输出流继承了OutputStream中字节写的方法

常用方法

方法名 描述
void writeBoolean(boolean val) 写一个布尔类型数据
void writeByte(int val) 写一个8比特字节数据,int类型只截取第8位
void writeBytes(String str) 将一个字符串数据当作一个字节序列写入流中
void writeChar(int val) 写入一个16比特的字符数据,参数为int,只截取低16位
void writeChars(String str) 将一个字符串数据当作一个字符序列写入
void writeDouble(double val) 写入一个64比特的double类型数据
void writeFloat(float val) 写入一个32比特的float类型数据
void writeLong(long val) 写入一个64比特的long类型数据
void writeObject(Object obj) 将一个对象写入到流中,包括类的名称和类的签名,以及类的对象中的字段值,以及arrays变量,以及从初始化对象引用的任何其他对象的闭包。
void writeShort(int val) 将一个16比特的short类型数据写入到流中
void writeUTF(String str) 将一个按UTF-8编码的字符串数据写入到流中

序列化对象

  • 代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.util.Date;

    import model.Person;

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

    FileOutputStream fos = new FileOutputStream("G:\\person.obj"); //定一个文件输出流,用来写文件
    ObjectOutputStream oos = new ObjectOutputStream(fos); //用文件输出流构造对象输出流

    Person p1 = new Person(); //定义两个对象
    Person p2 = new Person();

    p1.setName("福国");
    p1.setYear(23);
    p1.setBirth(new Date(95,6,12));

    p2.setName("zhangbin");
    p2.setYear(24);
    p2.setBirth(new Date(94,1,2));

    oos.writeObject(p1); //将两个对象写入文件
    oos.writeObject(p2);

    oos.close();
    fos.close();
    }
    }

字符流

  • 在执行完流操作后,要调用close()方法来关系输入流,因为程序里打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。

Reader

  • Reader 是所有的输入字符流的父类,它是一个抽象类,主要包含三个方法:

    1
    2
    3
    4
    5
    6
    //读取一个字符并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。
    int read()
    //读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回-1。
    int read(char[] cbuf)
    //读取length个字符,并存储到一个数组buffer,从off位置开始存,最多读取len,返回实际读取的字符数,如果读取前以到输入流的末尾返回-1。
    int read(char[] cbuf, int off, int len)
  • 对比InputStream和Reader所提供的方法,就不难发现两个基类的功能基本一样的,只不过读取的数据单元不同。

  • 除此之外,InputStream和Reader还支持如下方法来移动流中的指针位置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //在此输入流中标记当前的位置
    //readlimit - 在标记位置失效前可以读取字节的最大限制。
    void mark(int readlimit)
    // 测试此输入流是否支持 mark 方法
    boolean markSupported()
    // 跳过和丢弃此输入流中数据的 n 个字节/字符
    long skip(long n)
    //将此流重新定位到最后一次对此输入流调用 mark 方法时的位置
    void reset()

Writer

  • Writer 是所有的输出字符流的父类,它是一个抽象类,主要包含如下六个方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //向输出流中写入一个字符数据,该字节数据为参数b的低16位。
    void write(int c);
    //将一个字符类型的数组中的数据写入输出流,
    void write(char[] cbuf)
    //将一个字符类型的数组中的从指定位置(offset)开始的,length个字符写入到输出流。
    void write(char[] cbuf, int offset, int length);
    //将一个字符串中的字符写入到输出流。
    void write(String string);
    //将一个字符串从offset开始的length个字符写入到输出流。
    void write(String string, int offset, int length);
    //将输出流中缓冲的数据全部写出到目的地。
    void flush()
  • 可以看出,Writer比OutputStream多出两个方法,主要是支持写入字符和字符串类型的数据。

InputStreamReader

  • 转换流InputStreamReader的使用

构造方法

  • public InputStreamReader(InputStream in): 使用的默认的编码表

  • public InputStreamReader(InputStream in , String charsetName)使用指定的编码表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
    // 使用指定的编码表
    InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt") , "utf-8") ;
    // 读取数据
    int ch = 0 ;
    while((ch = isr.read()) != -1){
    System.out.print((char)ch);
    }
    // 释放资源
    isr.close() ;
    }
    }
  • 读取数据

    1
    2
    public int read()
    public int read(char[] cbuf)

OutputStreamWriter

  • 转换流OutputStreamWriter的使用

构造方式

  • public InputStreamReader(InputStream in)
  • public InputStreamReader(InputStream in , String charsetName) 使用指定的编码表

写数据方法

  • 字符流的5种写数据的方式

    1
    2
    3
    4
    5
    public void write(int c)
    public void write(char[] cbuf)
    public void write(char[] cbuf,int off,int len)
    public void write(String str)
    public void write(String str,int off,int len)
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
    // 创建: OutputStreamWriter
    // 创建: OutputStream的对象
    // FileOutputStream fos = new FileOutputStream("a.txt") ;
    // OutputStreamWriter osw = new OutputStreamWriter(fos) ;
    // OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt")) ;
    // public InputStreamReader(InputStream in , String charsetName) 使用指定的编码表
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt") , "UTF-8") ;
    // 调用方法
    osw.write("中国") ;
    // 释放资源
    osw.close() ;
    }
    }

字符流复制

复制单级文件夹

  • 复制单级文件夹,代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public static void main(String[] args) throws IOException {
    /**
    * 需求: 把C:\\course这个文件夹复制到D:\\course盘下
    * 分析:
    * 1: 把C:\\course这个目录封装成一个File对象
    * 2: 把D:\\course这个目录封装成一个File对象
    * 3: 判断D:\\course是否存在,如果存在就创建一个文件夹
    * 4: 获取C:\\course这个目录下所有的文件对应的File数组
    * 5: 遍历数组,获取元素进行复制
    */
    // 把C:\\course这个目录封装成一个File对象
    File srcFolder = new File("C:\\course") ;
    // 把D:\\course这个目录封装成一个File对象
    File destFolder = new File("D:\\course") ;
    // 判断D:\\course是否存在,如果存在就创建一个文件夹
    if(!destFolder.exists()){
    destFolder.mkdir() ;
    }
    // 复制文件夹
    IOUtils.copyFolder(srcFolder, destFolder, null) ;
    }

    public class IOUtils {

    public static void copyFolder(File srcFolder , File destFolder , FilenameFilter filenameFilter) throws IOException {
    File[] files = null ;
    if(filenameFilter == null) {
    files = srcFolder.listFiles() ;
    }else {
    files = srcFolder.listFiles(filenameFilter) ;
    }
    // 遍历
    for(File f : files) {
    // 创建目标文件
    String destFileName = f.getName() ;
    File destFile = new File(destFolder , destFileName) ;
    // 复制文件
    copyFile(f , destFile) ;
    }
    }

    public static void copyFile(File srcFile , File destFile) throws IOException {
    // 创建流对象
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;

    // 一次读取一个字节数组复制文件
    byte[] bytes = new byte[1024] ;
    int len = 0 ;
    while((len = bis.read(bytes)) != -1){
    bos.write(bytes, 0, len) ;
    }
    // 释放资源
    bos.close() ;
    bis.close() ;
    }
    }

复制指定目录下指定后缀名的文件并修改名称

  • 复制指定目录下指定后缀名的文件并修改名称

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    public static void main(String[] args) throws IOException {
    /**
    * 把C:\\demo这个目录下所有的以.java结尾的文件复制到D:\\demo中,然后将这个文件的后缀名更改为.jad
    */
    // 把C:\\demo这个目录下所有的以.java结尾的文件复制到D:\\demo中
    // 1: 把C:\\demo这个目录封装成一个File对象
    File srcFolder = new File("C:\\demo") ;
    // 2: 把D:\\demo这么目录封装成一个File对象
    File destFolder = new File("D:\\demo") ;
    // 3: 判断D:\\demo这个路径是否存在
    if(!destFolder.exists()) {
    destFolder.mkdir() ;
    }
    // 调用方法
    IOUtils.copyFolder(srcFolder, destFolder, new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
    return new File(dir , name).isFile() && name.endsWith(".java") ;
    }
    }) ;
    System.out.println("-----------------------------------------------------");
    // 获取destFolder下所有的文件对应的File数组
    File[] files = destFolder.listFiles() ;
    for(File f : files) {
    // 创建目标文件名称
    String destFileName = f.getName().replace(".java", ".jad") ;
    // 创建目标文件
    File destFile = new File(destFolder , destFileName) ;
    // 调用
    f.renameTo(destFile) ;
    }
    }


    public class IOUtils {

    public static void copyFolder(String srcPahtName , String destPathName , FilenameFilter filenameFilter) throws IOException {
    File srcFolder = new File(srcPahtName) ;
    File destFolder = new File(destPathName) ;
    if(!destFolder.exists()) {
    destFolder.mkdir() ;
    }
    copyFolder(srcFolder , destFolder , filenameFilter) ;
    }

    public static void copyFolder(File srcFolder , File destFolder , FilenameFilter filenameFilter) throws IOException {
    File[] files = null ;
    if(filenameFilter == null) {
    files = srcFolder.listFiles() ;
    }else {
    files = srcFolder.listFiles(filenameFilter) ;
    }
    // 遍历
    for(File f : files) {
    // 创建目标文件
    String destFileName = f.getName() ;
    File destFile = new File(destFolder , destFileName) ;
    // 复制文件
    copyFile(f , destFile) ;
    }
    }

    public static void copyFile(File srcFile , File destFile) throws IOException {
    // 创建流对象
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
    // 一次读取一个字节数组复制文件
    byte[] bytes = new byte[1024] ;
    int len = 0 ;
    while((len = bis.read(bytes)) != -1){
    bos.write(bytes, 0, len) ;
    }
    // 释放资源
    bos.close() ;
    bis.close() ;
    }
    }

中文乱码解决

  1. 字节流读取到数组,再显示read(byte[]): 将流中的数据读取到byte数组中,返回长度,在内存中读到数组中,效率高

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 拿一个袋子(byte[])
    // 文件的长度作为数组的长度
    // byte[] cbuf = new byte[(int) file.length()];

    // 流读取的长度作为数组的长度
    byte[] cbuf = new byte[fis.available()];

    // 将流中的数据读取到字节数组中
    System.out.println(fis.read(cbuf));

    // read(字节数组,字节数组的起始位置,存几个)
    fis.read(cbuf,4,2);

    // 从袋子里拿出来(转为字符)
    // System.out.println(new String(cbuf,"UTF-8"));
    System.out.println(new String(cbuf,"GBK"));
  2. 字符流InputStreamReader读取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 创建读/写的字节流对象
    FileInputStream fis = new FileInputStream(file);
    FileOutputStream fos = new FileOutputStream(file);
    // 包装流:字节--->字符流
    InputStreamReader reader = new InputStreamReader(fis);

    // 字符流读取
    int temp;
    while ((temp = reader.read()) != -1) {
    System.out.println((char) temp);
    }

Buffer缓冲流

  • Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此**缓冲流是一种处理流(包装流)**。

  • 当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。

    因此,缓冲流还是很重要的,我们在IO操作时记得加上缓冲流来提升性能

    • 特点:
      1. 读文件和写文件都使用了缓冲区,减少了读写次数,从而提高了效率
      2. 当创建这两个缓冲流的对象时,会创建内部缓冲数组,缺省使用32字节大小的缓冲区
      3. 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
      4. 当写入数据时,首先写入缓冲区,当缓冲区满时,其中的数据写入所连接的输出流。使用方法flush()可以强制将缓冲区的内容全部写入输出流。
      5. 关闭流的顺序和打开流的顺序相反。只要关闭高层流即可,关闭高层流其实关闭了底层节点流。
      6. Flush的使用:手动将buffer中内容写入文件。

缓冲流的特殊功能

BufferedInputStream

  • 构造方法: public BufferedInputStream(InputStream in)

  • 读取数据代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static void main(String[] args) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("e.txt")) ;
    // 一次读取一个字节
    //int by = 0 ;
    //while((by = bis.read()) != -1){
    // System.out.print((char)by);
    //}
    // 一次读取一个字节数组
    byte[] bytes = new byte[1024] ;
    int len = 0 ;
    while((len = bis.read(bytes)) != -1){
    System.out.print(new String(bytes , 0 , len));
    }
    // 释放资源
    bis.close() ;
    }

BufferedOutputStream

  • 构造方法:public BufferedOutputStream(OutputStream out)

  • 写出数据代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) throws IOException {
    // 创建FileOutputStream对象
    //FileOutputStream fos = new FileOutputStream("buf.txt") ;
    //BufferedOutputStream bof = new BufferedOutputStream(fos) ;
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("buf.txt")) ;
    // 调用方法
    bos.write("哈哈,我来了".getBytes()) ;
    // 释放资源
    bos.close() ;
    }

BufferedReader字符缓冲流

  • 构造方法:public BufferedReader(Reader in)

  • 特殊功能

    • public void newLine() 根据系统来决定换行符
  • 代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 使用高效的字符输入流进行读取数据
    * @throws IOException
    */
    private static void read() throws IOException{
    // 创建高效的字符输入流对象
    // BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("c.txt"))) ;
    BufferedReader br = new BufferedReader(new FileReader("c.txt")) ;
    // 读取数据
    // int ch = 0 ;
    // while((ch = br.read()) != -1){
    // System.out.print((char)ch);
    // }
    char[] chs = new char[1024] ;
    int len = 0 ;
    while((len = br.read(chs)) != -1){
    System.out.print(new String(chs , 0 , len));
    }
    // 释放资源
    br.close() ;
    }

BufferedWriter字符缓冲流

  • 构造方法:public BufferedWriter(Writer out)

  • 特殊功能

    • public void readLine() 一次读取一行数据
    • 包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
  • 代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 使用高效的字符输出流进行写数据
    * @throws IOException
    */
    private static void write() throws IOException {
    // 创建BufferedWriter对象
    // BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("c.txt"))) ;
    BufferedWriter bw = new BufferedWriter(new FileWriter("c.txt")) ;
    // 写数据
    bw.write("你好") ;
    // 释放资源
    bw.close() ;
    }

高效字符流复制

  • 一次读取一个字符数组复制文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class CopyFileDemo {
    public static void main(String[] args) throws IOException {
    // 创建高效的字符输入流对象
    BufferedReader br = new BufferedReader(new FileReader("OutputStreamWriterDemo.java")) ;
    // 创建高效的字符输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("copyFile3.java")) ;
    // 一次读取一个字符数组复制文件
    char[] chs = new char[1024] ;
    int len = 0;
    while((len = br.read(chs)) != -1){
    bw.write(chs, 0, len) ;
    }
    // 释放资源
    bw.close() ;
    br.close() ;
    }
    }
  • 一次读取一行复制文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class CopyFileDemo {
    public static void main(String[] args) throws IOException {
    /**
    * 需求: 使用高效的字符流中特有的功能复制文本文件
    */
    // 创建高效的字符输入流对象
    BufferedReader br = new BufferedReader(new FileReader("OutputStreamWriterDemo.java")) ;

    // 高效的字符输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("copyFile4.java")) ;

    // 复制文件
    // 一次读取一行复制文件
    String line = null ;
    while((line = br.readLine()) != null) {
    bw.write(line) ;
    bw.newLine() ;
    bw.flush() ;
    }
    // 释放资源
    bw.close() ;
    br.close() ;
    }
    }

PrintWriter打印输出字符流

  • 相当于是System.out.print(""),打印输出到文件中.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) throws FileNotFoundException {
    //1.
    PrintWriter pw = new PrintWriter("d:/data/number.txt");
    //2
    for(int i = 1; i <= 5; i++) {
    pw.println("第" + i + "个数字");
    // System.out.println("第" + i + "个数字");
    }
    //3.
    pw.close();
    }
  • BufferedReader 和PrintWriter一起使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter pw = new PrintWriter("d:/data/name.txt");
    //2
    String s ;
    System.out.println("--input name:");
    while(true) {
    s = bfr.readLine();
    if(s.equals("q")) {
    break;
    }
    pw.println(s);
    }
    //3
    bfr.close();
    pw.close();

自动释放资源

  • 不用close,系统指定释放资源

  • 前提:流要继承AutoCloseable;

  • 格式

    1
    2
    3
    4
    5
    6
    7
    try(声明需要释放的资源){

    可能产生异常的语句。

    }catch(异常){

    }
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    try(BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter pw = new PrintWriter("d:/data/name.txt");
    ){
    //2
    String s ;
    System.out.println("--input name:");
    while(true) {
    s = bfr.readLine();
    if(s.equals("q")) {
    break;
    }
    pw.println(s);
    }
    }catch(IOException e) {
    e.printStackTrace();
    }

文件操作练习

集合数据存储到文本文件

  • 把集合中的数据存储到文本文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public static void main(String[] args) throws IOException {
    /**
    * 把ArrayList集合中的数据存储到文本文件中
    * 分析:
    * 1: 创建ArrayList集合对象
    * 2: 添加数据
    * 3: 创建高效的字符输出流对象
    * 4: 遍历集合,获取每一个元素,然后通过流对象写出去
    * 5: 释放资源
    */
    // 创建ArrayList集合对象
    ArrayList<String> al = new ArrayList<String>() ;
    // 添加数据
    al.add("西施") ;
    al.add("貂蝉") ;
    al.add("杨玉环") ;
    al.add("王昭君") ;

    // 创建高效的字符输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("names.txt")) ;
    // 遍历集合,获取每一个元素,然后通过流对象写出去
    for(String name : al) {
    bw.write(name) ;
    bw.newLine() ;
    bw.flush() ;
    }
    // 释放资源
    bw.close() ;
    }

文本数据存储到集合中

  • 把文本文件中的数据存储到集合中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public static void main(String[] args) throws IOException {
    /**
    * 从文本文件中读取数据(每一行为一个字符串数据)到集合中,并遍历集合
    * 分析:
    * 1: 创建高效的字符输入流对象
    * 2: 创建集合对象
    * 3: 读取文本文件中的数据,将数据存储到集合中
    * 4: 释放资源
    * 5: 遍历集合
    */
    // 1: 创建高效的字符输入流对象
    BufferedReader br = new BufferedReader(new FileReader("names.txt")) ;
    // 2: 创建集合对象
    ArrayList<String> al = new ArrayList<String>() ;
    // 3: 读取文本文件中的数据,将数据存储到集合中
    String line = null ; // 作用: 用来记录读取到的行数据
    while((line = br.readLine()) != null) {
    al.add(line) ;
    }
    // 4: 释放资源
    br.close() ;
    // 5: 遍历集合
    for(String name : al) {
    System.out.println(name);
    }
    }

随机获取文本中姓名

  • 随机获取文本文件中的姓名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public static void main(String[] args) throws IOException {
    // 1: 创建集合对象
    ArrayList<String> students = new ArrayList<String> () ;
    // 2: 创建BufferedReader对象
    BufferedReader br = new BufferedReader(new FileReader("students.txt")) ;
    // 3: 读取数据,把数据存储到集合中
    String line = null ;
    while((line = br.readLine()) != null) {
    students.add(line) ;
    }
    // 4: 释放资源
    br.close() ;
    // 5: 生成一个随机数
    Random random = new Random() ;
    int index = random.nextInt(students.size());
    // 6: 把生成的随机数作为集合元素的索引,来获取一个元素
    String name = students.get(index) ;
    // 7: 把获取到的元素打印到控制台
    System.out.println(name);
    }

录入信息并且写入文件

  • 键盘录入学生信息按照总分排序并写入文本文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    public static void main(String[] args) throws IOException {

    /**
    * 需求:键盘录入3个学生信息(姓名,语文成绩(chineseScore),数学成绩(mathScore),英语成绩(englishScore)),按照总分从高到低存入文本文件
    * 分析:
    * 1: 创建一个学生类
    * 2: 创建一个集合对象TreeSet集合
    * 3: 键盘录入学生信息,把学生信息封装到学生对象中,然后把学生对象添加到集合中
    * 4: 创建一个高效的字符输出流对象
    * 5: 遍历集合,获取每一个元素,把其信息写入到文件中
    * 6: 释放资源
    */
    // 创建一个集合对象TreeSet集合
    TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
    // 总分
    int num = s2.getTotalScore() - s1.getTotalScore() ;
    // 比较姓名
    int num2 = (num == 0) ? s2.getName().compareTo(s1.getName()) : num ;
    // 返回
    return num2;
    }
    }) ;

    // 3: 键盘录入学生信息,把学生信息封装到学生对象中,然后把学生对象添加到集合中
    for(int x = 1 ; x <= 3 ; x++) {
    // 创建Scanner对象
    Scanner sc = new Scanner(System.in) ;
    System.out.println("请您输入第" + x + "个学生的姓名" );
    String sutName = sc.nextLine() ;
    System.out.println("请您输入第" + x + "个学生的语文成绩" );
    int chineseScore = sc.nextInt() ;
    System.out.println("请您输入第" + x + "个学生的数学成绩" );
    int mathScore = sc.nextInt() ;
    System.out.println("请您输入第" + x + "个学生的英语成绩" );
    int englishScore = sc.nextInt() ;

    // 把学生的信封装到一个学生对象中
    Student s = new Student() ;
    s.setName(sutName) ;
    s.setMathScore(mathScore) ;
    s.setChineseScore(chineseScore) ;
    s.setEnglishScore(englishScore) ;
    // 把学生的信息添加到集合中
    ts.add(s) ;
    }

    // 创建一个高效的字符输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("student.info")) ;
    bw.write("==========================================学生的信息如下====================================================") ;
    bw.newLine() ;
    bw.flush() ;
    bw.write("姓名\t\t总分\t\t数学成绩\t\t语文成绩\t\t英语成绩\t\t") ;
    bw.newLine() ;
    bw.flush() ;
    for(Student t : ts) {
    bw.write(t.getName() + "\t\t" + t.getTotalScore() + "\t\t" + t.getMathScore() + "\t\t" + t.getChineseScore() + "\t\t" + t.getEnglishScore()) ;
    bw.newLine() ;
    bw.flush() ;
    }
    // 释放资源
    bw.close() ;
    }