java——IO流基础

IO流

用于读写数据的数据流 (可以读写磁盘文件,或网络中的数据…)

IO流的四大分类:

  • 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到內存中去的流
  • 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流。
  • 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流。
  • 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。

IO流的体系:

20240222-041004-Bf.png

FileinputStream(文件字节输入流)

作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去。
20240222-041225-tI.png

FileOutputStream(文件字节输出流)

作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去。20240222-050152-3K.png

文件写入换行符:os.write("/r/n”.getBytes());

文件复制

20240222-143944-i8.png

代码实现:

package cn.kt.FileAndIO;

import java.io.*;

public class FileCopy {
    public static void main(String[] args) {
        InputStream is = null;
        OutputStream os = null;
        try {
            // 需求:复制照片。
            //1、创建一个字节输入流管道与源文件接通
            is = new FileInputStream("/Users/mac/Downloads/hzw.jpg");
            //2、创建一个字节输出流管道与目标文件接通。
            os = new FileOutputStream("/Users/mac/tao/hzw.jpg");
            //3、创建一个字节数组,负责转移字节数据。
            byte[] buffer = new byte[1024];// 1KB.
            //4、从字节输入流中读取宁节数据,写出去到宁节输出流中。读多少写出去多少。
            int len;    // 记住每次读取了多少个字节。
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

注意:字节流非常话合做一切文件的复制操作
任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!

资源释放

JDK 7开始提供了更简单的资源释放方案:try-with-resource
20240222-152716-Os.png

代码示例:

package cn.kt.FileAndIO;

import java.io.*;

/**
 * 描述: 复制照片。
 */
public class FileCopy {
    public static void main(String[] args) {
        try (
                //1、创建一个字节输入流管道与源文件接通
                InputStream is = new FileInputStream("/Users/mac/Downloads/hzw.jpg");
                //2、创建一个字节输出流管道与目标文件接通。
                OutputStream os = new FileOutputStream("/Users/mac/tao/hzw.jpg");
                // 注意:这里只能放置资源对象。(流对象)
                // 什么是资源呢?资源都是会实现AutoCloseable接口
        ) {

            //3、创建一个字节数组,负责转移字节数据。
            byte[] buffer = new byte[1024];// 1KB.
            //4、从字节输入流中读取宁节数据,写出去到宁节输出流中。读多少写出去多少。
            int len;    // 记住每次读取了多少个字节。
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码相对简洁很多

FileReader(文件字符输入流)

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

20240222-153237-sE.png

FileWriter(文件字符输出流)

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

字符输出流使用时的注意事项
字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
20240222-154945-pw.png

字节流、字符流的使用场景小结:
字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出。
字符流适合做文本文件的操作(读,写)。

缓冲流

20240222-160144-0G.png

字节缓冲流

提高字节流读写数据的性能
原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池。

20240222-155544-Bg.png
20240222-155840-nI.png

字符缓冲流

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

20240222-160545-Id.png

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

20240222-162236-0q.png

原始流、缓冲流的性能分析

测试代码:

package cn.kt.FileAndIO;

import java.io.*;

/**
 * Created by tao.
 * Date: 2024/2/22 16:44
 * 描述: 观察原始流和缓冲流的性能。
 */

public class BufferStreamTimeTest {
    // 复制的视频路径
    private static final String SRC_FILE = "D:\\resource\\线程池.avi";
    // 复制到哪个目的地
    private static final String DEST_FILE = "D:\\";

    public static void main(String[] args) {
        // copy01(); // 低级字节流一个一个字节的赋值,慢的简直让人无法忍受,直接淘汰!
        copy02();// 低级的字节流流按照一个一个字节数组的形式复制,速度较慢!
        // copy03(); // 缓冲流按照一个一个字节的形式复制,速度较慢,直接淘汰!
        copy04(); // 缓冲流按照一个一个字节数组的形式复制,速度极快,推荐使用!
    }

    private static void copy01() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                OutputStream os = new FileOutputStream(DEST_FILE + "1.avi");
        ) {

            int b;
            while ((b = is.read()) != -1) {
                os.write(b);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("低级字节流一个一个字节复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }

    private static void copy02() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                OutputStream os = new FileOutputStream(DEST_FILE + "2.avi");
        ) {
            byte[] buffer = new byte[1024 * 64];
            int len;
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("低级字节流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }

    private static void copy03() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                BufferedInputStream bis = new BufferedInputStream(is);
                OutputStream os = new FileOutputStream(DEST_FILE + "3.avi");
                BufferedOutputStream bos = new BufferedOutputStream(os);
        ) {

            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("缓冲流一个一个字节复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }

    private static void copy04() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                BufferedInputStream bis = new BufferedInputStream(is, 64 * 1024);
                OutputStream os = new FileOutputStream(DEST_FILE + "4.avi");
                BufferedOutputStream bos = new BufferedOutputStream(os, 64 * 1024);
        ) {
            byte[] buffer = new byte[1024 * 64]; // 32KB
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("缓冲流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }
}

测试一:

分别使用原始的字节流,以及字节缓冲流复制一个很大视频(889MB)。
测试步骤:

  1. 使用低级的字节流按照一个一个字节的形式复制文件。
  2. 使用低级的字节流按照字节数组的形式复制文件。
  3. 使用高级的缓冲字节流按照一个一个字节的形式复制文件。
  4. 使用高级的缓冲字节流按照字节数组的形式复制文件。

默认情况测试结果:

  1. 低级流一个字节复制: 慢得简直让人无法忍受
  2. 低级流按照字节数组复制(数组长度1024): 12.117s
  3. 缓冲流一个字节复制: 11.058s
  4. 缓冲流按照字节数组复制(数组长度1024): 2.163s

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

测试二:

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

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

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

测试三:

继续把数组变大,看一看缓冲流就一定性能高吗?现在采用一次读取1024*32个字节数据试试(缓冲流底层的字节数组也设成32k)

1. 低级流按照字节数组复制(数组长度32k): 1.128s
2. 缓冲流按照字节数组复制(数组长度32k): 1.133s

测试四:

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

1. 低级流按照字节数组复制(数组长度64k): 1.039s
2. 缓冲流按照字节数组复制(数组长度64k): 1.151s

此时你会发现,当数组大到一定程度,性能已经提高不了多少了,甚至缓冲流的性能还没有低级流高。
在实际开发中,想提升读写性能就扩大数组大小,大小取决于经验,并且缓冲流的性能不一定就比低级流好。

转换流

解决的问题:不同编码读取出现乱码的问题

  1. 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
  2. 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
    20240222-174932-ZD.png

InputStreamReader (字符输入转换流)

解决不同编码时,字符流读取文本内容乱码的问题。
解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了。

20240222-175337-6U.png
InputStreamReader也是不能单独使用的,它内部需要封装一个InputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。我们可以先准备一个GBK格式的文件,然后使用下面的代码进行读取是不会有乱码的。
代码示例:

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


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

OutputStreamWriter(字符输出转换流)

作用:可以控制写出去的字符使用什么字符集编码。
解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了。

20240222-180125-u2.png

OutputStreamReader也是不能单独使用的,它内部需要封装一个OutputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。我们可以先准备一个GBK格式的文件,使用下面代码往文件中写字符数据。
代码示例:

public class OutputStreamWriterTest2 {
    public static void main(String[] args) {
        // 指定写出去的字符编码。
        try (
                // 1、创建一个文件字节输出流
                OutputStream os = new FileOutputStream("io-app2/src/out.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();
        }
    }
}

打印流(PrintStream/PrintWriter)

20240222-180711-Y6.png

作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。

字节打印流

20240222-181147-0W.png

字符打印流

20240222-181749-DR.png

代码示例:

public class PrintTest1 {
    public static void main(String[] args) {
        try (
                // 1、创建一个打印流管道
//                PrintStream ps =
//                        new PrintStream("io-app2/src/itheima08.txt", Charset.forName("GBK"));
//                PrintStream ps =
//                        new PrintStream("io-app2/src/itheima08.txt");
                PrintWriter ps =
                        new PrintWriter(new FileOutputStream("io-app2/src/itheima08.txt", true));
                        //注意 高级流不支持追加参数 所以想要追加的方式来写 就得先包装一个低级流并声明是追加型写入
                ){
                ps.print(97);	//文件中显示的就是:97而不是a
                ps.print('a'); //文件中显示的就是:a
                ps.println("我爱你中国abc");	//文件中显示的就是:我爱你中国abc
                ps.println(true);//文件中显示的就是:true
                ps.println(99.5);//文件中显示的就是99.5

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

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

其实打印流我们一直在使用,只是没有感受到而已。打印流可以实现更加方便,更加高效的写数据的方式。
这里所说的打印其实就是写数据的意思,它和普通的write方法写数据还不太一样,一般会使用打印流特有的方法叫print(数据)或者println(数据),它的特点是打印啥就输出啥,并且内部底层自己封装了缓冲流,所以性能也不差。

PrintStream和Printwriter的区别

  1. 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
  2. PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
  3. PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。

重定向输出语句

System.out.println()这句话表示打印输出,但是至于为什么能够输出,其实我们一直不清楚。
其实是因为System里面有一个静态变量叫out,out的数据类型就是PrintStream,它就是一个打印流,而且这个打印流的默认输出目的地是控制台,所以我们调用System.out.pirnln()就可以往控制台打印输出任意类型的数据,而且打印啥就输出啥。

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

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

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

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

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

数据流

20240222-184547-3S.png

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

DataOutputStream(数据输出流)

DataOutputStream类,它也是一种包装流,创建DataOutputStream对象时,底层需要依赖于一个原始的OutputStream流对象。然后调用它的wirteXxx方法,写的是特定类型的数据。
20240222-185020-T4.png
示例代码如下:往文件中写整数、小数、布尔类型数据、字符串数据

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

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//运行完之后666前面显示的是乱码,但其实不是发生了编码错误,而是一种特定的数据存储方式
//因为本来就不是存给人看的,而是方便下次读取的

DatalnputStream(数据输入流)

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

示例代码如下:读取文件中特定类型的数据(整数、小数、字符串等)

public class DataInputStreamTest2 {
    public static void main(String[] args) {
        try (
                DataInputStream dis =
                        new DataInputStream(new FileInputStream("io-app2/src/10out.txt"));//文件是上面数据输出流输出的文件
                ){
            int i = dis.readInt();
            System.out.println(i);//97

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

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

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

注意输出的时候读取文件的数据类型要和写入文件的数据类型相同,否则会出问题。

序列化流

20240222-190021-7s.png

序列化流是干什么用的呢? 我们知道字节流是以字节为单位来读写数据、字符流是按照字符为单位来读写数据、而对象流是以对象为单位来读写数据。也就是把对象当做一个整体,可以写一个对象到文件,也可以从文件中把对象读取出来。这里有一个新词 序列化
序列化:意思就是把对象写到文件或者网络中去。(简单记:写对象)
反序列化:意思就是把对象从文件或者网络中读取出来。(简单记:读对象)

ObjectOutputStream(对象字节输出流)

ObjectOutputStream流也是一个包装流,不能单独使用,需要结合原始的字节输出流使用,可以把Java对象进行序列化:把Java对象存入到文件中去。

20240222-190353-Xy.png

代码如下:将一个User对象写到文件中去
第一步:先准备一个User类,必须让其实现Serializable接口。

// 注意:对象如果需要序列化,必须实现序列化接口。
public class User implements Serializable {
    private String loginName;
    private String userName;
    private int age;
    // transient 这个成员变量将不参与序列化,即某个对象被写到文件后再读取,这个字段就是null了。
    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 + '\'' +
                '}';
    }
}

注意:transient 这个成员变量将不参与序列化,即某个对象被写到文件后再读取,这个字段就是null了。

第二步:再创建ObjectOutputStream流对象,调用writeObject方法对象到文件。

public class Test1ObjectOutputStream {
    public static void main(String[] args) {
        try (
                // 2、创建一个对象字节输出流包装原始的字节 输出流。
                ObjectOutputStream oos =
                        new ObjectOutputStream(new FileOutputStream("io-app2/src/11out.txt"));
                ){
            // 1、创建一个Java对象。
            User u = new User("admin", "张三", 32, "666888xyz");

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

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

注意:写到文件中的对象,是不能用记事本打开看的。因为对象本身就不是文本数据,打开是乱码,这里必须用反序列化,自己写代码读。
20240222-190814-WV.png

ObjectinputStream(对象字节输入流)

ObjectInputStream流,它也是一个包装流,不能单独使用,需要结合原始的字节输入流使用,可以读任意的java对象。

20240222-190932-0h.png

接着前面的案例,文件中已经有一个User对象,现在要使用ObjectInputStream读取出来。称之为反序列化。

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

如果要一次序列化多个对象,怎么做?

用一个ArrayList集合存储多个对象,然后直接对集合进行序列化即可
注意:ArrayList集合已经实现了序列化接口!

IO框架

为了简化对IO操作,很多技术大牛或者组织提供了一些有关IO流小框架,可以提高IO流的开发效率。

如:

  1. 常用的hutool:https://hutool.cn/docs/#/core/IO/%E6%A6%82%E8%BF%B0
  2. apache开源基金组织提供了一组有关IO流小框架:commons-io
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不愿意做鱼的小鲸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值