IO流:字节流

简介: 在Java编程中,IO流是一个核心概念,用于与文件、网络、内存等数据源交互。Java的IO库提供丰富的类和方法支持数据读写。IO流分为字节流和字符流,前者可操作所有类型文件,后者仅限纯文本文件。`FileOutputStream`用于向文件写入字节,支持多种写入方式,并可通过构造函数的布尔参数控制是否追加写入。`FileInputStream`则用于从文件读取字节,支持逐字节或批量读取。文件拷贝可通过结合读写操作实现,高效的方法是一次性读取并写入大容量数组。处理IO流时需注意异常管理,合理使用try-catch-finally结构确保资源正确释放。JDK7及以后版本提供了自动关闭资源的简化语法

1. IO流体系结构

在Java编程中,IO(Input/Output)流是一个非常重要的概念,它允许我们与各种数据源(如文件、网络、内存等)进行交互。Java的IO库提供了丰富的类和方法,用于读取和写入数据。

IO流按照操作文件类型又可以分为
字节流:可以操作所有类型文件
字符流:只能操作纯文本文件

由于上面的四个都是抽象类,在实现的时候要创建子类的对象,这里以字节流为例,下面是其两个子类

2. FileOutputStream

FileOutputStream:操作本地文件的字节输出流,可以把程序中的数据写入到本地文件中。

1.参数是字符串表示的路径或者File对象都可以
2.如果文件不存在就会创建一个新的文件,但是要保证父级路径是存在的
3.如果文件存在,那么会清空文件中的数据,然后再写入
写入文件时,传入参数是整数,会转换为ASCII码对应的字符
如果想要写入整型,就分开写对应的ASCII码

2.1. FileOutputStream写数据的三种方式

创建对象之后是通过调用write()方法进行写入

FileOutputStream fos = new FileOutputStream("E:\\java\\a.txt");
        // 写入一个数据
        fos.write(97);

这里会出现异常,将异常进行抛出处理

write()也提供了其他的重载方法

可以传入一个byte类型的数组,写入多个字节,还可以指定写入的起始索引,再指出写入几个字节

byte[] bytes = {97, 98, 99, 100};
        fos.write(bytes);
        //从1索引开始写入两个字符
        fos.write(bytes, 1, 2);

每次执行操作结束之后都要调用close()方法进行资源释放,否则文件就会一直被占用

打开文件,其实是在该进程的文件描述符表中,创建了一个新的表项,描述了该进程要执行哪些操作,文件描述表,可以认为是一个数组,数组中的每一个元素就是一个struct file对象(Linux内核),每一个结构体就描述了对应操作的文件信息,数组的下标,就被称为文件描述符

每次打开一个文件,就相当于在数组上占用了一个位置,而在系统内核中,文件描述符表数组是固定长度并且不可扩容的,除非主动调用close关闭文件,此时才会释放出空间,如果代码里一直打开不去关闭,就会使这里的资源越来越少,数组满了之后文件就打不开了

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("E:\\java\\a.txt");
        // 写入一个数据
        fos.write(97);
        //写入多个数据
        byte[] bytes = {97, 98, 99, 100};
        fos.write(bytes);
        //从1索引开始写入两个字符
        fos.write(bytes, 1, 2);
        //释放资源,解除资源占用
        fos.close();
    }
}

2.2. 换行和续写

除了以上三种写入方式外,还可以通过字符串的方式进行写入
此外,如果需要写入换行,在windows操作系统中,是用 “\r\n” 表示换行

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("E:\\java\\a.txt");
        //写入字符串
        String str = "hello";
        byte[] bytes1 = str.getBytes();
        fos.write(bytes1);
        //写入换行
        String s = "\r\n";
        fos.write(s.getBytes());
        fos.write(97);
        //释放资源,解除资源占用
        fos.close();
    }
}

打开记事本查看a.txt的内容

续写就是不清空原来文件的内容,接着往下写
FileOutputStream()的构造方法还有一个boolean类型的参数,表示续写开关,默认是false,如果创建对象时给出true,那么就表示续写,此时就不会清空原来文件的内容

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        //续写,之前内容不会清空
        FileOutputStream fos1 = new FileOutputStream("E:\\java\\a.txt", true);//打开续写
        fos1.write("666".getBytes());
        //释放资源
        fos1.close();
    }
}

可以看出,这次是接着上次的内容继续进行写入的

3. FileInputStream

操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来

3.1. 每次读取一个字节

第一步也是创建对象,接着调用read()方法,就可以读取到一个字节的内容,读到的内容也是ASCII码对应的数字

public class ByteStreamDemo2 {
    public static void main(String[] args) throws IOException {
        //字节输出入流,如果文件不存在,就会报错
        FileInputStream fis = new FileInputStream("E:\\java\\a.txt");
        //读取一个字节,读取到末尾再继续读会返回-1
        int res = fis.read();
        System.out.println((char) res);
        //释放资源
        fis.close();
    }
}


创建对象时,如果对应路径不存在,就会直接报错,此外,读取到末尾之后继续读就会返回-1,那么文件中最后确实是-1的话怎么判断呢?“-1"其实是分为”-" 和 "1"的,所以并不冲突
根据这个特性可以进行文件的循环读取

int a = 0;
        while ((a = fis.read() )!= -1){
            System.out.print((char)a);
        }
        System.out.println();

问题:如果不用一个变量来接收行不行?

while(fis.read() != -1){
            System.out.println((char)fis.read());
        }

答:因为每次调用read就表示往后移动一位,所以每次循环都进行了两次读取(判断条件一次,输出语句一次),打印的数据其实是跳跃的

3.2. 读取多个字节

如果要读取多个字节,可以在read方法中传入一个byte类型的数组,数组长度是多少每次就读几个字节

public class ByteStreamDemo2 {
    public static void main(String[] args) throws IOException {
        //字节输出入流,如果文件不存在,就会报错
        FileInputStream fis = new FileInputStream("E:\\java\\a.txt");
        //读取多个字节
        byte[] byte1 = new byte[3];
        int len1 = fis.read(byte1);
        System.out.println(new String(byte1));
        System.out.println(len1);
        //如果继续往下读,因为文件中剩余的数据不足数组的长度,只覆盖读取到了的数据
        int len2 = fis.read(byte1);
        System.out.println(new String(byte1));
        System.out.println(new String(byte1,0,len2));//只打印读到的数据
        System.out.println(len2);
        //释放资源
        fis.close();
    }
}

如果继续往下读,文件中剩余的数据不足数组的长度,只覆盖读取到的数据,此时就可以把读取的字节数进行一个返回,只打印读取到的数据,也就是没有被覆盖的数据不打印

4. 文件拷贝

文件拷贝就是把读取和写入结合起来,把读取到的数据再写入另一个文件中,所以也有两种方式进行拷贝,一种是一个字节一个字节的拷贝,另一种就是直接定义一个大容量的数组,一次拷贝完成,很显然,后者更加高效

public class ByteStreamDemo3 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("E:\\java\\a.txt");
        FileOutputStream fos = new FileOutputStream("E:\\java\\b.txt");
        //一次拷贝一个字节
        int b1 = 0;
        while ((b1 = fis.read()) != -1) {
            fos.write(b1);
        }
        //一次拷贝多个字节
        byte[] b2 = new byte[1024];
        int len = 0;
        while((len = fis.read(b2)) != -1){
            fos.write(b2,0,len);
        }
        //释放资源,先开后关
        fos.close();
        fis.close();
    }
}

最后释放资源的时候需要注意,遵循最先打开的文件最后关闭的原则

5. IO流中的异常处理方式

在之前,我们都是对异常进行抛出处理,怎么去使用try - catch处理呢?
首先需要明白,程序最后都要进行资源释放,所以就可以采用try - catch - finally结构,把资源释放的模块放在finally里

如果按照正常的想法,把有异常的模块都放进try里,此时创建出的对象就属于局部变量,finally里调用不了,就需要把创建对象的部分写在外面

但此时还是报错了,还需要对finally的内容进行异常处理

这样看貌似是没有问题了,但是还是有一个细节需要注意,如果创建对象时给的路径不存在,还是会报错,除了正常给出的路径异常,还有一个空指针异常

就需要处理对象为空的情况

public class ByteStreamDemo4 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("E:\\java\\a.txt");
            fos = new FileOutputStream("E:\\java\\b.txt");
            //一次拷贝多个字节
            byte[] b = new byte[1024];
            int len = 0;
            while ((len = fis.read(b)) != -1) {
                fos.write(b, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源,先开后关
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

此时再运行就没有空指针异常了
上面的代码看起来很多,在JDK7和JDK9种给出了两种简化版本,推出了AutoCloseable接口,它定义了一个可以被自动关闭的资源,确保在 try 代码块执行完毕后,资源能够自动关闭,即使发生了异常,但是只能在特定的情况可以使用
JDK7:

JDK9:

这样就不用在finally种写一堆对释放资源处理的异常了,不过,一般情况下直接抛出就可以了

相关文章
|
4月前
|
存储 移动开发 Java
从零开始学习 Java:简单易懂的入门指南之IO字节流(三十)
从零开始学习 Java:简单易懂的入门指南之IO字节流(三十)
|
1月前
|
存储 缓存 Java
15 Java IO流(File类+IO流+字节流+字符流+字节编码)
15 Java IO流(File类+IO流+字节流+字符流+字节编码)
42 3
|
3月前
|
Java 数据处理 开发者
揭秘Java IO流:字节流与字符流的神秘面纱!
【6月更文挑战第26天】Java IO流涵盖字节流和字符流,字节流处理二进制数据,如图像,由InputStream/OutputStream家族管理;字符流处理文本,基于Reader/Writer,适于文本文件。在文件复制示例中,字节流用FileInputStream/FileOutputStream,字符流用FileReader/FileWriter。选择流类型取决于数据类型和处理需求,文本文件优选字符流,二进制数据则选字节流。
51 6
|
2月前
|
存储 缓存 Java
JavaSE—IO流之字符流与字节流
JavaSE—IO流之字符流与字节流
|
3月前
|
存储 自然语言处理 Java
Java IO流完全手册:字节流和字符流的常见应用场景分析!
【6月更文挑战第26天】Java IO流涵盖字节流和字符流,字节流用于二进制文件读写及网络通信,如图片和音频处理;字符流适用于文本文件操作,支持多语言编码,确保文本正确性。在处理数据时,根据内容类型选择合适的流至关重要。
47 0
|
3月前
|
自然语言处理 Java
Java IO流进阶教程:掌握字节流和字符流的高级用法!
【6月更文挑战第26天】Java IO流助你高效交换数据,包括字节流(InputStream/OutputStream)和字符流(Reader/Writer)的高级技巧。缓冲流(Buffered*)提升读写性能,对象流(Object*Stream)支持对象序列化。字符流的BufferedReader/BufferedWriter优化文本处理,注意字符集如UTF-8用于编码转换。掌握这些,优化IO操作,提升代码质量。
31 0
|
3月前
|
Java 测试技术
Java IO流深度剖析:字节流和字符流的性能对比!
【6月更文挑战第26天】Java IO流分字节流和字符流,字节流处理所有类型数据(如图片),字符流处理文本(基于Unicode)。字节流直接处理,性能高,适合非文本文件;字符流处理文本时考虑编码,适合文本文件。性能测试显示,字节流在读写非文本文件时更快,而字符流在处理文本时更方便。选择流类型应依据数据类型和需求。
39 0
|
3月前
|
自然语言处理 Java 数据处理
Java IO流全解析:字节流和字符流的区别与联系!
【6月更文挑战第26天】Java IO流涵盖字节流与字符流。字节流(InputStream/OutputStream)处理数据单位为字节,适用于二进制和文本,而字符流(Reader/Writer)专注于文本,处理单位为字符,处理编码转换。字符流在字节流基础上添加编码处理,以装饰器模式实现。文件复制示例展示了两者区别:字节流直接复制所有数据,字符流处理字符编码。理解并选择适当流类型对优化程序至关重要。
89 0
|
4月前
|
存储 缓存 Java
【Java IO系列】那字节流和字符流有什么区别?
而如果使用缓存流,一次性从文件里读取多个字节到缓存中,可以减少系统调用同时也减少了磁盘读取,提高了读取的效率。所以字符流是一个很方便的流了,没有必要把一个方便的流转换成一个不方便的流。,涉及到用户空间和内核空间之间的上下文切换,这些切换是很昂贵的。从输入流读取下一个数据字节,值字节以0到255范围内的。好的面试官,Java IO有两个参与对象,一个是。,这个基类提供了3个方法可以来读取字节流。,同样是提供了3个方法来支持字符流读取。好的,有这些不同之处,主要是3个方面。是这样的,虽然字节流比字符流的。
【Java IO系列】那字节流和字符流有什么区别?
|
4月前
|
缓存 Java
IO流【Java中IO的四大抽象类、常用流详解 、 缓冲字节流、 文件字符流、缓冲字符流】(二)-全面详解(学习总结---从入门到深化)
IO流【Java中IO的四大抽象类、常用流详解 、 缓冲字节流、 文件字符流、缓冲字符流】(二)-全面详解(学习总结---从入门到深化)
64 0
IO流【Java中IO的四大抽象类、常用流详解 、 缓冲字节流、 文件字符流、缓冲字符流】(二)-全面详解(学习总结---从入门到深化)