1.前置知识:字符集

简介

使用ASCII码表示128个字符,第一位都是0。

GBK:一个中文字符编码成两个字节存储。包含ASCII,汉字第一个字节第一位必须是1,以此来区分是汉字还是字母或者其他。

Unicode:万国码。

UTF-32:四个字节表示一个字符。

UTF-8:可变长编码方案。一到四个字不等。ASCII占一个字节,汉字占三个字节

IO流:字节流_字节数

字符集的编码与解码

IO流:字节流_System_02

2.IO流

概述

输入流和输出流。

IO流:字节流_System_03

(1)字节输入流   InputStream   FileInputStream 从磁盘读内容到内存

(2)字节输出流   OutStream   FileOutStream

(3)字符输入流   Reader   FileReader

(4)字符输出流   Writer   FileWriter

以上四个都是抽象类,后面是他们的实现类。

(1)FileInputStream 文件字节输入流(读一个字节)

IO流:字节流_System_04

public class FileInputStreamTest1 {

    public static void main(String[] args) throws Exception {
        // 1. 创建文件字节输入流对象,与源文件接通。
        // InputStream is = new FileInputStream(new File("file-io-app\\src\\a01.txt"));
        // 简化写法,推荐使用。
        InputStream is = new FileInputStream("file-io-app\\src\\a01.txt");

        // 2. 开始读取文件的字节数据:
        // public int read():每次读一个字节返回,如果没有数据了,返回-1。
        int b1 = is.read();
        System.out.println((char) b1);

        int b2 = is.read();
        System.out.println((char) b2);

        int b3 = is.read();
        System.out.println(b3);
        
        //循环读取,读中文会出错
        while((b1 = is.read()) != -1){
            System.out.println((char) b1);
            
         is.close();//关闭通道
    }
}

上面代码有很多去缺陷。1.读取性能很差,因为这是到硬盘去读,效率低。2.读取汉字会乱码。

流使用之后需要关闭。

(2)读取多个字节

IO流:字节流_输入流_05

public class FileInputStreamTest2 {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个字符输入流对象以代表字符输入流管道与源文件接通。
        InputStream is = new FileInputStream("file-io-app\\src\\a\\data.txt");//存的是abcde

        // 2. 开始读取文件中的字节数据,每次读取多个字节。
        byte[] buffer = new byte[3];
        int len = is.read(buffer);
        String rs = new String(buffer);
        System.out.println(rs);
        System.out.println("当次读取的字节数是:" + len);//abc

        // 第二次读取
        int len2 = is.read(buffer);
        String rs2 = new String(buffer, 0, len2);//不加后边的0-len2,读的是dec,加了读的是de
        System.out.println(rs2);
        System.out.println("当次读取的字节数是:" + len2);

        // 第三次读取
        int len3 = is.read(buffer);
        System.out.println(len3); // -1
        //循环,
         byte[] buffer = new byte[3];
        int len; // 记录每次读取了多少个字节
        while ((len = is.read(buffer)) != -1) {
            // 注意:读取多少,输出多少
            String rs = new String(buffer, 0, len);
            System.out.print(rs);
        }

        // 关闭输入流
        is.close();
    }
}

上面代码读取性能的搭配提升,但读取汉字也会出现问题,会出现乱码。

(3)一次性读完全部字节

public class FileInputStreamTest3 {
    public static void main(String[] args) throws Exception {
        // 1. 一次性读取完文件的全部字节到一个字节数组中去。
        // 创建一个字符输入流管道与源文件接通
        InputStream is = new FileInputStream("file-io-app\\src\\a03.txt");
        
        // 2. 准备一个字节数组,大小与文件的大小正好一样大。
        File f = new File("file-io-app\\src\\a03.txt");
        long size = f.length();
        byte[] buffer = new byte[(int) size];

        int len = is.read(buffer); 
        System.out.println(new String(buffer));

        // 输出文件大小和读取的字节数
        System.out.println(size);
        System.out.println(len);

		// 使用 readAllBytes() 方法读取文件所有字节
        byte[] buffer = is.readAllBytes();
        System.out.println(new String(buffer));
        
        // 关闭输入流以释放资源
        is.close();
    }
}

可以解决中文乱码的问题,适合读相对较较小的文件。如果文件过大,创建的字节数组也会过大,会引起内存溢出。

读取文本内容更适合用字符流。

字节流适合做数据的转移,如文件复制。

(4)文件字节输出流

把内存的数据以字节的形式写到文件中去。

IO流:字节流_System_06

public class FileOutputStreamTest4 {
    public static void main(String[] args) throws Exception {
        // 追加数据到文件的管道
        OutputStream os = new FileOutputStream("file-io-app/src/itheima04out.txt", true);

        // 2. 开始写字节数据出去
        os.write(97); // 97是一个字节,代表'a'
        os.write('b'); // 'b'也是一个字节

        byte[] bytes = "我爱你中国abc".getBytes();
        os.write(bytes);

        os.write(bytes, 0, 15);// 从bytes数组的0开始,写入15个字节

        //换行符
        
        os.write("\r\n".getBytes());
        // 关闭流
        os.close();
    }
}

案例:文件复制

public class FileCopy {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个字节输入流管道与源文件接通
        InputStream is = new FileInputStream("D:/resource/meinv.png");

        // 2. 创建一个字节输出流管道与目标文件接通
        OutputStream os = new FileOutputStream("C:/data/meinv.png");//这里需要自己先写出文件名,因为系统不会自己创建

        // 3. 创建一个字节数组,负责转移字节数据
        byte[] buffer = new byte[1024]; // 1KB

        // 4. 从字节输入流中读取字节数据,每次读到字节数组中,读多少写出多少
        int len; // 记录每次读取了多少字节
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }

        // 关闭流
        os.close();
        is.close();
        System.out.println("复制完成!");
    }
}

一切文件的复制,不会出错,虽然读的时候是分开读的,但是传输完成会接着读,只要编码合适就能成功显示。

释放资源的方式

前面学的close方法关闭通道会出现问题,比如程序异常了,就不会执行后边的程序,通道关不上。下面这两种是解决方法。

(1)try-catch-finally

finally代码区的特点:无论try的程序是否正常执行了,还是出现了异常,最后一定会执行finally的代码,除非jvm终止,加return也没用。

注意:在finally不要加return语句。

finally作用:一般用于执行完程序释放资源。

public class FileCopyWithExceptionHandling {
    public static void main(String[] args) {
        InputStream is = null;
        OutputStream os = null;//放在前边,是因为如果放在try中,finally无法调用is os

        try {
            // 1. 创建一个字节输入流管道与源文件接通
            is = new FileInputStream("file-io-app\\src\\a03.txt");
            // 2. 创建一个字节输出流管道与目标文件接通
            os = new FileOutputStream("file-io-app\\src\\a03copy.txt");

            // 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();
                if (is != null) is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

(2)try-with-resource

更简单的资源释放方案。 

IO流:字节流_输入流_07

public class Test3 {
    public static void main(String[] args) {
        try (
            // 1. 创建一个字节输入流管道与源文件接通
            InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");
            // 2. 创建一个字节输出流管道与目标文件接通
            OutputStream os = new FileOutputStream("file-io-app\\src\\itheima03copy.txt")

            //这里只能放资源,int a = 1;会报错
            //资源是实现了AutoCloseable接口的对象,在try语句块结束时自动调用close()方法释放资源
            //也可以自己写一个类实现AutoCloseable接口,在try语句块结束时自动调用close()方法释放资源
        ) {
            // 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();
        }
    }
}

try这里只能放资源,int a = 1;会报错

资源是实现了AutoCloseable接口的对象,在try语句块结束时自动调用close()方法释放资源

也可以自己写一个类实现AutoCloseable接口,在try语句块结束时自动调用close()方法释放资源