IO流(二)之字符流、缓冲流、转换流、打印流,数据流,序列化流,File和IO工具包

通过名字就很高区分,前面的单词代表着功能,后面的代表着类别

一、字符流

字节流:适合复制文件等,不适合读写文本文件

字符流:适合读写文本文件内容

具体的原因我们在前面的内容中也看到了,只有一次性读完才不会出现乱码的情况。因为不管怎样,都有很大的概率,后面的字节不是一个完整的字符。

//        demo.txt 中存入的是, a你好
        FileInputStream inputStream = new FileInputStream("demo.txt");
        byte[] bytes1 = new byte[3];
        int i;
        while ((i = inputStream.read(bytes1)) != -1) {
            System.out.println(new String(bytes1,0,i));
        }
        inputStream.close();

在不改编码的前提下,默认使用的是UTF-8编码,我们知道UTF-8编码中,一个字母是一个字节,一个汉字是三个字节,那么三个三个读,那么这样分来必然会乱码。控制台如下: 所以也就有了字符流,按照字符去操作数据。


体系结构如上:

1,FileReader类<文件字符输入流>

作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。

学习构造器和方法:

构造器说明
public FileReader (File file)创建字符输入流管道与源文件接通
public FileReader (String pathname)创建字符输入流管道与源文件接通
方法名称说明
public int read()每次读取一个字符返回,如果发现没有数据可读会返回-1.
public int read(char[] buffer)每次用一个字符数组去读取数据,返回字符数组读取了多少个字符,如果发现没有数据可读会返回-1.
2,FileWriter类<文件字符输出流>

作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。

构造器说明
public FileWriter(File file)创建字节输出流管道与源文件对象接通
public FileWriter(String filepath)创建字节输出流管道与源文件路径接通
public FileWriter(File file,boolean append)创建字节输出流管道与源文件对象接通,可追加数据
public FileWriter(String filepath,boolean append)创建字节输出流管道与源文件路径接通,可追加数据
方法名称说明
void write(int c)写一个字符
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分
方法名称说明
public void flush() throws IOException刷新流,就是将内存中缓存的数据立即写到文件中去生效!
public void close() throws IOException关闭流的操作,包含了刷新!
3,FileWriter的注意事项

字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效,如果不刷,内容会在内存中不会进入到指定的文件中。

另外关闭流,之后不能再对流进行操作。否则会出异常

比如:下面的代码只调用了写数据的方法,没有关流的方法。当你打开目标文件时,是看不到任何数据的。

//1.创建FileWriter对象
Writer fw = new FileWriter("io-app2/src/test03out.txt");

//2.写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');

而下面的代码,加上了flush()方法之后,数据就会立即到目标文件中去。

//1.创建FileWriter对象
Writer fw = new FileWriter("io-app2/src/test03out.txt");

//2.写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');

//3.刷新
fw.flush(); 

下面的代码,调用了close()方法,数据也会立即到文件中去。因为close()方法在关闭流之前,会将内存中缓存的数据先刷新到文件,再关流。

//1.创建FileWriter对象
Writer fw = new FileWriter("io-app2/src/test03out.txt");

//2.写字符数据出去
fw.write('a');
fw.write('b');
fw.write('c');

//3.关闭流
fw.close(); //会先刷新,再关流
字节流和字符流的使用场景小结:

字节流是和做一切文件数据的拷贝(图片,音频,视频,文本)可以操作文本,但是不建议,容易在末尾出现乱码
字符流适合做文本文件的操作(读,写)

二,缓冲流

缓冲流的作用:可以对原始流进行包装,提高原始流读写数据的性能。

在这里插入图片描述

1,字节缓冲流<基本不用,后面详解>

字节缓冲流是如何提高读写数据的性能的,原理如下图所示。是因为在缓冲流的底层自己封装了一个长度为8KB(8129byte)的字节数组,但是缓冲流不能单独使用,它需要依赖于原始流。

读数据时: 它先用原始字节输入流一次性读取8KB的数据存入缓冲流内部的数组中(ps: 先一次多囤点货),再从8KB的字节数组中读取一个字节或者多个字节(把消耗屯的货)。

在这里插入图片描述

写数据时: 它是先把数据写到缓冲流内部的8BK的数组中(ps: 先攒一车货),等数组存满了,再通过原始的字节输出流,一次性写到目标文件中去(把囤好的货,一次性运走)。

在创建缓冲字节流对象时,需要封装一个原始流对象进来。构造方法如下:

构造器说明
public BufferedInputStream(InputStream is)把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能
public BufferedOutputStream(OutputStream os)把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能
public static void main(String[] args) {
        try (
                InputStream is = new FileInputStream("io-app2/src/test01.txt");
                // 1、定义一个字节缓冲输入流包装原始的字节输入流
                InputStream bis = new BufferedInputStream(is);

                OutputStream os = new FileOutputStream("io-app2/src/test01_bak.txt");
                // 2、定义一个字节缓冲输出流包装原始的字节输出流
                OutputStream bos = new BufferedOutputStream(os);
        ){

            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1){
                bos.write(buffer, 0, len);
            }
            System.out.println("复制完成!!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
2,字符缓冲流
1,BufferedReader字符缓冲输入流

作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能

构造器说明
public BufferedReader(Reader r)把低级的字符输入流包装成字符缓冲输入流管道,从而提高字符输入流读字符数据的性能

下面这个方法是是BufferReader特有的方法。

方法说明
public String readLine()读取一行数据返回,如果没有数据可读了,会返回null
public static void main(String[] args)  {
      try (
                Reader fr = new FileReader("io-app2\\src\\test04.txt");
                // 创建一个字符缓冲输入流包装原始的字符输入流
                BufferedReader br = new BufferedReader(fr);
        ){
            String line; // 记住每次读取的一行数据
            while ((line = br.readLine()) != null){
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
2,BufferedWriter(字符缓冲输出流)

作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能。

构造器说明
public BufferedWriter(Writer r)把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能

字符缓冲输出流新增的功能:换行,这个Line会根据系统的不同生成不同的换行,提高了代码的兼容性,比如在Windows中写数据是的换行符是“/r/n”,而在mac中是“\n”

方法说明
public void newLine()换行
    public static void main(String[] args) {
        try (
                Writer fw = new FileWriter("io-app2/src/test05out.txt", true);
                // 创建一个字符缓冲输出流管道包装原始的字符输出流
                BufferedWriter bw = new BufferedWriter(fw);
        ){

            bw.write('a');
            bw.write(97);
            bw.write('磊');
            bw.newLine();

            bw.write("我爱你中国abc");
            bw.newLine();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
3,缓冲流性能的分析

可能会有这么一个疑问,它和我们使用原始流,自己加一个8BK数组不是一样的吗? 缓冲流就一定能提高性能吗?先说答案,缓冲流不一定能提高性能

下面我们用一个比较大文件(889MB)复制,做性能测试,分别使用下面四种方式来完成文件复制,并记录文件复制的时间。

① 使用低级流一个字节一个字节的复制

② 使用低级流按照字节数组的形式复制

③ 使用缓冲流一个字节一个字节的复制

④ 使用缓冲流按照字节数组的形式复制

低级流一个字节复制: 巨。。。。。。。。。。。。。。。。。。。。。慢
低级流按照字节数组复制(数组长度1024): 12.117s
缓冲流一个字节复制: 11.058s
缓冲流按照字节数组复制(数组长度1024): 2.163s
【注意:这里的测试只能做一个参考,和电脑性能也有直接关系】

经过上面的测试,我们可以得出一个结论:默认情况下,采用一次复制1024个字节,缓冲流完胜。

但是,缓冲流就一定性能高吗?我们采用一次复制8192个字节试试

低级流按照字节数组复制(数组长度8192): 2.535s
缓冲流按照字节数组复制(数组长度8192): 2.088s

经过上面的测试,我们可以得出一个结论:**一次读取8192个字节时,低级流和缓冲流性能相当。**相差的那几毫秒可以忽略不计。

继续把数组变大,看一看缓冲流就一定性能高吗?现在采用一次读取1024*32个字节数据试试

低级流按照字节数组复制(数组长度8192): 1.128s
缓冲流按照字节数组复制(数组长度8192): 1.133s

经过上面的测试,我们可以得出一个结论:**数组越大性能越高,低级流和缓冲流性能相当。**相差的那几秒可以忽略不计。

继续把数组变大,看一看缓冲流就一定性能高吗?现在采用一次读取1024*6个字节数据试试

低级流按照字节数组复制(数组长度8192): 1.039s
缓冲流按照字节数组复制(数组长度8192): 1.151s

此时你会发现,当数组大到一定程度,性能已经提高了多少了,甚至缓冲流的性能还没有低级流高。

最终总结一下: 缓冲流的性能不一定比低级流高,其实低级流自己加一个数组,性能其实是不差。 只不过缓冲流帮你加了一个相对而言大小比较合理的数组 。

三,转换流<了解>

FileReader默认只能读取UTF-8编码格式的文件。如果使用FileReader读取GBK格式的文件,可能存在乱码,因为FileReader它遇到汉字默认是按照3个字节来读取的,而GBK格式的文件一个汉字是占2个字节,这样就会导致乱码。

Java给我们提供了另外两种流InputStreamReader,OutputStreamWriter,这两个流我们把它叫做转换流。它们可以将字节流转换为字符流,并且可以指定编码方案。

先看一下JDK11之后的构造方法:可以通过参数直接指定编码,所以转换流只做了解

1,InputStreamReader

需求:我们可以先准备一个GBK格式的文件,然后使用下面新方式的代码进行读取,看是是否有乱码。

 public static void main(String[] args) {
        try (
                // 1、得到文件的原始字节流(GBK的字节流形式)
                InputStream is = new FileInputStream("io-app2/src/test06.txt");
                // 2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
                Reader isr = new InputStreamReader(is, "GBK");
                // 3、把字符输入流包装成缓冲字符输入流
                BufferedReader br = new BufferedReader(isr);
                ){
            String line;
            while ((line = br.readLine()) != null){
                System.out.println(line);
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
2,OutputStreamWrite

OutputStreamReader也是不能单独使用的,它内部需要封装一个OutputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。

需求:我们可以先准备一个GBK格式的文件,使用下面代码往文件中写字符数据。

 public static void main(String[] args) {
        // 指定写出去的字符编码。
        try (
                // 1、创建一个文件字节输出流
                OutputStream os = new FileOutputStream("io-app2/src/test07out.txt");
                // 2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流。
                Writer osw = new OutputStreamWriter(os, "GBK");
                // 3、把字符输出流包装成缓冲字符输出流
                BufferedWriter bw = new BufferedWriter(osw);
                ){
            bw.write("我是中国人abc");
            bw.write("我爱你中国123");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

四,打印流

在学习打印流之前先知道一句话,所见即所得

1,打印流基本使用

打印流,这里所说的打印其实就是写数据的意思,它和普通的write方法写数据还不太一样,一般会使用打印流特有的方法叫print(数据)或者println(数据),它打印啥就输出啥。

打印流有两个,一个是字节打印流PrintStream,一个是字符打印流PrintWriter,如下图所示

在这里插入图片描述

PrintStream和PrintWriter的用法是一样的,下面就一块演示了。

    public static void main(String[] args) {
        try (
                // 1、创建一个打印流管道
//                PrintStream ps =
//                        new PrintStream("io-app2/src/test08.txt", Charset.forName("GBK"));
//                PrintStream ps =
//                        new PrintStream("io-app2/src/test08.txt");
                PrintWriter ps =
                        new PrintWriter(new FileOutputStream("io-app2/src/test08.txt", true));
                ){
                ps.print(97);	//文件中显示的就是:97
                ps.print('a'); //文件中显示的就是:a
                ps.println("我爱你中国abc");	//文件中显示的就是:我爱你中国abc
                ps.println(true);//文件中显示的就是:true
                ps.println(99.5);//文件中显示的就是99.5

                ps.write(97); //文件中显示a,发现和前面println方法的区别了吗?

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
2,重定向输出语句

我们之前总是使用的System.out.println(),一直使用,但是至于为什么能够输出,其实我们一直不清楚。

因为System里面有一个静态变量叫out,out的数据类型就是PrintStream,它就是一个打印流,而且这个打印流的默认输出目的地是控制台,所以我们调用System.out.pirnln()就可以往控制台打印输出任意类型的数据,而且打印啥就输出啥。

而且System还提供了一个方法,可以修改底层的打印流,这样我们就可以重定向打印语句的输出目的地了。我们玩一下, 直接上代码。

public static void main(String[] args) {
        System.out.println("老骥伏枥");
        System.out.println("志在千里");

        try ( PrintStream ps = new PrintStream("io-app2/src/test09.txt"); ){
            // 把系统默认的打印流对象改成自己设置的打印流
            System.setOut(ps);

            System.out.println("烈士暮年");	
            System.out.println("壮心不已");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

此时打印语句,将往文件中打印数据,而不在控制台。

五,数据流

在开发中也会有这样的一种情况,我们想把数据和数据的类型一并写到文件中去,读取的时候也将数据和数据类型一并读出来。这个时候就可以使用数据流。数据流有两个DataInputStream和DataOutputStream.

在这里插入图片描述

1,DataOutputStream

DataOutputStream类,它也是一种包装流,创建DataOutputStream对象时,底层需要依赖于一个原始的OutputStream流对象。然后调用它的wirteXxx方法,写的是特定类型的数据。

构造器说明
public DataOutputStream (OutputStream out)创建新数据输出流包装基础的字节输出流
方法说明
public final void writeByte(int v) throws IOException将byte类型的数据写入基础的字节输出流
public final void writeInt (int v) throws IOException将int类型的数据写入基础的字节输出流
public final void writeDouble (Double v) throws IOException将double类型的数据写入基础的字节输出流
public final void writeUTF (String str) throw IOException将字符串数据以UTF-8编码成字节写入基础的字节输出流
public void write(int/byte[]/byte[]一部分)支持写字节数据出去

代码如下:往文件中写整数、小数、布尔类型数据、字符串数据

public class DataOutputStreamTest1 {
    public static void main(String[] args) {
        try (
                // 1、创建一个数据输出流包装低级的字节输出流
                DataOutputStream dos =
                        new DataOutputStream(new FileOutputStream("io-app2/src/test10out.txt"));
                ){
            dos.writeInt(97);
            dos.writeDouble(99.5);
            dos.writeBoolean(true);
            dos.writeUTF("尼古拉斯666!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2,DataInputStream

DataIntputStream类,它也是一种包装流,创建DataInputStream对象时,底层需要依赖于一个原始的InputStream流对象。然后调用它的readXxx()方法就可以读取特定类型的数据。

构造器说明
public DataInputStream ( InputStream is)创建新数据输入流包装基础的字节输入流
方法说明
Public final byte readByte() throws IOException读取字节数据返回
public final int readInt () throws IOException读取int类型的数据返回
public final double readDouble () throws IOException读取double类型的数据返回
public final String read UTF() throws IOException读取字符串数(UTF-8)据返回
public int readInt()/read(byte[])支持读字节数据进来
 public static void main(String[] args) {
        try (
                DataInputStream dis =
                        new DataInputStream(new FileInputStream("io-app2/src/test10out.txt"));
                ){
            int i = dis.readInt();
            System.out.println(i);

            double d = dis.readDouble();
            System.out.println(d);

            boolean b = dis.readBoolean();
            System.out.println(b);

            String rs = dis.readUTF();
            System.out.println(rs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

六,序列化流

那么序列化流是干什么用的呢? 我们知道字节流是以字节为单位来读写数据、字符流是按照字符为单位来读写数据、而对象流是以对象为单位来读写数据。也就是把对象当做一个整体,可以写一个对象到文件,也可以从文件中把对象读取出来。

首先要了解两个概念:
序列化:意思就是把对象写到文件或者网络中去。(简单记:写对象)
反序列化:意思就是把对象从文件或者网络中读取出来。(简单记:读对象)
1,ObjectOutputStream类

先学习ObjectOutputStream流,它也是一个包装流,不能单独使用,需要结合原始的字节输出流使用。

代码如下:将一个User对象写到文件中去

  • 第一步:先准备一个User类,必须让其实现Serializable接口
// 注意:对象如果需要序列化,必须实现序列化接口。
public class User implements Serializable {
    private String loginName;
    private String userName;
    private int age;
    // transient 这个成员变量将不参与序列化。
    private transient String passWord;

    public User() {
    }

    public User(String loginName, String userName, int age, String passWord) {
        this.loginName = loginName;
        this.userName = userName;
        this.age = age;
        this.passWord = passWord;
    }

    @Override
    public String toString() {
        return "User{" +
                "loginName='" + loginName + '\'' +
                ", userName='" + userName + '\'' +
                ", age=" + age +
                ", passWord='" + passWord + '\'' +
                '}';
    }
}
  • 第二步:再创建ObjectOutputStream流对象,调用writeObject方法对象到文件。
public class Test1ObjectOutputStream {
    public static void main(String[] args) {
        try (
                // 2、创建一个对象字节输出流包装原始的字节 输出流。
                ObjectOutputStream oos =
                        new ObjectOutputStream(new FileOutputStream("io-app2/src/test11out.txt"));
                ){
            // 1、创建一个Java对象。
            User u = new User("admin", "张三", 32, "666888xyz");

            // 3、序列化对象到文件中去
            oos.writeObject(u);
            System.out.println("序列化对象成功!!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:写到文件中的对象,是不能用记事本打开看的。因为对象本身就不是文本数据,打开是乱码

只有通过反序列的方式才能读取,其中的内容。

2,ObjectInputStream类

上面已经把Student对象序列化了,下面就需要通过ObjectInputStream类把他反序列化出来。

public class Test2ObjectInputStream {
    public static void main(String[] args) {
        try (
            // 1、创建一个对象字节输入流管道,包装 低级的字节输入流与源文件接通
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-app2/src/test11out.txt"));
        ){
            User u = (User) ois.readObject();
            System.out.println(u);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

七,开源 IO工具包

Commons-io链接:

mvn坐标:

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

其中有两个需要关注的类,一个是FileUtils用来操作文件的,另一个是IoUtils,用来操作IO的,其中的方法都是静态方法,拿着这两个类名直接调用即可,具体的方法如下:

FileUtils类提供的部分方法展示说明
public static void copyFile(File srcFile, File destFile)复制文件。
public static void copyDirectory(File srcDir, File destDir)复制文件夹
public static void deleteDirectory(File directory)删除文件夹
public static String readFileToString(File file, String encoding)读数据
public static void writeStringToFile(File file, String data,
String charname, boolean append)
写数据
IOUtils类提供的部分方法展示说明
public static int copy(InputStream inputStream, OutputStream outputStream)复制文件。
public static int copy(Reader reader, Writer writer)复制文件。
public static void write(String data, OutputStream output, String charsetName)写数据

javaSE项目引入流程:

1.在模块的目录下,新建一个lib文件夹
2.把jar包复制粘贴到lib文件夹下
3.选择lib下的jar包,右键点击Add As Library,然后就可以用了。
public class CommonsIOTest1 {
    public static void main(String[] args) throws Exception {
        //1.复制文件
        FileUtils.copyFile(new File("io-app2\\src\\test01.txt"), new File("io-app2/src/a.txt"));
        
        //2.复制文件夹
        FileUtils.copyDirectory(new File("D:\\resource\\私人珍藏"), new File("D:\\resource\\私人珍藏3"));
        
        //3.删除文件夹
        FileUtils.deleteDirectory(new File("D:\\resource\\私人珍藏3"));

        // Java提供的原生的一行代码搞定很多事情
         Files.copy(Path.of("io-app2\\src\\test01.txt"), Path.of("io-app2\\src\\b.txt"));
        System.out.println(Files.readString(Path.of("io-app2\\src\\test01.txt")));
    }
}

直接调用很是方便。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yfs1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值