字节流操作:InputStream类 读取文件的操作(三种 read 方法)

字节流操作:InputStream类 和 OutputStream类

观前提醒:

这篇博客是从 Java 文件操作 和 IO(3)-- Java文件内容操作(1)-- 字节流操作 这篇博客分离出来的,目的是减少博客字数。

如果你是第一次点击进来的,需要你将上面这个博客,阅读到这篇博客的连接,再点击进来看这篇文章。

InputStream类 读取文件的操作(三种 read 方法)

读取文件的最核心方法,就是:read方法

对于 InputStream类 ,Java提供了三种 read方法:

1. 不带参数的 read( )方法,返回值是:int 类型

在这里插入图片描述

这个方法的作用是:调用一次,读取一个字节

关于返回值的解释:返回的是int,不是 byte,当返回值为 -1 时,表示文件已经读取完毕

例子1(以英文字符为例):

现在在我本人的java项目路径当中,存在 test.txt 文件,文件当中,写的是 hello ,现在我使用循环,使用字节读取的方式,将里面的内容读取出来:
在这里插入图片描述
演示代码:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class demo1 {
    public static void main(String[] args) throws IOException {

        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            // 读取文件操作:
            while (true) {
                int data = inputStream.read();
                // 返回 -1 ,说明文件内容已经读取完毕
                if (data == -1) {
                    break;
                }
                System.out.println(data);
            }
        }
    }
}

运行结果演示:
在这里插入图片描述
相信你读到这样的结果,肯定会有疑惑:不是哥们,我文件里明明写的是 hello 啊,怎么给我输出的是 104,101… …这样的数字呢?

原因的话,就是说:我们这里是按照 字节的方式进行读取的,同时根据程序代码,是一个一个字节读取出来,分别进行打印的,而每个字节的范围,是:0 ~ 255。

hello 是由五个英文字母构成的一个英文字母在计算机中的存储大小为1个字节(byte)。所以,每个英文字母有对应的一个字节码,所以显示出来的就是五个字节码(数字)

而 hello 是纯英文的方式,是根据 ASCII码 进行编码的。我们可以查一下 ASCII码表,看看 h 在其中的表示:
在这里插入图片描述
至于其他的,你可以自己去 百度 搜查 ASCII码表,然后对照一下,运行结果中的那5个十进制数字,就正好是 hello 的对应的 ASCII码

特别提醒一下:
10 进制是正常的
8 进制是 0 开头的
16 进制是 0x 开头的

例子2(以中文字符为例):

现在在我本人的java项目路径当中,存在 test.txt 文件,文件当中,写的是 “你好” ,现在我使用循环,使用字节读取的方式,将里面的内容读取出来:
在这里插入图片描述
演示代码,还是例子1 的那个代码:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class demo1 {
    public static void main(String[] args) throws IOException {

        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            // 读取文件操作:
            while (true) {
                int data = inputStream.read();
                // 返回 -1 ,说明文件内容已经读取完毕
                if (data == -1) {
                    break;
                }
                System.out.println(data);
            }
        }
    }
}

运行结果:
在这里插入图片描述
这里显示的有 6 个字节,而我的文件里面写的只有 2 个汉字,所以,可以知道, 1 个汉字占 3 个字节
一个汉字占三个字节,是属于 utf-8 的编码集
我们可以看看 utf-8 的码表,去对比一下。

通过这个网站:查看字符码表 UTF-8,可以查看到 utf-8 码表中,对应汉字的字节码构成。

在码表,输入 你好 ,进行查询,结果:
在这里插入图片描述
三个字节是放到一起的,不是分开的显示,不是像我们程序当中,是分开显示的,所以,不好观察。我们可以使用 16 进制,进行打印,然后对比
十六进制,一个十六进制数字,表示 4 个 bit(比特位),两个十六进制的数字,表示 8 个bit(比特位),就是 1 个字节

那么,问题来了:Java里面,怎么打印 十六进制?
答:使用 C语言当中的 printf( )%x 或者 %X 表示的是十六进制的占位符。这两个占位符,也是有区别的。
%x小写的字母 x,表示输出出来的十六进制数中,英文字符,全部都以小写形式出现
%X大写的字母 X,表示输出出来的十六进制数中,英文字符,全部都以大写形式出现

为了与网站上查到的字符的十六进制相等,我们下面就是用 %X 来进行演示结果:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class demo1 {
    public static void main(String[] args) throws IOException {

        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            // 读取文件操作:
            while (true) {
                int data = inputStream.read();
                // 返回 -1 ,说明文件内容已经读取完毕
                if (data == -1) {
                    break;
                }
                // 以十六进制的方式,打印读取到的字节数
                // \n 表示的是换行符
                // %x 表示的是十六进制的占位符
                
                System.out.printf("%X\n",data);
            }
        }
    }
}

运行结果:
在这里插入图片描述
与网站上查到的十六进制编码进行比较:
在这里插入图片描述
通过对比发现,代码输出的结果的六个十六进制数字,和网上 UTF-8 编码表中,你好两个汉字所对应的十六进制数字,是一样的!!!

总结:

  1. 读取英文字符的时候,按照一个一个字节读出来的,打印的结果,是 英文字符所代表的 ASCII码
  2. 读取中文字符的时候,按照字节读取,结果以十六进制的形式表示,可以证明,是 UTF-8 字符集 中所表示的汉字

2. 带 byte[ ] 参数的 read( )方法,返回值是:int

在这里插入图片描述
这个方法的作用:调用一次,读取若干个字节读取到的数据放到定义的 字节数组 当中。

此方法的注意事项:

  1. 首先,此 read( ) 方法,带有 byte[ ] b 参数,使用之前,我们需要先定义一个这样的字节数组对象。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo2 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                //读取文件操作
                
			//一次读取多个字节,字节数组的长度,自行定义
            byte[] bytes = new byte[1024];
//            这个代码里面,有两个返回值,一个是 n,一个是 bytes数组(输出型参数)
                int n = inputStream.read(bytes);
            }
        }
    }
}

定义的这个字节数组的长度,根据自己的需要来进行自定义就可以了。

进行读操作的时候,会尽可能的把这个字节数组(字节数组的长度需要自定义)给填满,填不满的话,能填几个就是几个!

  1. 返回值,是 int 类型的数据

当返回值为 -1 时,表示文件已经读取完毕

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo2 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                //读取文件操作
                
			//一次读取多个字节,字节数组的长度,自行定义
            byte[] bytes = new byte[1024];
//            这个代码里面,有两个返回值,一个是 n,一个是 bytes数组(输出型参数)
                int n = inputStream.read(bytes);
            }
        }
    }
}

以这一句代码为例子,此处的 n 表示实际 read()方法 读取到的字节数,比如,读取了 6 个字节,n 的结果就是 6

  1. 这个read( )方法,使用 参数 作为方法的返回值,有两个返回值

第一个返回值:第二点讲到的 n (int类型的返回值)
第二个返回值:括号里面的参数,读取到的字节数据,会放到read( )方法括号里的字节数组引用指向的字节数组。

输出型参数:

这种写法,叫做“ 输出型参数 ” ,在 Java 当中,并不是很常见,在 C++ 中比较常见
什么意思呢?

不知道你们对于 Java 当中的方法(也就是 C语言 中的函数)是怎么理解的?
一般来说,我们可以把 函数/方法 想象成一座工厂参数就是原材料返回值就是产品。
在这里插入图片描述
可以根据上面这个图,对方法(也可以称为 函数)进行理解。

函数的参数,是原材料,函数的返回值,是输出的产品。
对于输出型参数来说:有的时候,我们也会使用参数来接收返回值。

在java中,如果参数是引用类型(数组,String,自定义类等),方法内部修改对象内容,能够影响到方法的外部。

在 C语言 当中,最典型的例子就是:交换两个变量的值。
如果你直接传入的是数值,形参是实参的拷贝,直接交换,就只会在方法内部生效,方法外没有影响。故而有了指针,可以传入该变量的指针,交换变量的指针所指向的值,就可以在方法外部也生效,真正实现了两个变量的值。

在Java中,没有指针了,但是有引用的概念,对于引用类型,有这么个语法特点:方法内部的修改,可以影响到方法外部 你所指向的对象。
正是由于这样的语法特点,使得引用类型的参数,可以作为输出型参数

以上,你能理解多少就理解多少,上面所述,只是扩展,哪怕你理解不了,你只需要记住:
第一个返回值:n,记录的是 read( ) 方法,这一次的读取,读到了多少个字节
第二个返回值:bytes,是数组的引用,既是参数,也是方法执行结束后,存放了读取数据的字节数组的引用。

再举一个例子:这个过程有点像 食堂打饭!!
食堂打饭,就是你拿个空的餐盘,来到窗口,你把空的餐盘递给打饭阿姨,让阿姨给你打饭,打完饭以后,餐盘给你,餐盘里面,还有饭菜。

//读取文件操作
byte[] bytes = new byte[1024];
//这个代码里面,有两个返回值,一个是 n,一个是 bytes数组(输出型参数)
int n = inputStream.read(bytes);

就拿这两句代码来进行举例:

byte[] bytes = new byte[1024];

bytes就相当于一个空餐盘

int n = inputStream.read(bytes);

inputStream.read()相当于阿姨给你打饭,你把空餐盘bytes,递给阿姨inputStream.read(bytes),打完饭后,阿姨给你的是盛满了饭菜的餐盘bytesn表示的就是你餐盘饭菜的量。

在这里插入图片描述
输出型参数的出现,本质上来说,还是语法上有限制。
Java,C++ 中,硬性要求:一个方法,只能有一个返回值,如果希望返回多个数据
(上述read()方法,就是希望同时返回,长度 和 内容数组),就只能通过引用类型的参数来凑了。

但是,同样的问题,在 Python 和 Go语言 上,就没有这样的限制了,他们都支持,一个方法,返回多个值。

带 byte[ ] 参数的 read( )方法的读取演示:

仍然是以 test.txt 文件,文件当中的 “你好” ,来进行读取打印:

具体思路:带 byte[ ] 参数的 read( )方法,可以一次读取多个字节,所以,每读一次,就需要循环遍历 bytes 数组一次,格式化输出里面的字节System.out.printf("%X\n",data),直至读取到的字节数 n 为 -1,表示文件数据已经读完,跳出 while()循环,结束读取文件操作。
这里有一个注意的点,遍历读取 bytes 数组内容的时候,不是使用 bytes.length去当作循环条件,而是使用 n,因为 n 才是真正反映此次读取到的有效字节数

演示代码:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class demo2 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                //读取文件操作
                byte[] bytes = new byte[1024];
//            这个代码里面,有两个返回值,一个是 n,一个是 bytes数组(输出型参数)
                int n = inputStream.read(bytes);
//            判断文件是否已经读取完毕
                if (n == -1) {

                    break;
                }
                for (int i = 0; i <n; i++) {
                    System.out.printf("%X\n",bytes[i]);
                }
            }
        }
    }
}

运行结果:
在这里插入图片描述
这里显示的就是 你好,所对应的 UTF-8 编码中,对应的十六进制数。

我们之前说,这个read()方法,会尽可能的去读取文件中的数据,放到你所定义的空白字节数组里面,读取到的字节数据的大小,取决于你空白字节数组的大小,现在,我们上面代码所定义的空白字节数组 bytes 的大小,由 1024 改为 3 ,同时,输出字节的时候,我们加个分隔符,以便进行区分。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class demo2 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                //读取文件操作
                byte[] bytes = new byte[3];
//            这个代码里面,有两个返回值,一个是 n,一个是 bytes数组(输出型参数)
                int n = inputStream.read(bytes);
//            判断文件是否已经读取完毕
                if (n == -1) {

                    break;
                }
                for (int i = 0; i <n; i++) {
                    System.out.printf("%X\n",bytes[i]);
                }
                System.out.println("======================");
            }
        }
    }
}

运行结果:
在这里插入图片描述
所以,这个 read()方法一次能读取到多少个字节,跟你自己定义的空白字节数组的大小是有关系的。

3. 带 三个参数的 read( )方法,返回值是:int

在这里插入图片描述

对这三个参数进行解释:

byte[ ] b :就是你定义的空白字节数组,调用 read()方法,后,读取到的字节,存放到这个数组里面。

int off :off --> offset 表示的是 偏移量(数组下标),本次读取到的字节数据,从空白字节数组的 off下标 开始存放,限制存放的起始位置

int len :表示你本次读取,想读取多少个字节往空白字节数组 b 中的哪个起始位置 off开始存放,限制了你本次可以读取到的字节len 往往会和返回值的大小(本次读取到了多少个字节)是一样的。

所以这个read()方法的作用就是:想读取多少(len)个字节往空白字节数组 b 中的哪个起始位置 off下标 开始存放

这个方法的使用场景通常是:你有一个非常大的字节数组,每次读操作,都把数据放到数组的某个部分,就会使用到这个 带有三个参数的 read()方法

演示:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class demo3 {
    public static void main(String[] args) throws IOException {
//使用 try with resource 的语法,打开文件,并程序结束的时候,自动执行 close()方法
        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
//              操作文件
//              定义空白字节数组
                byte[] bytes = new byte[1024];

//                读取文件
                int n = inputStream.read(bytes,0,3);
//                查看你本次读取到了多少个字节
                System.out.println("n = " + n);
//                判断是否已经读取结束
                if (n == -1) {
                    break;
                }
//                输出读取后,存放到字节数组中的内容
                for (int i = 0; i < n; i++) {
                    System.out.printf("%X\n",bytes[i]);
                }
                System.out.println("===================");
            }

        }
    }
}

运行结果:
在这里插入图片描述

代码的解释:
首先,我们还是先定义一个空白字节数组,大小为 1024个字节。

然后读取文件,使用带有三个参数的 read(bytes,0,3)方法,表示:把读取到的字节,存放到空白字节数组里面去,从字节数组的 0 下标,开始存放,同时 len 为 3,也就是,限制每一个读取的字节的数量为 3,哪怕你空白字节数组一次性能装下 1024 个字节,len 的大小,和 返回值 n 的大小是一样的,都是 3,从输出结果可以看出。

最后,当 n 等于 -1 时,表示文件已经读取完毕,结束while循环,整个java程序结束。

4. 读取文件的操作(三种 read方法)的简单总结

其实,文件内容操作,就那么几个步骤打开文件操作文件中的内容(读或者写),关闭文件

在实际开发过程当中,这三种read方法需要根据具体的情况来进行使用。

总结:

这篇博客,主要是讲解 使用字节流操作:InputStream类 读取文件的操作(三种 read 方法)。

看完这篇博客,请点击这个链接:Java 文件操作 和 IO(3)-- Java文件内容操作(1)-- 字节流操作

继续学习 OutputStream类 写数据的操作(三种 write 方法)。

最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值