聊聊FileInputStream文件输入流的二三事

客官,小板凳坐好,开始啦!
在这里插入图片描述
要聊FileInputStream对象,不妨先说说AutoCloseable吧,也算是个新成员,它诞生于JDK1.7的时代。

/**
 * @since 1.7
 */
public interface AutoCloseable {
    void close() throws Exception;
}

接口AutoCloseableJDK1.7版本开始的,它的作用是提供另一种资源关闭的方式。

自定义一个测试类,实现AutoCloseable接口:

public class TestAutoCloseable implements AutoCloseable {

    public void service() {
        System.out.println("TestAutoCloseable实例的service方法被调用");
    }

    @Override
    public void close() throws Exception {
        System.out.println("TestAutoCloseable实例的close方法由jvm自动调用");
    }
}

close()方法是重写的AutoCloseable接口。service()方法是TestAutoCloseable类中的一个普通成员方法。

public class Test {
    public static void main(String[] args) {
        try (
                TestAutoCloseable test1 = new TestAutoCloseable();
                TestAutoCloseable test2 = new TestAutoCloseable()
        ) {
            test1.service();
            test2.service();
        } catch (Exception exception) {
            System.out.println("发生异常");
        } finally {
            System.out.println("善后处理");
        }
    }
}

写一个测试类进行测试:

TestAutoCloseable实例的service方法被调用
TestAutoCloseable实例的service方法被调用
TestAutoCloseable实例的close方法由jvm自动调用
TestAutoCloseable实例的close方法由jvm自动调用
善后处理

还记得以前,你是如何创建一个FileInputStream对象的吗?

public class Test {
    public static void main(String[] args) {
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream("");
        } catch (FileNotFoundException e) {
            // TODO
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // TODO
                } finally {
                    // TODO
                }
            }
        }
    }
}

为了关闭FileInputStream资源,我们不得不在try-catch-finally块外部声明FileInputStream inputStream不然在finally中无法获取inputStream引用,我说的没错吧?

有了AutoCloseable接口,我们可以将资源对象的创建放入try后的(...)中,多个资源可以用分号;分隔。当try中的代码执行完后,由JVM自动调用重写的close()方法。这样资源就不用再手动的关闭了!

假设,在try代码块中发生了异常,如何处理呢?还能自动关闭吗?

public class Test {
    public static void main(String[] args) {
        try (
                TestAutoCloseable test1 = new TestAutoCloseable();
                TestAutoCloseable test2 = new TestAutoCloseable()
        ) {
            test1.service();
            int i = 1 / 0;
            test2.service();
        } catch (Exception exception) {
            System.out.println("发生异常");
        } finally {
            System.out.println("善后处理");
        }
    }
}

测试结果:

TestAutoCloseable实例的service方法被调用
TestAutoCloseable实例的close方法由jvm自动调用
TestAutoCloseable实例的close方法由jvm自动调用
发生异常
善后处理

从结果中,我们可以看出:

无论是否发生异常,一旦跳出try(...){代码块},那么jvm就会在跳出之前自动调用重写的close()方法。


好,接下来进入正题:聊聊FileInputStream文件输入流的二三事。

明白了AutoCloseable接口,那么你对FileInputStream对象应该也有了一点了解,因为FileInputStream对象实现了AutoCloseable接口,所以可以用新的资源关闭的方式。

FileInputStream有3个构造方法:

FileInputStream(String name);          // ①
FileInputStream(File file);            // ②
FileInputStream(FileDescriptor fdObj); // ③

其中,①、②两种构造器,其实是属于同一种构造器,都是通过传入一个File对象来构造的。第①种构造器,在FileInputStream内部会构造一个File对象的。

由于FileInputStream也实现了AutoCloseable接口,所以可以用try-catch新语法来管理资源的关闭。

public class Test {
    private static final String filePath = "C:\\Users\\Disney\\Desktop\\test.txt";
    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
          // TODO
        } catch (Exception exception) {
            System.out.println("发生IO异常");
        }
    }
}

以上代码就是FileInputStream对象创建的大概样子。

先介绍两个简单的API:int available()long skip(long n)

  • available

    可以从该输入流中读取(或跳过)而不阻塞的剩余字节数的估计。可以理解为剩余字节数统计。如果文件流刚建立还有开始读操作,那么调用available方法,返回值就是文件的字节大小。

public class Test {
    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            int typeCount = inputStream.available();
            System.out.println("typeCount = " + typeCount);
        } catch (Exception exception) {
            System.out.println("发生IO异常");
        }
    }
}
typeCount = 14
  • skip

    跳过指定的n个字节数。比如,当读到第20个字节,skip(10)之后,就开始从第31个字节开始读了。当然,文件要有31个字节以上,不然实际上有多少个字节就跳过多少个字节了。返回值就是实际跳过的字节数。如果long n是负数就是往前跳喽,如果流不支持往前跳就会抛出IO异常。

public class Test {
    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            int data = inputStream.read();
            System.out.println("data = " + data);
            data = inputStream.read();
            System.out.println("data = " + data);
            System.out.println("往前跳2个字节 = " + inputStream.skip(-2));
            data = inputStream.read();
            System.out.println("data = " + data);
            data = inputStream.read();
            System.out.println("data = " + data);
        } catch (Exception exception) {
            System.out.println("发生IO异常");
        }
    }
}
data = 65
data = 66
往前跳2个字节 = -2
data = 65
data = 66
  • markSupported

    由于FileInputStream并没有重写InputStreammarkSupported方法,而方法的默认返回值是false,所以也就不支持markreset方法。

public boolean markSupported() {
	return false;
}

输入流最重要的方法当然是read方法,FileInputStream有3中重载的read方法,我们先看第①种:

public class Test {
    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            int read;
            while ((read = inputStream.read()) != -1) {
                System.out.println(read);
            }
        } catch (Exception exception) {
            System.out.println("发生IO异常");
        }
    }
}

没有输入参数,返回值是读取的数据,即一个字节。如果返回值是-1,那么代表文件读取到了末尾位置。

为什么要用int作为返回值呢?用byte不就可以了吗?

原因是:字节输入流可以操作任意类型的文件,如图片、音频、视频等,这些文件底层都是二进制存储的;如果用byte作为返回值类型的话,当读取到11111111时,计算机就认为读到了-1.因为-1的原码是10000001,反码是11111110,补码是11111111。计算机存的就是补码11111111,所以一旦读到-1就结束了,但是事实上并没有结束哇!因为11111111是文件的数据,不是表示文件的结束。因此,改用int类型接收,前面再加3×8=24个0,写的时候write方法会去掉这24个0,就可以获取到-1这个数据了(如果看不明白,也无所谓,不影响使用)

接下来,我们看第②种read方法:

public class Test {
    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            byte[] container = new byte[4];
            int read;
            while ((read = inputStream.read(container)) != -1) {
                System.out.println("本次读取的字节数 = " + read);
                System.out.println(Arrays.toString(container));
            }
        } catch (Exception exception) {
            System.out.println("发生IO异常");
        }
    }
}

输入参数是一个字节数组,用于存放读取的数据。返回值是实际读取的字节数,当值为-1时,表示文件结束。

本次读取的字节数 = 4
[65, 66, 67, 68]
本次读取的字节数 = 4
[69, 70, 71, 72]
本次读取的字节数 = 4
[73, 74, 75, 76]
本次读取的字节数 = 2
[77, 78, 75, 76]

从运行结果中,可以看出:

每次读取我们定义的4个字节的数据,在最后一次读取时,仅剩2个字节了,所以字节数组container,仅前两个元素被修改了,最后的75, 76没有变动,因此,我们在读取数据时,一定要根据返回值int来判断实际读取了多少个字节。

接下来,我们再看最后一个read方法的使用:

public class Test {
    public static void main(String[] args) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            byte[] container = new byte[4];
            int len = inputStream.read(container, 2, 1);
            System.out.println("本次读取的字节数 = " + len);
            System.out.println(Arrays.toString(container));
        } catch (Exception exception) {
            System.out.println("发生IO异常");
        }
    }
}

运行结果:

本次读取的字节数 = 1
[0, 0, 65, 0]

可以看出:

第①个参数:依然是我们用于接收数据的容器。

第②个参数:指从容器的第几个位置开始存放数据,坐标从0开始。如果偏移量超过了数组最大索引值,抛异常。

第③个参数:每次读取多少个字节放入我们定义的容器container中。如果容器放不下,抛异常。

返回值:依然是,实际上读取的字节数量。

关于文件描述符相关的内容,暂不做解释,一般使用较少。

FileDescriptor fd = inputStream.getFD();
FileChannel channel = inputStream.getChannel();

以上就是关于FileInputStream的全部内容!不知客官欣赏的是否尽兴,评论区聊聊

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值