字节输入流

昨天我们学习了字节输出流的学习,而在字节流中,我们还剩下一个字节输入流。

所以今天我们准备对字节输入流进行一个详细的了解吧!

字节输入流的父类(InputStream)

InputStream,是所有字节输入流基类,是表示字节输入流的所有类的超类。主要作用是读取字节信息到内存中。它对整个体系的字节输入流制定了一些规范和方法。

1.先来看看他的体系结构

  • FileInputSream:文件输入流。它通常用于对文件进行读取操作。
  • FilterInputStream :装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。
  • BufferedInputStream:缓冲流,对处理流进行装饰,增强,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送。效率更高。
  • ObjectInputStream:对象输入流,用来提供对基本数据或对象的持久存储。也就是能直接传输对象(反序列化中使用)。
  • DataInputStream:数据输入流,它是用来装饰其它输入流。

2. InputStream中的方法

后续继承的子类都有这些方法,基本字节输入流都需要read()方法进行读取文件,使用close()方法关闭资源。

3. FileInputStream

FileInputStream的使用与FileOutputStream基本类似。

1. 构造方法

  • public FileInputStream(File file):通过打开与实际文件的连接来创建一个 FileInputStream,该文件由文件系统中的 File对象 File命名。
  • public FileInputStream(String name):通过打开与实际文件的连接来创建一个 FileInputStream,该文件由文件系统中的路径名 name命名。

这次我们来看下使用构造前的文件信息:

public class FileInputStreamTest {
    public static void main(String[] args) throws FileNotFoundException {
        //public FileInputStream(File file)
        //使用File对象创建流对象
        File file = new File("e:\\demo\\b.txt");
        FileInputStream fis = new FileInputStream(file);

        //public FileInputStream(String name)
        // 使用文件名称创建流对象
        FileInputStream fis2 = new FileInputStream("e:\\demo\\c.txt");
    }
}

执行程序,结果:

Exception in thread "main" java.io.FileNotFoundException: e:\demo\c.txt (系统找不到指定的文件。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at com.it.test3.FileInputStreamTest.main(FileInputStreamTest.java:16) 
    这里的16行错误即: FileInputStream fis2 = new FileInputStream("e:\\demo\\c.txt");

注意:

  • 与输出流不同的是,FileInputStream如果传入的路径是空的话,就会报错,而FileOutputStream则会创建文件。
  • FileInputStream创建出来输入流对象,是不会清空对象的。很好理解,输入流本来就是要读取文件信息创建出来的流,如果清空了,那我还读个啥对吧,读出一片寂寞吗。

2. 读取数据

既然我们要创建出字节输入流,不就是为了读取数据吗,不然创建出来有什么用呢?那我们看下怎么用吧:

  1. public int read():每次可以读取一个字节的数据,读出的类型为int类型,当读取到文件末尾即没有数据,返回-1

    public class ReadTest {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileInputStream fis = new FileInputStream("e:\\demo\\b.txt");
    
            //int read():一次读取一个字节
            int read = fis.read();
            System.out.println(read);
            System.out.println((char) read);
            
            fis.close();
        }
    }

    输出结果:

    104
    h

    我们b.txt中存储的是hello。根据输入流读取的结果返回的是int类型,那我们就需要强转成char字符类型,返回得到原来的文件信息。

    可是如果每次都是这样一个字节慢慢读取的话,是不是很麻烦,那这种重复度很高的话,就可以考虑循环优化它,可是我们现在不知道循环结束的条件是什么?那我们来测试下,读完后是什么结果呢?

    public class ReadTest2 {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileInputStream fis = new FileInputStream("e:\\demo\\b.txt");
    
            //int read():一次读取一个字节
            int read = fis.read();
            System.out.print(read);
            System.out.println(" : "+(char) read);
    
            read = fis.read();
            System.out.print(read);
            System.out.println(" : "+(char) read);
    
            read = fis.read();
            System.out.print(read);
            System.out.println(" : "+(char) read);
    
            read = fis.read();
            System.out.print(read);
            System.out.println(" : "+(char) read);
    
            read = fis.read();
            System.out.print(read);
            System.out.println(" : "+(char) read);
    
            //b.txt中,我们知道hello是5次就可以读完,那我们读第六次会怎样呢?
            read = fis.read();
            System.out.print(read);
            System.out.println(" : "+(char) read);
    
            fis.close();
        }
    }

    程序运行结果:

    104 : h
    101 : e
    108 : l
    108 : l
    111 : o
    -1 : 

    可以发现如果读到没有数据,最后读到的结果是-1,那我们是不是知道了循环结束的条件呢,如果没读到数据了就返回-1

    那我们使用循环改进:

    public class ReadTest3 {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            FileInputStream fis = new FileInputStream("e:\\demo\\b.txt");
            // 定义变量,保存数据
            int b;
            // 循环读取
            while ((b = fis.read())!=-1) {
                System.out.println((char)b);
            }
            // 关闭资源
            fis.close();
        }
    }

    程序运行结果:

    h
    e
    l
    l
    o

    使用输入流的有几点注意:

    • 虽然读取了一个字节,但是会自动提升为int类型。需要强转回字符。
    • 跟输出流一样,输入流操作完毕后,必须释放系统资源,调用close()方法
  2. public int read(byte b[]):每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1

    public class ReadTest4 {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建流对象
            // b.txt中信息为:
            FileInputStream fis = new FileInputStream("e:\\demo\\b.txt");
    
            //public int read(byte b[])
            byte[] b = new byte[3];
            int len = fis.read(b);
            System.out.println(len);// 可以看到读到是实际长度
            System.out.println(new String(b));
    
            len = fis.read(b);
            System.out.println(len);// 可以看到读到是实际长度
            System.out.println(new String(b));
    
            //到这么,已经读取完了,看看是不是和读取一个一样是读到-1。
            len = fis.read(b);
            System.out.println(len);// 可以看到读到是实际长度
    
            // 关闭资源
            fis.close();
        }
    }

    执行结果为:

    3
    hel
    2
    lol
    -1

    可以发现:

    • 如果读取到的实际长度是-1,就说明没有数据了。这就可以利用这个改进行循环优化了。

    • 而且我们知道System.out.println,每打印出字符串后,就会进行换行,则有可能对我们的获取产生错乱,因为可能我们读取的字符就在一行呢,所以我们也得进行更换为System.out.print

    • len获取到是实际的数据长度,第一次得到是3,获得了hel,而第二次获取的也就是2,可以看到确实不错,但按道理来说,我们得到不应该是lo吗,怎么会是lol呢,这是在提示我要打LOL了吗,嘿嘿!你看我是想喜欢打游戏吗?而且是像我这种经常拿五杀的人,并且经常CARRY的人,排位打到王者的男人。这里我战绩就不放出来了,怕吓到你们。而且我现在不打了,如果打下去,就可能是下一个UZI,theShy了。

      好了好了,会到重点,我们看下为什么最后会打出lol呢,而不是lo

    在知道了以上的步骤后,我们进行循环优化,并对之前的发生的问题进行修改。

    public class ReadTest5 {
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("e:\\demo\\b.txt");
    
            byte[] b = new byte[3];
            int len;
            while((len = fis.read(b)) != -1) {
                System.out.print(new String(b, 0, len));//如此便也可以返回实际的字符
            }
    
            // 关闭资源
            fis.close();
        }
    }
    
    执行程序结果为:
    hello

    可以看到,完美的在控制台上打印出了hello,并且不会每次读取,都会换行,完美复刻。

    小结下:

    在开发中,我们使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率。

字节流练习:图片复制

既然我们已经学习了字节的输出流和输入流,那何不妨来具体实战一下:

先来解释一波,复制的原理,就像电脑的右键复制一样:

复制前,先看看文件信息,免得你说我偷偷右键复制过去。

跟着我走,使用Java复制图片,你将会觉得很简单。So Easy

public class CopyTest {
    public static void main(String[] args) throws IOException {
        // 1. 创建输入和输出流对象。
        // 1.1 指定要读文件的路径。
        FileInputStream fis = new FileInputStream("e:\\demo\\北极星.jpg");
        // 1.2 指定目的地文件的路径。
        FileOutputStream fos = new FileOutputStream("e:\\demoCopy\\北极星.jpg");

        // 2.读写数据
        // 2.1 定义字节数组
        byte[] b = new byte[1024];
        int len;
        // 2.2 循环读取出数据
        while ((len = fis.read(b))!=-1) {
            // 2.3 将得到的字节数据写出
            fos.write(b, 0 , len);
        }

        // 3.关闭所有资源
        fos.close();
        fis.close();
    }
}

程序运行后,你将会看到很快就完成,再到文件查看下,发现复制好的北极星.jpg已经完整的躺好在哪里了。

大家要看下北极星小姐姐吗,不知道大家还记得北极星小姐姐吗,真的太可了,真的awsl,为此我多年前还专门刷完了天赋异禀整部剧,看到北极星和交食两人在一起产生极光,真的Interesting。极光是因为太阳带电粒子流进入地球磁场而产生的。 嘿嘿,好东西还是自己偷偷看吧,哈哈,还是默默躺在我文件夹中吧。

总结

相信各位看官都对IO流中字节输入流有了一定了解,期待等待下一章的字符流教学吧!

当然还有很多流等着下次一起看吧!欢迎期待下一章的到来!

学到这里,今天的世界打烊了,晚安!虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!

感谢各位看到这里!愿你韶华不负,青春无悔!

注: 如果文章有任何错误和建议,请各位大佬尽情留言!如果这篇文章对你也有所帮助,希望可爱亲切的您给个三连关注下,非常感谢啦!


作者:***爷哪吒
链接:https://juejin.cn/post/6993640831424921608
来源:掘金