前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅析FileInputStream#read方法

浅析FileInputStream#read方法

作者头像
书唐瑞
发布2022-11-14 09:21:39
3180
发布2022-11-14 09:21:39
举报
文章被收录于专栏:Netty历险记Netty历险记

本篇文章使用的JVM源码版本是jdk8-b116

在FileInputStream.java文件中,有4个和读操作相关的方法,其中有2个方法属于native方法.

代码语言:javascript
复制

public native int read() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;

public int read(byte b[]) throws IOException {
    return readBytes(b, 0, b.length);
}

public int read(byte b[], int off, int len) throws IOException {
    return readBytes(b, off, len);
}

首先看下native read()方法.

它的实现在jdk/src/share/native/java/io/FileInputStream.c

它内部调用readSingle方法.

readSingle实现在jdk/src/share/native/java/io/io_util.c

readSingle内部调用了IO_Read方法, 关注下第三个参数值1

在C程序里, 如果读取到文件末尾,那么read系统调用会返回0,从上面的JVM源码可以看到, Java做了一个中转, 如果读取到文件末尾, 返回的是-1, 并不是0, 这也是为什么在我们的程序中要根据-1来判断是否读取到文件末尾的原因, 而C程序是根据返回值等于0判断读取到文件末尾. 我们可以通过 man 2 read 查看手册瞧一瞧

在jdk/src/solaris/native/java/io/io_util_md.h头文件中定义了IO_Read即handleRead

继续查看handleRead方法.

handleRead实现在jdk/src/solaris/native/java/io/io_util_md.c

handleRead内部调用了read这个系统调用. 入参len值就是从上面传递下来的1.

也就是说, 在我们的Java程序中调用FileInputStream的read()方法, JVM会向操作系统读取1个字节数据.

而另一个native readBytes(byte b[], int off, int len)入参可以指定一个字节数组,指定一次性读取多少字节. 它的内部和上面说的native read()一样, 区别在调用系统调用read方法时传的len参数值是程序员设置的len值.

接下来做个实验

代码语言:javascript
复制

package com.infuq;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ReadFile {

    public static void main(String[] args) throws IOException {
        final String path = "/home/v-infuq/tmp/1.txt";
        File file = new File(path);
        FileInputStream fileInputStream = new FileInputStream(file);
        System.out.println("begin read 1.txt");
        
        int d = fileInputStream.read();
        System.out.println("" + d);
        d = fileInputStream.read();
        System.out.println("" + d);
        d = fileInputStream.read();
        System.out.println("" + d);
        d = fileInputStream.read();
        System.out.println("" + d);
        d = fileInputStream.read();
        System.out.println("" + d);

        fileInputStream.close();

    }
}

在1.txt文件中写入的是infuq

通过strace命令可以追踪程序的系统调用.

strace命令运行完成之后, 会生成很多out.*文件, 由于在ReadFile.java程序中输出了'begin read 1.txt', 使用grep命令搜索这个关键字在哪个out文件中, 它在out.2227文件中, 也就是说main线程打印的系统调用信息都在out.2227文件里. 如果大家对JVM启动稍微熟悉点的话, 可以直接断定第二个out文件一定就是main线程对应的文件. 因为main线程是JVM启动过程中的第二个线程.

通过查看out.2227文件内容,如下

由于程序调用了5次read方法, 因此在out.2227文件中打印的系统调用read也是5次, 且每次只读取1个字节.

改写一下程序

代码语言:javascript
复制

package com.infuq;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ReadFile {

    public static void main(String[] args) throws IOException {
        final String path = "/home/v-infuq/tmp/1.txt";
        File file = new File(path);
        FileInputStream fileInputStream = new FileInputStream(file);
        System.out.println("begin read 1.txt");
        
        byte[] buf = new byte[8];
        int length = 0;
        while((length = fileInputStream.read(buf)) != -1){
            System.out.print(new String(buf,0,length));
        }
        fileInputStream.close();

    }
}

如上, 执行相同的操作, 查看out.2676文件

它只调用了一次read方法就把数据(infuq)读取完成了.

总结

直接调用read()方法每次只能读取1个字节. 可以通过read(byte[])或read(byte[],int,int)一次读取多个字节. 这样的话, 读取磁盘上相同的文件, 后者比前者减少了系统调用次数, 也就提高了读取效率. 这也是为什么BufferedInputStream比FileInputStream读取效率高的原因, 因为在BufferedInputStream内部有一个默认8192大小的byte[].

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Netty历险记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档