JavaSE学习总结(十七)IO流之字节流和字符流(上)/用流复制文件/流的异常处理/字节缓冲输入输出流/字符缓冲输入输出流/编码、解码和乱码

一、IO流分类

  • 按照数据流向
    输入流——>读入数据
    输出流——>写出数据

  • 按照数据类型
    字节流——>可以读写任何类型的文件,比如音频文件、视频文件、文本文件
    字符流——>只能读写文本文件

什么情况下使用哪种流呢?
如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。如果你什么都不知道,就用字节流。

二、IO流基类概述

  • 字节流抽象基类:
    InputStreamOutputStream
  • 字符流抽象基类:
    ReaderWriter

注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream
如:Reader的子类FileReader

三、字节流

(一)字节流继承关系与分类

下面是几个常用的字节流的继承关系
在这里插入图片描述
字节流分为又分为字节输入流和字节输出流,当从硬盘读取文件进内存时,用的是字节输入流(前提是该文件必须已经存在);当从内存写出数据进硬盘时,用的是字节输出流(该文件如果不存在,会自动创建)。
在这里插入图片描述

(二)FileOutputStream

需求:假如我们现在需要往一个文本文件中写一串数据 :Hello World
分析:

  • 我们现在操作的是文本文件,所有按照我们的想法,我们选择字符流,但是字节流是优先于字符流,所以先使用字节流
  • 因为我们要写入数据,所以我们应该使用字节流中的输出流——OutputStream
  • 我们发现OutputStream是一个抽象类,我们不能对其进行直接实例化,而我们需要使用子类对其进行实例化,那么选择哪个子类呢?——我们现在操作的是文件,所以我们选择的是FileOutputStream

1.构造方法

FileOutputStream(File file):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流。
FileOutputStream(File file, boolean append):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(String name, boolean append):创建一个向具有指定名称的文件中写入数据的输出文件流。

注意:输出流所关联的文件,如果不存在,会帮你自动创建

案例演示
演示前两种构造方法,后两种后面讲

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        //不需要写file.createNewFile();因为输出流所关联的文件,如果不存在,会自动帮你创建
        
        //FileOutputStream(File file)
        //创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
        FileOutputStream out1 = new FileOutputStream(file);
        
        //FileOutputStream(String name)
        //创建一个向具有指定名称的文件中写入数据的输出文件流。
        FileOutputStream out2 = new FileOutputStream("b.txt");
    }
}

创建字节输出流对象了做了几件事情?

  • 调用系统资源创建a.txt文件
  • 创建了一个FileOutputStream对象
  • 把FileOutputStream对象指向这个文件

2.三个write()方法

public void write(int b):写一个字节,若超过一个字节,则会出现乱码
public void write(byte[] b):写一个字节数组
public void write(byte[] b,int off,int len):写一个字节数组的一部分,off是起始位置,len是要写的长度

案例演示1
FileOutputStream的三个write()方法

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream out1 = new FileOutputStream(file);
        out1.write(97);//一次写一个字节,如果超过一个字节,则会丢弃掉多余字节,因此出现乱码
        out1.write(98);
        out1.write(99);
        //流使用完毕之后记得要释放资源
        out1.close();

        FileOutputStream out2 = new FileOutputStream("b.txt");
        out2.write(new byte[]{100,101,102});//一次写一个字节数组
        out2.close();

        String str="今天天气真好";//如何用字节流写这句话?将String转换成byte数组
        byte[] bytes = str.getBytes();
        FileOutputStream out3 = new FileOutputStream("c.txt");
        out3.write(bytes);//一次写一个字节数组
        out3.close();

        FileOutputStream out4 = new FileOutputStream("d.txt");
        out4.write(bytes,6,12);//一次写一个字节数组的一部分,从第6字节开始写12个字节(天气真好)
        //UTF-8 编码一个汉字占3个字节
        out4.close();
    }
}

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
注意事项:流使用完毕后记得释放资源

为什么一定要close()?

  • 通知系统释放关于管理文件的资源
  • 让IO流对象变成垃圾,等待垃圾回收器对其回收

案例演示2
FileOutputStream写出数据如何实现数据的换行

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream out1 = new FileOutputStream(file);
        out1.write("Hello World!".getBytes());
        out1.write("\r\n".getBytes());//写入一个换行符
        out1.write("Good Morning!".getBytes());
        out1.write("\r\n".getBytes());
        out1.write("Good Afternoon!".getBytes());
        out1.write("\r\n".getBytes());
        out1.close();
//        windows下的换行符用的是 \r\n
//        Linux下的换行符用的是	\n
//        Mac下的换行符用的是		\r
    }
}

在这里插入图片描述

案例演示3
假如我们写了一个Java程序,实现了输出流,写一段话到文本文件中。如果我们重复运行这个Java程序,文本文件就会不断刷新,每次运行也都是只有一段话,而如果我们重复运行程序的目的是在文本文件中重复追加这段话怎么办呢?
这就需要用到FileOutputStream的后两种构造方法FileOutputStream(File file, boolean append)
FileOutputStream(String name, boolean append)

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        //此构造的第二个参数代表是否追加写入,true:追加写入;false:不追加
        FileOutputStream out1 = new FileOutputStream(file,true);
        out1.write("Hello World!".getBytes());
        out1.write("\r\n".getBytes());//写入一个换行符
        out1.write("Good Morning!".getBytes());
        out1.write("\r\n".getBytes());
        out1.write("Good Afternoon!".getBytes());
        out1.write("\r\n".getBytes());
        out1.close();
    }
}

运行三次此程序的结果:
在这里插入图片描述

3.流的异常处理

前面我们对异常的处理方式都是抛出异常(throws IOException),其实也可以捕获这个异常

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

public class MyTest1 {
    public static void main(String[] args) {
        File file = new File("a.txt");
        FileOutputStream out1 = null;//为了finally中可以拿到
        try {
            out1 = new FileOutputStream(file);//创建文件字节输出流对象
            out1.write(100);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //如果创建FileOutputStream对象前就有异常,那么out1=null,会出现空指针异常
                //因此需要做非空判断
                if(out1!=null){
                    out1.close();//释放资源,此处也会有异常,我们也需要捕获
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

(三)FileInputStream

1.构造方法

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

注意:输入流所关联的文件,如果不存在,就报错了

2.三个read()方法

public int read():从此输入流中读取一个字节的数据。
public int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
public int read(byte[] b, int off, int len):从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。

注意:返回的值是读取到的字节数据,如果读取不到,则返回-1

案例演示1
给定一个a.txt,文本文件里内容是“abc”

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        //输入流所关联的文件,如果不存在,就报错了
        FileInputStream in = new FileInputStream(file);
        int b = in.read();//一次读取一个字节,如果读取不到,则会返回-1
        System.out.println(b);
        b = in.read();//一次读取一个字节
        System.out.println(b);
        b = in.read();//一次读取一个字节
        System.out.println(b);
        //释放资源
        in.close();
    }
}

在这里插入图片描述
案例演示2
一次读取一个byte数组的数据
给定一个a.txt,文本文件里内容是“Hello World”

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        //输入流所关联的文件,如果不存在,就报错了
        FileInputStream in = new FileInputStream(file);
        byte[] bytes = new byte[1024];//缓冲区思想:创建一个字节数组充当容器
        int len = in.read(bytes);//把文件中的数据,读取到容器中,返回值是读取到的有效字节个数
        System.out.println(len);
        //我们想查看bytes数组里的数据怎么查看?
        //用for循环行不通,因为我们事先知道a.txt中的数据小于1024字节
        //因此bytes数组后面的数据全都是0
        //我们可以将字节数组有效数据转化成字符串
        String str = new String(bytes, 0, len);
        System.out.println(str);
        //释放资源
        in.close();
    }
}

在这里插入图片描述
案例演示3
一次读取一个字节数组部分的数据
给定一个a.txt,文本文件里内容是“Hello World”

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        //输入流所关联的文件,如果不存在,就报错了
        FileInputStream in = new FileInputStream(file);
        byte[] bytes = new byte[1024];//缓冲区思想:创建一个字节数组充当容器
        int len = in.read(bytes,0,5);//一次从文件中读取一部分字节,放到容器中,从0开始读取5个
        System.out.println(len);
        String str = new String(bytes, 0, len);
        System.out.println(str);
        //释放资源
        in.close();
    }
}

在这里插入图片描述

(四)用输入输出流复制文件

学会了输入输出流,我们就可以实现文件的复制了。

方式1:一次读写一个字节

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("MyTest.java");
        //文件字节输入流 关联源文件(要复制/读取的文件)
        FileInputStream in = new FileInputStream(file);
        //文件字节输出流 关联目标文件(要写出的文件)
        FileOutputStream out = new FileOutputStream("D:/MyTest.java");//将文件复制到D盘
        //定义data用来接收读取的数据
        int data=0;
        //循环读写,边读边写(读一个字节,写一个字节)
        while((data=in.read())!=-1){//如果读取的内容是存在的,不为-1
            out.write(data);
            out.flush();//刷新,可刷可不刷
        }
        //释放资源
        in.close();
        out.close();
    }
}

这样的方式也行得通,但是复制的效率低,速度慢,如果是大文件,则更明显。因此我们可以一次性多复制一些字节。

方式2:一次读写一个字节数组

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("MyTest.java");
        //文件字节输入流 关联源文件(要复制/读取的文件)
        FileInputStream in = new FileInputStream(file);
        //文件字节输出流 关联目标文件(要写出的文件)
        FileOutputStream out = new FileOutputStream("D:/MyTest.java");//将文件复制到D盘
        byte[] bytes = new byte[1024 * 8];//缓冲区的思想:创建一个容器
        //定义一个变量,记录每次读取到的有效字节个数
        int len=0;
        //循环读写
        while((len=in.read(bytes))!=-1){//len
            out.write(bytes,0,len);
            out.flush();//刷新,可刷可不刷
        }
        //释放资源
        in.close();
        out.close();
    }
}

方式3:利用字节缓冲区输入输出流,一次读写一个字节

Java也利用缓冲区的思想,提供了一对字节缓冲区输入输出流:BufferedOutputStreamBufferedInputStream

  • 构造方法
    BufferedInputStream(InputStream in):创建一个缓冲输入流并保存其参数,即输入流 in,以便将来使用。
    BufferedInputStream(InputStream in, int size):创建具有指定缓冲区大小的缓冲输入流并保存其参数,即输入流 in,以便将来使用。
    BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    BufferedOutputStream(OutputStream out, int size):创建一个新的具有指定缓冲区大小的缓冲输出流,以将数据写入指定的底层输出流。
import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("MyTest.java"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/MyTest.java"));
        //定义data 接收读取的数据
        int data=0;
        while((data=bis.read())!=-1){
            bos.write(data);
            bos.flush();
        }
        bis.close();
        bos.close();
    }
}

我们点上面代码的BufferedInputStream构造方法进去看源码,会发现其实它是调用了自己的另一个构造。
在这里插入图片描述
我们再点this进去,会发现最后一行确实创建了一个字节数组缓冲区
在这里插入图片描述
我们点buf进去:
在这里插入图片描述
这个buf其实就是BufferedInputStream的字段,用来存储数据的内部缓冲区

方式4:利用字节缓冲区输入输出流,一次读写一个字节数组

import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("MyTest.java"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/MyTest.java"));
        byte[] bytes = new byte[1024 * 8];
        //定义len,用来记录读取到的有效字节个数
        int len=0;
        while((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
            bos.flush();
        }
        bis.close();
        bos.close();
    }
}

字节流四种方式复制MP3文件并测试效率

import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        copy1();
        copy2();
        copy3();
        copy4();
    }

    //一次读写一个字节
    private static void copy1() throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream in = new FileInputStream("D:/You Raise Me Up - Westlife.mp3");
        FileOutputStream out = new FileOutputStream("D:/QQ音乐/You Raise Me Up - Westlife.mp3");
        int data=0;
        while((data=in.read())!=-1){
            out.write(data);
            out.flush();
        }
        in.close();
        out.close();
        long end = System.currentTimeMillis();
        System.out.println("方式1耗时:" + (end - start) + "毫秒");
    }

    //一次读写一个字节数组
    private static void copy2() throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream in = new FileInputStream("D:/You Raise Me Up - Westlife.mp3");
        FileOutputStream out = new FileOutputStream("D:/QQ音乐/You Raise Me Up - Westlife.mp3");
        byte[] bytes = new byte[1024 * 8];
        int len=0;
        while((len=in.read(bytes))!=-1){
            out.write(bytes,0,len);
            out.flush();
        }
        in.close();
        out.close();
        long end = System.currentTimeMillis();
        System.out.println("方式2耗时:" + (end - start) + "毫秒");
    }

    //字节缓冲区输入输出流,一次读写一个字节
    private static void copy3() throws IOException {
        long start = System.currentTimeMillis();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/You Raise Me Up - Westlife.mp3"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/QQ音乐/You Raise Me Up - Westlife.mp3"));
        int data=0;
        while((data=bis.read())!=-1){
            bos.write(data);
            bos.flush();
        }
        bis.close();
        bos.close();
        long end = System.currentTimeMillis();
        System.out.println("方式3耗时:" + (end - start) + "毫秒");
    }

    //字节缓冲区输入输出流,一次读写一个字节数组
    private static void copy4() throws IOException {
        long start = System.currentTimeMillis();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/You Raise Me Up - Westlife.mp3"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/QQ音乐/You Raise Me Up - Westlife.mp3"));
        byte[] bytes = new byte[1024 * 8];
        int len=0;
        while((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
            bos.flush();
        }
        bis.close();
        bos.close();
        long end = System.currentTimeMillis();
        System.out.println("方式4耗时:" + (end - start) + "毫秒");
    }
}

在这里插入图片描述

流的异常处理

自己捕获异常

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

public class MyTest1 {
    public static void main(String[] args) {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream("MyTest.java");
            out = new FileOutputStream("D:/MyTest.java");
            int data=0;
            while((data=in.read())!=-1){
                out.write(data);
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in!=null){
                    in.close();
                }
                if(out!=null){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

四、字符流

(一)字符流出现的原因及编码表概述和常见编码表

字符流出现的原因:由于字节流操作中文不是特别方便,所以,java就提供了字符流。

通俗的讲,字符流 = 字节流 + 字符集,底层还是拿字节流来实现的

  • 什么是字符集呢?
    字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、GBK字符集、Unicode字符集等(UTF-8是Unicode的其中一个使用方式)。计算机要准确的处理各种字符集文字,就需要进行字符编码,以便计算机能够识别和存储各种文字。

  • 编码: 就是把字符串转换成字节数组
    public byte[] getBytes()使用平台的默认字符集将此 String编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
    public byte[] getBytes(String charsetName) 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

  • 解码: 把字节数组转换成字符串
    public String(byte[] bytes)通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
    public String(byte[] bytes, String charsetName) 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。

  • 乱码:编解码采用的字符集不一致,就会导致乱码

案例演示
编码和解码的字符集不一致会出现乱码

import java.io.UnsupportedEncodingException;

public class MyTest1{
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = "你好中国";
        //编码
        //public byte[] getBytes()
        //使用平台的默认字符集将此 String编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
        //public byte[] getBytes(String charsetName)
        //使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
        byte[] bytes = str.getBytes("UTF-8");
        //解码
        //public String(byte[] bytes)
        //通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
        //public String(byte[] bytes, String charsetName)
        //通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String。
        String s = new String(bytes, "GBK");
        System.out.println(s);
    }
}

在这里插入图片描述

(二)字符流继承关系

下面是几个常用的字符流的继承关系
在这里插入图片描述

(三)OutputStreamWriter

OutputStreamWriter 是字符流通向字节流的桥梁,可使用指定的字符集将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

1.构造方法

OutputStreamWriter(OutputStream out):根据默认字符集编码把字节流的数据转换为字符流
OutputStreamWriter(OutputStream out,String charsetName):根据指定字符集编码把字节流数据转换为字符流

2.五个write()方法

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

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        //输出流所关联的文件,如果不存在,会自动帮你创建
        OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("a.txt"));

        //public void write(int c)一次写入一个字符
        out.write(100);
        out.write('b');
        out.write("\r\n");//换行

        //public void write(char[] cbuf)一次写一个字符数组
        out.write(new char[]{'你','好','漂','亮'});
        out.write("\r\n");//换行

        //public void write(char[] cbuf,int off,int len)一次写一个字符数组的一部分
        out.write(new char[]{'今','天','天','气','真','好'},2,4);
        out.write("\r\n");//换行

        //public void write(String str)一次写一个字符串
        out.write("Hello World!");
        out.write("\r\n");//换行

        //public void write(String str,int off,int len)一次写一个字符串的一部分
        out.write("今天天气真好",2,4);
        out.write("\r\n");//换行

        //字符流记得一定要刷新一下
        out.flush();
        out.close();
    }
}

在这里插入图片描述

案例演示2

第二种构造方法

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        //根据指定字符集编码把字节流数据转换为字符流
        OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("a.txt"),"UTF-8");
        out.write("Hello World!");
		out.write("\r\n");
		
        //字符流记得一定要刷新一下
        out.flush();
        out.close();
    }
}

案例演示3
如果我们重复运行程序想在文本文件中重复追加一段话怎么办呢?

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        //根据指定字符集编码把字节流数据转换为字符流
        OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("a.txt",true));
        out.write("Hello World!");
        out.write("\r\n");

        //字符流记得一定要刷新一下
        out.flush();
        out.close();
    }
}

连续运行五次:
在这里插入图片描述

(四)InputStreamReader

InputStreamReader 是字节流通向字符流的桥梁,它使用指定的字符集读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

1.构造方法

InputStreamReader(InputStream is):用默认的字符集读取数据
InputStreamReader(InputStream is,String charsetName):用指定的字符集读取数据

2.三个read()方法

public int read() 一次读取一个字符
public int read(char[] cbuf) 一次读取一个字符数组的字符,放进字符数组中
public int read(char[] cbuf,int off,int len)一次读取一部分字符,放进字符数组中

案例演示1
已知存在c.txt,读取其中数据
在这里插入图片描述

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        //输入流,所关联的文件如果不存在就会报错
        InputStreamReader in = new InputStreamReader(new FileInputStream("c.txt"));
        //一次读取一个字符
        // 如果读取不到有效字符,返回-1。我们经常通过是否为-1,判断这个文件是否读完
        int data = in.read();
        System.out.println(data);
        data = in.read(); //一次读取一个字符
        System.out.println(data);
        data = in.read(); //一次读取一个字符
        System.out.println(data);
        data = in.read(); //一次读取一个字符
        System.out.println(data);
        data = in.read(); //一次读取一个字符
        System.out.println(data);
        data = in.read(); //一次读取一个字符
        System.out.println(data);
        data = in.read(); //一次读取一个字符
        System.out.println(data);
        
        in.close();
    }
}

在这里插入图片描述
案例演示2
已知存在c.txt,读取其中数据
在这里插入图片描述

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        InputStreamReader in = new InputStreamReader(new FileInputStream("c.txt"));
        //定义一个容器
        char[] chars = new char[100];
        int len = in.read(chars); //一次读取一些字符,放到字符数组中,返回的是实际读取到的有效字符个数
        System.out.println(len);
        //由于字符数组可能并未装满,遍历字符数组后面会出现很多0
        //因此采用字符数组转换为字符串的方法输出
        System.out.println(new String(chars,0,len));
        System.out.println(String.valueOf(chars,0,len));

        in.close();
    }
}

在这里插入图片描述

读取前17个字符

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

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        InputStreamReader in = new InputStreamReader(new FileInputStream("c.txt"));
        //定义一个容器
        char[] chars = new char[100];

        int len = in.read(chars, 0, 17);//从0开始读,读取17个字符放到容器中
        System.out.println(len);
        //由于字符数组可能并未装满,遍历字符数组后面会出现很多0
        //因此采用字符数组转换为字符串的方法输出
        System.out.println(new String(chars,0,len));
        System.out.println(String.valueOf(chars,0,len));

        in.close();
    }
}

在这里插入图片描述

(五)用字符输入输出流复制文本文件

方式1:一次读写一个字符

import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        InputStreamReader in = new InputStreamReader(new FileInputStream("MyTest.java"));
        OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("D:/MyTest.java"));
        //定义一个变量,记录每次读取到的字符
        int data=0;
        while((data=in.read())!=-1){
            out.write(data);
            out.flush();
        }
        in.close();
        out.close();
    }
}

方式2:一次读写一个字符数组

import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        InputStreamReader in = new InputStreamReader(new FileInputStream("MyTest.java"));
        OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("D:/MyTest.java"));
        //定义一个字符缓冲区
        char[] chars=new char[2000];
        //定义一个变量,记录每次读取到的有效字符个数
        int len=0;
        while((len=in.read(chars))!=-1){
            out.write(chars,0,len);
            out.flush();
        }
        in.close();
        out.close();
    }
}

流的异常处理

import java.io.*;

public class MyTest1 {
    public static void main(String[] args){
        InputStreamReader in = null;
        OutputStreamWriter out = null;
        try {
            in = new InputStreamReader(new FileInputStream("MyTest.java"));
            out = new OutputStreamWriter(new FileOutputStream("D:/MyTest.java"));
            char[] chars=new char[2000];
            int len=0;
            while((len=in.read(chars))!=-1){
                out.write(chars,0,len);
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(in!=null){
                    in.close();
                }
                if(out!=null){
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

(六)FileReader和FileWriter

转换流InputStreamWriter和OutputStreamWriter的名字比较长,而我们常见的操作都是按照本地默认字符集实现的,所以,为了简化我们的书写,转换流提供了对应的子类:FileWriterFileReader

父类(转换流)子类(便捷类)
OutputStreamWriterFileWriter
InputStreamReaderFileReader

1.与父类的区别

FileReader和FileWriter基本上与父类的用法一致,不同体现在FileReader和FileWriter不能指定字符集,构造方法有些不同,其余都一样。

2.构造方法

构造方法的参数不再需要一个流

  • FileReader(File file)在给定从中读取数据的 File 的情况下创建一个新 FileReader。
    FileReader(String fileName)在给定从中读取数据的文件名的情况下创建一个新 FileReader。

  • FileWriter(File file)根据给定的 File 对象构造一个 FileWriter 对象。
    FileWriter(File file, boolean append)根据给定的 File 对象构造一个 FileWriter 对象。
    FileWriter(String fileName)根据给定的文件名构造一个 FileWriter 对象。
    FileWriter(String fileName, boolean append)根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。

案例演示
用FileWriter和FileReader实现文本文件的复制

import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        FileReader reader = new FileReader("MyTest.java");
        FileWriter writer = new FileWriter("MyTest2.java");
        char[] chars = new char[1000];
        int len=0;
        while ((len=reader.read(chars))!=-1){
            writer.write(chars,0,len);
            writer.flush();
        }
        reader.close();
        writer.close();
    }
}

(七)字符缓冲流

是一种高效的字符输入输出流:BufferedWriterBufferedReader

1.构造方法

  • public BufferedWriter(Writer w)
  • public BufferedReader(Reader e)

2.字符缓冲流的特殊方法

  • BufferedWriter:
    public void newLine():写换行符,换行符具有系统兼容性(比"\r\n"好)

  • BufferedReader:
    public String readLine():一次读取一行数据,是以换行符为标记的,读到换行符就换行,没读到数据返回null(读取到的数据包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null)

案例演示
用特殊方法实现文本文件的复制

import java.io.*;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        BufferedReader bfr = new BufferedReader(new FileReader("a.txt"));
        BufferedWriter bfw = new BufferedWriter(new FileWriter("b.txt"));
        String s=null;
        //一次读写一行
        while((s=bfr.readLine())!=null){
            bfw.write(s);
            bfw.newLine();
            bfw.flush();
        }
        bfr.close();
        bfw.close();
    }
}

思考题1:把集合中的数据存储到文本文件
需求:把如下ArrayList集合中的字符串数据存储到文本文件
在这里插入图片描述
思路:

  1. 创建一个ArrayList集合
  2. 添加元素
  3. 创建一个高效的字符输出流对象
  4. 遍历集合,获取每一个元素,把这个元素通过高效的字符输出流写到文本文件中
  5. 释放资源
import java.io.*;
import java.util.ArrayList;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        list.add("小明");
        list.add("小红");
        list.add("小刚");
        list.add("小张");

        BufferedWriter bfw = new BufferedWriter(new FileWriter("b.txt"));
        //遍历集合,取出数据写入文本文件
        for (String s : list) {
            bfw.write(s);
            bfw.newLine();
            bfw.flush();
        }
        bfw.close();
    }
}

思考题2:把文本文件中的数据存储到集合中
从文本文件中读取数据(每一行为一个字符串数据)到集合中,并遍历集合

思路:

  1. 创建高效的字符输入流对象
  2. 创建一个集合对象
  3. 读取数据(一次读取一行)
  4. 把读取到的数据添加到集合中
  5. 释放资源
  6. 遍历集合
import java.io.*;
import java.util.ArrayList;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        ArrayList<String> list = new ArrayList<>();
        BufferedReader bfr = new BufferedReader(new FileReader("b.txt"));
        String s=null;
        while((s=bfr.readLine())!=null){
            list.add(s);
        }
        bfr.close();
        System.out.println(list);
    }
}

在这里插入图片描述

思考题3:随机获取文本文件中的姓名
需求:(点名器)我有一个文本文件,每一行是一个学生的名字,请写一个程序,每次允许随机获取一个学生名称

思路:

  1. 创建一个高效的字符输入流对象
  2. 创建集合对象
  3. 读取数据,把数据存储到集合中
  4. 产生一个随机数,这个随机数的范围是 0 - 集合的长度,作为集合的随机索引
  5. 根据索引获取指定的元素
  6. 释放资源
  7. 输出
import java.io.*;
import java.util.ArrayList;
import java.util.Random;

public class MyTest1 {
    public static void main(String[] args) throws IOException {
        ArrayList<String> list = new ArrayList<>();
        BufferedReader bfr = new BufferedReader(new FileReader("b.txt"));
        String s=null;
        while((s=bfr.readLine())!=null){
            list.add(s);
        }
        bfr.close();

        Random random = new Random();
        //生成一个随机索引
        int i = random.nextInt(list.size());
        System.out.println(list.get(i));
    }
}

此题体现了一种思想:程序跟数据进行了解耦
以后班级有新的学生,只需要修改配置文件(文本文件)即可,程序不需要改动。

思考题4:复制单级文件夹
我们一直处理的复制都是文件复制,我们知道文件夹不能直接通过流进行复制,那么如何复制一个包含许多文件的单级文件夹呢?

import java.io.*;

public class MyTest1{
    public static void main(String[] args) throws IOException {
        //封装源文件夹
        File sourceFolder = new File("D:/test");
        //封装目标文件夹
        File targetFolder = new File("D:/test123");
        //文件夹不能复制,因此应该先创建目标文件夹
        if(!targetFolder.exists()){
            targetFolder.mkdirs();
        }
        copyFolder(sourceFolder,targetFolder);
        System.out.println("复制成功!");
    }
    public static void copyFolder(File sourceFolder,File targetFolder) throws IOException {
        //遍历源文件夹下所有的文件,复制到目标文件夹下去
        File[] files = sourceFolder.listFiles();
        for (File f : files) {
            copyFile(f,targetFolder);
        }
    }

    private static void copyFile(File f, File targetFolder) throws IOException {
        //使用字节流来复制
        FileInputStream in = new FileInputStream(f);
        FileOutputStream out = new FileOutputStream(new File(targetFolder,f.getName()));
        byte[] bytes=new byte[1024*8];
        int len=0;
        while((len=in.read(bytes))!=-1){
            out.write(bytes,0,len);
            out.flush();
        }
        in.close();
        out.close();
    }
}

思考题5:复制多级文件夹
只需要在复制单级文件夹的基础上加一个递归

import java.io.*;

public class MyTest1{
    public static void main(String[] args) throws IOException {
        //封装源文件夹
        File sourceFolder = new File("D:/test");
        //封装目标文件夹
        File targetFolder = new File("D:/test123");
        //文件夹不能复制,因此应该先创建目标文件夹
        if(!targetFolder.exists()){
            targetFolder.mkdirs();
        }
        copyFolder(sourceFolder,targetFolder);
        System.out.println("复制成功!");
    }
    public static void copyFolder(File sourceFolder,File targetFolder) throws IOException {
        //遍历源文件夹下所有的文件,复制到目标文件夹下去
        File[] files = sourceFolder.listFiles();
        for (File f : files) {
            if(f.isFile()){//判断是否为文件
                copyFile(f,targetFolder);
            }else{//如果为文件夹(递归),那么f是源文件夹
                //封装目标文件夹
                File tf = new File(targetFolder, f.getName());
                //创建目标文件夹
                if(!tf.exists()){
                    tf.mkdirs();
                }
                //递归
                copyFolder(f,tf);
            }
        }
    }

    private static void copyFile(File f, File targetFolder) throws IOException {
        //使用字节流来复制
        FileInputStream in = new FileInputStream(f);
        FileOutputStream out = new FileOutputStream(new File(targetFolder,f.getName()));
        byte[] bytes=new byte[1024*8];
        int len=0;
        while((len=in.read(bytes))!=-1){
            out.write(bytes,0,len);
            out.flush();
        }
        in.close();
        out.close();
    }
}

思考题6:键盘录入学生信息按照总分排序并写入文本文件
需求:键盘录入3个学生信息(姓名,语文成绩(chineseScore),数学成绩(mathScore),英语成绩(englishScore)),按照总分从高到低存入文本文件

思路:

  1. 创建一个学生类: 姓名、语文成绩(chineseScore)、数学成绩(mathScore)、英语成绩(englishScore)
  2. 因为要排序,所以需要选择TreeSet进行存储学生对象
  3. 键盘录入学生信息,把学生信息封装成一个学生对象,再把学生对象添加到集合中
  4. 创建一个高效的字符输出流对象
  5. 遍历集合,把学生的信息写入到指定的文本文件中
  6. 释放资源
import java.io.*;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;

class Student{
    public String name;
    private int chineseScore;
    private int mathScore;
    private int englishScore;

    public Student() {
    }

    public Student(String name, int chineseScore, int mathScore, int englishScore){
        this.name=name;
        this.chineseScore=chineseScore;
        this.mathScore=mathScore;
        this.englishScore=englishScore;
    }

    public int getChineseScore() {
        return chineseScore;
    }

    public int getMathScore() {
        return mathScore;
    }

    public int getEnglishScore() {
        return englishScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", chineseScore=" + chineseScore +
                ", mathScore=" + mathScore +
                ", englishScore=" + englishScore +
                '}';
    }
}
public class MyTest {
    public static void main(String[] args) throws IOException {
        TreeSet<Student> students = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num1 = (o2.getChineseScore() + o2.getMathScore() + o2.getEnglishScore())
                        - (o1.getChineseScore() + o1.getMathScore() + o1.getEnglishScore());
                int num2=num1==0?o1.name.compareTo(o2.name):num1;
                return num2;
            }
        });
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println("请输入学生的名字:");
            String name = sc.next();
            System.out.println("请输入语文成绩:");
            int chineseScore = sc.nextInt();
            System.out.println("请输入数学成绩:");
            int mathScore = sc.nextInt();
            System.out.println("请输入英语成绩:");
            int englishScore = sc.nextInt();
            Student student = new Student(name, chineseScore, mathScore, englishScore);
            students.add(student);
            System.out.println("如果要退出,请输入0,继续则输入1");
            if(sc.nextInt()==0){
                break;
            }
        }
        BufferedWriter bfw = new BufferedWriter(new FileWriter("student.txt"));
        for (Student student : students) {
            bfw.write(student.toString());
            bfw.newLine();
            bfw.flush();
        }
        bfw.close();
    }
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值