JAVA基础篇——IO流

十四、IO流

1. 概念

File存在原因:

变量、数组、对象、集合都是内存中的数据容器,它们在断电、或者程序终止时会丢失。File可以把数据长久保存,存储在计算机硬盘中。

File类可以操作文件

File概念:

File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)

注意:

File类只能对文件本身进行操作,不能读写文件里面存储的数据。可以进行创建文件/文件夹,删除文件/文件夹,获取文件大小、文件名等信息,判断文件类型等操作。

IO流:

读写文件中或网络中的数据

2. File文件对象的创建

public class FileTest1 {
    public static void main(String[] args) {
        // 1. 创建一个File对象,指代某个具体的文件
        // 路径分隔符
        // File f1 = new File("E:\\want\\code\\javasepromax\\test_file_IO.txt");
        // File f1 = new File("E:/want/code/javasepromax/test_file_IO.txt");
        File f1 = new File("E:" + File.separator + "want" + File.separator + "code" + File.separator + "javasepromax" + File.separator + "test_file_IO.txt");
        System.out.println(f1.length());  // 输出文件的大小  文件夹也可以输出大小,只不过是文件夹信息的大小,不是文件夹中所有内容的大小

        // 2. File对象可以指代一个不存在的文件路径
        File f2 = new File("E:\\hhh.txt");
        System.out.println(f2.length());

        // 3. 相对路径
        File f3 = new File("test_file_IO.txt");  // 相对路径工程名以及前面的路径都不要写!!!直接从模块名开始,默认到当前工程下的模块路径找
        System.out.println(f3.length());
        System.out.println(f3.getAbsolutePath());

    }
}

3. File对象常用方法

判断文件的类型、获取文件信息

public class FileTest2 {
    public static void main(String[] args) {
        // 1. 创建一个File对象,指代某个具体的文件
        // 路径分隔符
        File f1 = new File("E:\\want\\code\\javasepromax\\test_file_IO.txt");
        // 2. exists():判断当前文件对象对应的文件路径是否存在,如果存在,返回true
        System.out.println(f1.exists());  // true
        // 3. isFile():判断当前文件对象指代的是否是文件,是文件返回true
        System.out.println(f1.isFile());  // true
        // 4. isDirectory():判断当前文件对象指代的是否是文件夹,是文件夹返回true
        System.out.println(f1.isDirectory());  // false
        // 5. getName():获取文件名称(包含后缀)
        System.out.println(f1.getName());
        // 6. length():获取文件大小,返回字节个数
        System.out.println(f1.length());
        // 7. lastModified():获取文件最后的修改时间
        System.out.println(f1.lastModified());
        // 8. getPath():获取创建文件对象时,使用的路径
        System.out.println(f1.getPath());  // 绝对路径/相对路径
        // 9. getAbsolutePath()获取绝对路径
        System.out.println(f1.getAbsolutePath());
    }
}

创建文件、删除文件

public class FileTest3 {
    public static void main(String[] args) throws IOException {
        File f1 = new File("E:\\want\\code\\javasepromax\\test_file_IO1.txt");
        System.out.println(f1.createNewFile());

        // mkdir()创建一级文件夹——意思不是不能跨文件夹创建,而是文件的上一层文件夹必须存在
        File f2 = new File("E:\\want\\code\\javasepromax\\oop-app5\\src\\test_file_IO3.txt");
        System.out.println(f2.mkdir());

        // mkdires()创建多级文件
        // delete()只能删除文件、空文件夹,不能删除非空文件夹

    }
}

遍历文件夹

public class FileTest4 {
    public static void main(String[] args) {
        // list():获取一级文件名称到一个字符串数组中去返回
        File f1 = new File("E:\\want\\code\\javasepromax");
        String[] names = f1.list();
        System.out.println(Arrays.toString(names));

        // listFiles():重点,获取当前目录下所有的一级文件对象到一个文件对象数组中去返回
        File[] files = f1.listFiles();
        for (File file : files) {
            System.out.println(file.getAbsoluteFile());
        }
    }
}

注意事项:

  • 当主调是文件,或者路径不存在时,返回null
  • 当主调是空文件夹时,返回一个长度为0的数组
  • 当主调时有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
  • 当主调是一个文件夹,里面隐藏文件也会返回在File数组中
  • 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null

4. 方法递归

需要注意:要控制好终止条件,不然会出现递归死循环,导致栈内存溢出错误

文件搜索案例——没有公式的案例

public class FileTest5 {
    public static void main(String[] args) {
        searchFile(new File("D:/"), "wemeetapp.exe");
    }

    /**
     * 去目录下搜索某个文件
     * @param dir  目录
     * @param fileName  要搜索的文件名称
     */
    public static void searchFile(File dir, String fileName) {
        // 1. 把非法的情况拦截
        if (dir == null || !dir.exists() || dir.isFile()) {
            return;  // 无法搜索
        }

        // 2. dir不是null,存在且一定是目录对象
        File[] files = dir.listFiles();

        // 3. 判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象
        if (files != null && files.length > 0) {
            // 4. 遍历全部一级文件对象
            for (File file : files) {
                // 5. 判断文件是文件,还是文件夹
                if (file.isFile()) {
                    // 是文件,判断这个文件名是否是我们要找的,contains可以多个
                    if (file.getName().contains(fileName)) {
                        System.out.println(file.getAbsoluteFile());
                    }
                } else {
                    // 是文件夹,继续重复这个过程(递归)
                    searchFile(file, fileName);
                }
            }
        }

    }
}

5. 字符集

ASCLL字符集: 英文数字特殊符号等,使用一个字节 存储一个字符,首位是0,总共可表示128个字符

GBK:汉字内码扩展规范,国标,包含两万多个汉字等字符,GBK中一个中文字符编码称两个字节 的形式存储,首位必须是1。其兼容了ASCLL字符集

Unicode字符集(统一码,万国码): 世界上所有文字、符号的字符集。

  • UTF-32:4个字节表示一个字符,有容乃大——浪费存储空间,降低通讯效率
  • UTF-8: 采取可变长编码方案,共分为四个长度区:1个字节,2个字节,3个字节,4个字节;英文字符、数字只占1个字节,汉字字符占3个字节

在这里插入图片描述

开发时都应该用UTF-8编码!!!

注意:

  • 编码和解码时使用的字符集必须一致,否则会出现乱码
  • 英文,数字一般不会乱码,因为很多字符集都兼容了ASCLL编码
public class FileTest6 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        // 1. 编码
        String data = "a我b";
        byte[] bytes = data.getBytes();  // 默认的UTF-8
        System.out.println(Arrays.toString(bytes));  // [97, -26, -120, -111, 98]

        // 按照指定 字符集编码
        byte[] bytes1 = data.getBytes("GBK");
        System.out.println(Arrays.toString(bytes1));  // [97, -50, -46, 98];


    	// 2. 解码
   	 	String s1 = new String(bytes);  // 默认UTF-8
    	System.out.println(s1);

    	String s2 = new String(bytes1);  // 乱码
    	System.out.println(s2);

    	String s3 = new String(bytes1, "GBK");  // 指定解码字符集
    	System.out.println(s3);
}
}

6. IO流

输入输出流,读写数据的

输入流 Input:把磁盘\网络中的数据读到内存中去

输出流Output:把内存中的数据写到磁盘/网络中

应用场景:

通信/拷贝文件

6.1 分类

按流的方向: 输入流、输出流

按流中数据的最小单位:

  • 字节流:可以操作所有类型的文件——音频、视频、图片、文件复制等
  • 字符流:只适合操作纯文本文件——读写txt、java文件等

将四个流结合

6.2 IO流的体系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.2.1 FileInputStream(文件字节输入流)

作用:把磁盘文件中的数据以字节的形式读入到内存中去

每次读取一个字节

public class FileInputStreamTest1 {
    public static void main(String[] args) throws IOException {
        // 每次读取一个字节
        // 1. 创建文件字节输入流管道,与源文件接通
        // FileInputStream is = new FileInputStream("file-io-app\\src\\itheima01.txt");
        // 多态
        // InputStream is = new FileInputStream(new File("file-io-app\\src\\itheima01.txt"));
        InputStream is = new FileInputStream("file-io-app\\src\\itheima01.txt");  // 简化写法,推荐使用

        // 2. 读取文件的字节数据
        // 每次读取一个字节,如果没有数据,返回-1
//        int b1 = is.read();
//        System.out.println(b1);  // 97
//        System.out.println((char)b1);  // a
//
//        int b2 = is.read();
//        System.out.println(b2);  // 98
//        System.out.println((char)b2);  // b
//
//        int b3 = is.read();
//        System.out.println(b3);  // -1

        // 3. 循环读取文件
        int b;
        while ((b = is.read()) != -1) {
            System.out.print((char)b);
        }
        // 问题:
        // 读取数据的性能很差,每次读取一个字节时,要去硬盘上,要找还要调用硬盘资源
        // 读取汉字会乱码
        // 流使用完毕后,必须关闭!释放系统资源!!!

        // 3. 释放资源
        is.close();

    }
}

每次读取多个字节

public class FileInputStreamTest2 {
    public static void main(String[] args) throws Exception {
        // 每次读取多个字节
        // 1. 创建FileInputStream输入流对象,代表字节输入流管道与源文件相通
        InputStream is = new FileInputStream("file-io-app\\src\\itheima02.txt");  // abc66

//        // 2. 一次读取多个字节
//        byte[] buffer = new byte[3];
//        int len = is.read(buffer);  // 返回当前读了多少个字节
//        // String的构造器
//        String rs = new String(buffer);
//        System.out.println(rs);  // abc
//        System.out.println(len);  // 3
//
//        int len2 = is.read(buffer);
        System.out.println(new String(buffer));  // 66c
        System.out.println(len2);  // 2
//
//        // 为了实现读多少,打印多少
//        System.out.println(new String(buffer, 0, len2));  // 66
//        System.out.println(len2);  // 2

        // 循环改造 —— 降低系统调用的次数
        byte[] buffer = new byte[3];
        int len;
        while((len = is.read(buffer)) != -1) {
            String rs = new String(buffer, 0, len);
            System.out.print(rs);
        }

        // 3. 关闭资源
        is.close();
        
        // 问题:读取汉字有乱码,与指定的数组长度有关
        // 适合文件拷贝操作
    }
}

每次读取完文件的全部字节——使用字节流读取中文,保证输出不乱码

public class FileInputStreamTest3 {
    public static void main(String[] args) throws Exception {
        // 使用文件字节输入流一次读取完文件的全部字节
        // 1. 创建字节输入流管道
        InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");

        // 2. 准备一个与文件大小一致的字节数组
//        File f = new File("file-io-app\\src\\itheima03.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);

        // 改进:文件字节输入流提供了方法
        byte[] buffer = is.readAllBytes();  // 内部有机制看文件过大的问题——>内存溢出
        System.out.println(new String(buffer));

        // 关闭资源
        is.close();
    }
}

但是读取文件全部内容可能会引起内存溢出

总结:字节流适合做数据的转移,如文件复制;字符流更适合读写文本内容

6.2.2 FileOutputStream(文件字节输出流)

作用:把内存中的数据以字节的形式写出到文件中去

public class FileOutPutStreamTest4 {
    public static void main(String[] args) throws Exception {
        // 掌握文件字节输出流FileOutputStream的使用
        // 1. 创建一个文件字节输出流管道
//        OutputStream os = new FileOutputStream("file-io-app\\src\\itheima04.txt");  // 覆盖
        OutputStream os = new FileOutputStream("file-io-app\\src\\itheima04.txt", true);  // 添加

        // 2. 开始写字节出去文件
        // 每次写一个字节:不能写汉字,字符
//        os.write(97);
//        os.write('b');

        // 每次写多个字节——新执行一次会覆盖原来文件里面的内容
        byte[] bytes = "我爱你中国abc".getBytes();
        os.write(bytes);

        // 写一部分
        os.write(bytes, 0, 15);  // 这里的长度是以字节为单位,一个中文字符在UTF-8下是3个字节,一共五个中文字符,所以长度是15个字节

        // 换行符
        os.write("\r\n".getBytes());

        // 3. 关闭流
        os.close();
    }
}
6.2.3 案例:文件复制
6.2.4 释放资源的方式

原:.close();——问题:如果中间代码出现异常,关闭流的代码就执行不到

try-catch-finally:无论正常执行还是出现异常,都一定会执行finally区,除非JVM终止——不要在finally中返回数据

格式:

try {

​ ……

} catch(异常名称) {

​ e.printStackTrace();

} finally {

}

public class Test1 {
    public static void main(String[] args) {
        try {
            System.out.println(10 / 2);
            System.exit(0);  // 结束虚拟机,finally不执行了
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("finally执行了一次");  // 不能在finally中返回数据
        }
    }
}

作用:一般用于在程序执行完成后进行资源释放

缺点:代码臃肿

public class FileOutPutStreamTest4 {
    public static void main(String[] args) {
        // 作用域扩大,在finally中能找到
        OutputStream os = null;
        try {
            // 掌握文件字节输出流FileOutputStream的使用
            // 1. 创建一个文件字节输出流管道
//        OutputStream os = new FileOutputStream("file-io-app\\src\\itheima04.txt");  // 覆盖
            os = new FileOutputStream("file-io-app\\src\\itheima04.txt", true);  // 添加

            // 2. 开始写字节出去文件
            // 每次写一个字节:不能写汉字,字符
//        os.write(97);
//        os.write('b');

            // 每次写多个字节——新执行一次会覆盖原来文件里面的内容
            byte[] bytes = "我爱你中国abc".getBytes();
            os.write(bytes);

            // 写一部分
            os.write(bytes, 0, 15);  // 这里的长度是以字节为单位,一个中文字符在UTF-8下是3个字节,一共五个中文字符,所以长度是15个字节

            // 换行符
            os.write("\r\n".getBytes());


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 关闭流
            // os.close();  // 报错的原因是怕流没有创建好
            try {
                if (os != null) os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

try-with-resource

格式:

try (定义资源1; 定义资源2; ……) {

​ 可能出现异常的代码;

} catch (异常类名 变量名) {

​ 异常处理的代码;

}

将流对象放在try的小括号中,结束后系统自动调用.close

小括号中只能放置资源对象——资源都会实现AutoCloseable接口

6.2.5 FileReader(文件字符输入流)

作用:把文件中的数据以字符的方式读入到内存中去

public class FileReaderTest1 {
    public static void main(String[] args) {
        // 1. 创建一个文件字符输入流
        try (
                Reader fr = new FileReader("file-io-app\\src\\itheima01.txt");
                ) {
            // 2. 读取文件内容
//            int c;  // 记住每次读取的字符编号
//            while ((c = fr.read()) != -1) {
//                System.out.print((char)c);;
//            }

            // 每次读取多个字符
            char[] buffer = new char[3];
            int len;
            while ((len = fr.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));  // 注意写范围,为了避免报错
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
6.2.6 FileWriter(文件字符输出流)

作用:把内存中的数据以字符的形式输出到文件中去

public class FileWriterTest1 {
    public static void main(String[] args) {
        // 0. 创建一个文件字符输出流对象,与目标文件接通
        try (
                Writer fw = new FileWriter("file-io-app\\src\\itheima05.txt")
            ) {
            // 1. write(int c) :写一个字符进去
            fw.write('a');
            fw.write(97);
            fw.write('与');

            // 2. write(String c):写一个字符串进去
            fw.write("小鱼0135");

            // 3. write(String c, int pops, int len):写字符串的一部分出去
            fw.write("小鱼0135", 0, 2);

            // 4. write(char[] buffer):写一个字符数组出去
            char[] buffer = {'小', '黑'};
            fw.write(buffer);

            // 5. write(char[] buffer, int pos, int len):写字符数组的一部分出去
            fw.write(buffer, 1, 1);

            // 换行
            fw.write("\r\n");

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

    }
}

注意事项: 在字符输出流写出数据后,必须刷新或者关闭流,写出去的数据才能生效;缓存区满了会自己刷新

fw.flush(); // 刷新流

7 缓冲流

对原始流进行包装,以提高原始流读写数据的性能——增加内存中的空间,减少操作硬盘的次数,转而在内存中操作

原理:字节缓存输入流自带8KB缓冲池,字节缓存输出流自带8KB缓冲池

(缓存:提高读写速度;缓冲:方便数据传输,平衡传输速度)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

字节缓冲流——增加操作:new BufferedInputStream(文件字节输入流对象)

字符缓冲流——自带8ak字符缓冲池

  • 字符缓冲输入流——新增 readLine():读取一行——>不要使用多态的写法,因为要用实现类里面特有的方法
  • 字符缓冲输出流——新增newLine():换行

性能分析:

缓冲的一般会快,字节数组较大的话,性能也差不多。但是字节数组扩太大对性能影响不明显

8. 转换流(字符流下面的实现类,因为字符有编码)

问题:文件输入编码和读取的编码不一致时,就会出现乱码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

字符输入转换流

思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流

public class InputStreamReaderTest2 {
    public static void main(String[] args) {
        // 字符输入转换流
        // 1. 得到文件的原始字节输入流
        try (
                InputStream is = new FileInputStream("file-io-app\\src\\itheima06.txt");
                // 2. 把原始的字节输入流按照指定的编码转换成字符输入流
                Reader isr = new InputStreamReader(is, "GBK");  // 第二个参数是文件原始的编码方式
                // 3. 把字符输入流包装成缓冲字符输入流
                BufferedReader bisr = new BufferedReader(isr);
                ){
            String line;
            while ((line = bisr.readLine()) != null) {
                System.out.println(line);
            }

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

字符输出转换流

  • byte[] bytes = data.getBytes(“GBK”);
  • 字符输出转换流

9. 打印流

方便高效的打印出去——>写出去,能实现打印啥就是啥

在这里插入图片描述

public class PrintStreamTest1 {
    public static void main(String[] args) {
        // 1. 创建一个打印流管道
        try (
                PrintStream ps = new PrintStream("file-io-app\\src\\itheima07.txt");
//                PrintStream ps = new PrintStream("file-io-app\\src\\itheima07.txt", Charset.forName("GBK"));  // 指定字符集编码形式
                ){
            ps.println(97);  // 文件是97
            ps.println('a');
            ps.println("小鱼0135");
            ps.println(true);
            ps.write(97);  // a
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class PrintStreamTest1 {
    public static void main(String[] args) {
        // 1. 创建一个打印流管道
        try (
                PrintWriter ps = new PrintWriter("file-io-app\\src\\itheima07.txt");
//                PrintStream ps = new PrintStream("file-io-app\\src\\itheima07.txt", Charset.forName("GBK"));  // 指定字符集编码形式
                ){
            ps.println(97);  // 文件是97
            ps.println('a');
            ps.println("小鱼0135");
            ps.println(true);
            ps.write(97);  // a
            ps.write("嘎嘎嘎");  // 比PriintStream多的可以打印字符、字符串等
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

要想模式变成追加而不是覆盖,可以先使用低级流,再包装成高级流

应用:输出语句的重定向

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10. 数据流(通讯的时候)

可以输出/输入数据类型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DataOutStream(数据输出流)——允许数据和其类型一并写出去

public class DataOutPutStreamTest1 {
    public static void main(String[] args) {
        // 1. 创建一个数据输出流包装低级的字节输出流
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("file-io-app\\src\\itheima08.txt"))) {
            dos.writeInt(97);
            dos.writeBoolean(true);
            dos.writeUTF("小鱼0135");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 2. 创建数据输入流
        try (
                DataInputStream dis = new DataInputStream(new FileInputStream("file-io-app\\src\\itheima08.txt"));
                ){
            int i = dis.readInt();  // 按写入的顺序
            System.out.println(i);
            Boolean b = dis.readBoolean();
            System.out.println(b);
            String s = dis.readUTF();
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

11. 序列化流

用来把对象写入文件,或者将对象从文件中读取出来

  • 对象序列化:把Java对象写入文件
  • 对象反序列化:把文件里的Java对象读出来

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值