夯实Java基础系列16:一文读懂Java IO流和常见面试题

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到个人仓库里查看html

https://github.com/h2pl/Java-Tutorial前端

喜欢的话麻烦点下Star哈java

文章首发于个人我的博客:python

www.how2playlife.comgit

本文是微信公众号【Java技术江湖】的《夯实Java基础系列博文》其中一篇,本文部份内容来源于网络,为了把本文主题讲得清晰透彻,也整合了不少我认为不错的技术博客内容,引用其中了一些比较好的博客文章,若有侵权,请联系做者。程序员

该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每一个Java知识点背后的实现原理,更完整地了解整个Java技术体系,造成本身的知识框架。为了更好地总结和检验你的学习成果,本系列文章也会提供部分知识点对应的面试题以及参考答案。github

若是对本系列文章有什么建议,或者是有什么疑问的话,也能够关注公众号【Java技术江湖】联系做者,欢迎你参与本系列博文的创做和修订。
面试

本文参考数据库

并发编程网 – ifeve.com编程

IO概述

在这一小节,我会试着给出Java IO(java.io)包下全部类的概述。更具体地说,我会根据类的用途对类进行分组。这个分组将会使你在将来的工做中,进行类的用途断定时,或者是为某个特定用途选择类时变得更加容易。

输入和输出

术语“输入”和“输出”有时候会有一点让人疑惑。一个应用程序的输入每每是另一个应用程序的输出

那么OutputStream流究竟是一个输出到目的地的流呢,仍是一个产生输出的流?InputStream流到底会不会输出它的数据给读取数据的程序呢?就我我的而言,在第一天学习Java IO的时候我就感受到了一丝疑惑。

为了消除这个疑惑,我试着给输入和输出起一些不同的别名,让它们从概念上与数据的来源和数据的流向相联系。

Java的IO包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。如下是最典型的数据源和目标媒介:

文件
管道
网络链接
内存缓存
System.in, System.out, System.error(注:Java标准输入、输出、错误输出)

下面这张图描绘了一个程序从数据源读取数据,而后将数据输出到其余媒介的原理:

[外链图片转存失败(img-VIwahDo3-1567839588993)(http://ifeve.com/wp-content/uploads/2014/10/%E6%97%A0%E6%A0%87%E9%A2%981.png)]

在Java IO中,流是一个核心的概念。流从概念上来讲是一个连续的数据流。你既能够从流中读取数据,也能够往流中写数据。流与数据源或者数据流向的媒介相关联。在Java IO中流既能够是字节流(以字节为单位进行读写),也能够是字符流(以字符为单位进行读写)。

类InputStream, OutputStream, Reader 和Writer
一个程序须要InputStream或者Reader从数据源读取数据,须要OutputStream或者Writer将数据写入到目标媒介中。如下的图说明了这一点:

[外链图片转存失败(img-dsCHgGDu-1567839588994)(http://ifeve.com/wp-content/uploads/2014/10/%E6%97%A0%E6%A0%87%E9%A2%982.png)]

InputStream和Reader与数据源相关联,OutputStream和writer与目标媒介相关联。

Java IO的用途和特征

Java IO中包含了许多InputStream、OutputStream、Reader、Writer的子类。这样设计的缘由是让每个类都负责不一样的功能。这也就是为何IO包中有这么多不一样的类的缘故。各种用途汇总以下:

文件访问
网络访问
内存缓存访问
线程内部通讯(管道)
缓冲
过滤
解析
读写文本 (Readers / Writers)
读写基本类型数据 (long, int etc.)
读写对象

当通读过Java IO类的源代码以后,咱们很容易就能了解这些用途。这些用途或多或少让咱们更加容易地理解,不一样的类用于针对不一样业务场景。

Java IO类概述表
已经讨论了数据源、目标媒介、输入、输出和各种不一样用途的Java IO类,接下来是一张经过输入、输出、基于字节或者字符、以及其余好比缓冲、解析之类的特定用途划分的大部分Java IO类的表格。

[外链图片转存失败(img-a3UYZ3ow-1567839588995)(http://ifeve.com/wp-content/uploads/2014/10/QQ%E6%88%AA%E5%9B%BE20141020174145.png)]

Java IO类图

[外链图片转存失败(img-n1TqKmEv-1567839588995)(https://images.cnblogs.com/cnblogs_com/davidgu/java_io_hierarchy.jpg)]

什么是Java IO流

Java IO流是既能够从中读取,也能够写入到其中的数据流。正如这个系列教程以前提到过的,流一般会与数据源、数据流向目的地相关联,好比文件、网络等等。

流和数组不同,不能经过索引读写数据。在流中,你也不能像数组那样先后移动读取数据,除非使用RandomAccessFile 处理文件。流仅仅只是一个连续的数据流。

某些相似PushbackInputStream 流的实现容许你将数据从新推回到流中,以便从新读取。然而你只能把有限的数据推回流中,而且你不能像操做数组那样随意读取数据。流中的数据只可以顺序访问。

Java IO流一般是基于字节或者基于字符的。字节流一般以“stream”命名,好比InputStream和OutputStream。除了DataInputStream 和DataOutputStream 还可以读写int, long, float和double类型的值之外,其余流在一个操做时间内只能读取或者写入一个原始字节。

字符流一般以“Reader”或者“Writer”命名。字符流可以读写字符(好比Latin1或者Unicode字符)。能够浏览Java Readers and Writers获取更多关于字符流输入输出的信息。

InputStream

java.io.InputStream类是全部Java IO输入流的基类。若是你正在开发一个从流中读取数据的组件,请尝试用InputStream替代任何它的子类(好比FileInputStream)进行开发。这么作可以让你的代码兼容任何类型而非某种肯定类型的输入流。

组合流

你能够将流整合起来以便实现更高级的输入和输出操做。好比,一次读取一个字节是很慢的,因此能够从磁盘中一次读取一大块数据,而后从读到的数据块中获取字节。为了实现缓冲,能够把InputStream包装到BufferedInputStream中。

代码示例
InputStream input = new BufferedInputStream(new FileInputStream("c:\data\input-file.txt"));

缓冲一样能够应用到OutputStream中。你能够实现将大块数据批量地写入到磁盘(或者相应的流)中,这个功能由BufferedOutputStream实现。

缓冲只是经过流整合实现的其中一个效果。你能够把InputStream包装到PushbackInputStream中,以后能够将读取过的数据推回到流中从新读取,在解析过程当中有时候这样作很方便。或者,你能够将两个InputStream整合成一个SequenceInputStream。

将不一样的流整合到一个链中,能够实现更多种高级操做。经过编写包装了标准流的类,能够实现你想要的效果和过滤器。

IO文件

在Java应用程序中,文件是一种经常使用的数据源或者存储数据的媒介。因此这一小节将会对Java中文件的使用作一个简短的概述。这篇文章不会对每个技术细节都作出解释,而是会针对文件存取的方法提供给你一些必要的知识点。在以后的文章中,将会更加详细地描述这些方法或者类,包括方法示例等等。

经过Java IO读文件

若是你须要在不一样端之间读取文件,你能够根据该文件是二进制文件仍是文本文件来选择使用FileInputStream或者FileReader。

这两个类容许你从文件开始到文件末尾一次读取一个字节或者字符,或者将读取到的字节写入到字节数组或者字符数组。你没必要一次性读取整个文件,相反你能够按顺序地读取文件中的字节和字符。

若是你须要跳跃式地读取文件其中的某些部分,可使用RandomAccessFile。

经过Java IO写文件

若是你须要在不一样端之间进行文件的写入,你能够根据你要写入的数据是二进制型数据仍是字符型数据选用FileOutputStream或者FileWriter。

你能够一次写入一个字节或者字符到文件中,也能够直接写入一个字节数组或者字符数据。数据按照写入的顺序存储在文件当中。

经过Java IO随机存取文件

正如我所提到的,你能够经过RandomAccessFile对文件进行随机存取。

随机存取并不意味着你能够在真正随机的位置进行读写操做,它只是意味着你能够跳过文件中某些部分进行操做,而且支持同时读写,不要求特定的存取顺序。

这使得RandomAccessFile能够覆盖一个文件的某些部分、或者追加内容到它的末尾、或者删除它的某些内容,固然它也能够从文件的任何位置开始读取文件。

下面是具体例子:

@Test
    //文件流范例,打开一个文件的输入流,读取到字节数组,再写入另外一个文件的输出流
    public void test1() {
        try {
            FileInputStream fileInputStream = new FileInputStream(new File("a.txt"));
            FileOutputStream fileOutputStream = new FileOutputStream(new File("b.txt"));
            byte []buffer = new byte[128];
            while (fileInputStream.read(buffer) != -1) {
                fileOutputStream.write(buffer);
            }
            //随机读写,经过mode参数来决定读或者写
            RandomAccessFile randomAccessFile = new RandomAccessFile(new File("c.txt"), "rw");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

字符流和字节流

Java IO的Reader和Writer除了基于字符以外,其余方面都与InputStream和OutputStream很是相似。他们被用于读写文本。InputStream和OutputStream是基于字节的,还记得吗?

Reader
Reader类是Java IO中全部Reader的基类。子类包括BufferedReader,PushbackReader,InputStreamReader,StringReader和其余Reader。

Writer
Writer类是Java IO中全部Writer的基类。子类包括BufferedWriter和PrintWriter等等。

这是一个简单的Java IO Reader的例子:

Reader reader = new FileReader("c:\\data\\myfile.txt");

int data = reader.read();

while(data != -1){

    char dataChar = (char) data;

    data = reader.read();

}

你一般会使用Reader的子类,而不会直接使用Reader。Reader的子类包括InputStreamReader,CharArrayReader,FileReader等等。能够查看Java IO概述浏览完整的Reader表格。

整合Reader与InputStream

一个Reader能够和一个InputStream相结合。若是你有一个InputStream输入流,而且想从其中读取字符,能够把这个InputStream包装到InputStreamReader中。把InputStream传递到InputStreamReader的构造函数中:

Reader reader = new InputStreamReader(inputStream);

在构造函数中能够指定解码方式。

Writer

Writer类是Java IO中全部Writer的基类。子类包括BufferedWriter和PrintWriter等等。这是一个Java IO Writer的例子:

Writer writer = new FileWriter("c:\\data\\file-output.txt"); 

writer.write("Hello World Writer"); 

writer.close();

一样,你最好使用Writer的子类,不须要直接使用Writer,由于子类的实现更加明确,更能表现你的意图。经常使用子类包括OutputStreamWriter,CharArrayWriter,FileWriter等。Writer的write(int c)方法,会将传入参数的低16位写入到Writer中,忽略高16位的数据。

整合Writer和OutputStream

与Reader和InputStream相似,一个Writer能够和一个OutputStream相结合。把OutputStream包装到OutputStreamWriter中,全部写入到OutputStreamWriter的字符都将会传递给OutputStream。这是一个OutputStreamWriter的例子:

Writer writer = new OutputStreamWriter(outputStream);

IO管道

Java IO中的管道为运行在同一个JVM中的两个线程提供了通讯的能力。因此管道也能够做为数据源以及目标媒介。

你不能利用管道与不一样的JVM中的线程通讯(不一样的进程)。在概念上,Java的管道不一样于Unix/Linux系统中的管道。在Unix/Linux中,运行在不一样地址空间的两个进程能够经过管道通讯。在Java中,通讯的双方应该是运行在同一进程中的不一样线程。

经过Java IO建立管道

能够经过Java IO中的PipedOutputStream和PipedInputStream建立管道。一个PipedInputStream流应该和一个PipedOutputStream流相关联。

一个线程经过PipedOutputStream写入的数据能够被另外一个线程经过相关联的PipedInputStream读取出来。

Java IO管道示例
这是一个如何将PipedInputStream和PipedOutputStream关联起来的简单例子:

//使用管道来完成两个线程间的数据点对点传递
    @Test
    public void test2() throws IOException {
        PipedInputStream pipedInputStream = new PipedInputStream();
        PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    pipedOutputStream.write("hello input".getBytes());
                    pipedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte []arr = new byte[128];
                    while (pipedInputStream.read(arr) != -1) {
                        System.out.println(Arrays.toString(arr));
                    }
                    pipedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

管道和线程
请记得,当使用两个相关联的管道流时,务必将它们分配给不一样的线程。read()方法和write()方法调用时会致使流阻塞,这意味着若是你尝试在一个线程中同时进行读和写,可能会致使线程死锁。

管道的替代
除了管道以外,一个JVM中不一样线程之间还有许多通讯的方式。实际上,线程在大多数状况下会传递完整的对象信息而非原始的字节数据。可是,若是你须要在线程之间传递字节数据,Java IO的管道是一个不错的选择。

Java IO:网络

Java中网络的内容或多或少的超出了Java IO的范畴。关于Java网络更多的是在个人Java网络教程中探讨。可是既然网络是一个常见的数据来源以及数据流目的地,而且由于你使用Java IO的API经过网络链接进行通讯,因此本文将简要的涉及网络应用。

当两个进程之间创建了网络链接以后,他们通讯的方式如同操做文件同样:利用InputStream读取数据,利用OutputStream写入数据。换句话来讲,Java网络API用来在不一样进程之间创建网络链接,而Java IO则用来在创建了链接以后的进程之间交换数据。

基本上意味着若是你有一份可以对文件进行写入某些数据的代码,那么这些数据也能够很容易地写入到网络链接中去。你所须要作的仅仅只是在代码中利用OutputStream替代FileOutputStream进行数据的写入。由于FileOutputStream是OuputStream的子类,因此这么作并无什么问题。

//从网络中读取字节流也能够直接使用OutputStream
public void test3() {
    //读取网络进程的输出流
    OutputStream outputStream = new OutputStream() {
        @Override
        public void write(int b) throws IOException {
        }
    };
}
public void process(OutputStream ouput) throws IOException {
    //处理网络信息
    //do something with the OutputStream
}

字节和字符数组

从InputStream或者Reader中读入数组

从OutputStream或者Writer中写数组

在java中经常使用字节和字符数组在应用中临时存储数据。而这些数组又是一般的数据读取来源或者写入目的地。若是你须要在程序运行时须要大量读取文件里的内容,那么你也能够把一个文件加载到数组中。

前面的例子中,字符数组或字节数组是用来缓存数据的临时存储空间,不过它们同时也能够做为数据来源或者写入目的地。
举个例子:

//字符数组和字节数组在io过程当中的做用
    public void test4() {
        //arr和brr分别做为数据源
        char []arr = {'a','c','d'};
        CharArrayReader charArrayReader = new CharArrayReader(arr);
        byte []brr = {1,2,3,4,5};
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(brr);
    }

System.in, System.out, System.err

System.in, System.out, System.err这3个流一样是常见的数据来源和数据流目的地。使用最多的多是在控制台程序里利用System.out将输出打印到控制台上。

JVM启动的时候经过Java运行时初始化这3个流,因此你不须要初始化它们(尽管你能够在运行时替换掉它们)。

System.in
System.in是一个典型的链接控制台程序和键盘输入的InputStream流。一般当数据经过命令行参数或者配置文件传递给命令行Java程序的时候,System.in并非很经常使用。图形界面程序经过界面传递参数给程序,这是一块单独的Java IO输入机制。

System.out
System.out是一个PrintStream流。System.out通常会把你写到其中的数据输出到控制台上。System.out一般仅用在相似命令行工具的控制台程序上。System.out也常常用于打印程序的调试信息(尽管它可能并非获取程序调试信息的最佳方式)。

System.err
System.err是一个PrintStream流。System.err与System.out的运行方式相似,但它更多的是用于打印错误文本。一些相似Eclipse的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式经过System.err输出到控制台上。

System.out和System.err的简单例子:
这是一个System.out和System.err结合使用的简单示例:

//测试System.in, System.out, System.err    
    public static void main(String[] args) {
        int in = new Scanner(System.in).nextInt();
        System.out.println(in);
        System.out.println("out");
        System.err.println("err");
        //输入10,结果是
//        err(红色)
//        10
//        out
    }

字符流的Buffered和Filter

BufferedReader能为字符输入流提供缓冲区,能够提升许多IO处理的速度。你能够一次读取一大块的数据,而不须要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲一般会让IO快上许多。

BufferedReader和BufferedInputStream的主要区别在于,BufferedReader操做字符,而BufferedInputStream操做原始字节。只须要把Reader包装到BufferedReader中,就能够为Reader添加缓冲区(译者注:默认缓冲区大小为8192字节,即8KB)。代码以下:

Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"));

你也能够经过传递构造函数的第二个参数,指定缓冲区大小,代码以下:

Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"), 8 * 1024);

这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。

除了可以为输入流提供缓冲区之外,其他方面BufferedReader基本与Reader相似。BufferedReader还有一个额外readLine()方法,能够方便地一次性读取一整行字符。

BufferedWriter

与BufferedReader相似,BufferedWriter能够为输出流提供缓冲区。能够构造一个使用默认大小缓冲区的BufferedWriter(译者注:默认缓冲区大小8 * 1024B),代码以下:

Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"));

也能够手动设置缓冲区大小,代码以下:

Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"), 8 * 1024);

为了更好地使用内置缓冲区的磁盘,一样建议把缓冲区大小设置成1024的整数倍。除了可以为输出流提供缓冲区之外,其他方面BufferedWriter基本与Writer相似。相似地,BufferedWriter也提供了writeLine()方法,可以把一行字符写入到底层的字符输出流中。

值得注意是,你须要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。

FilterReader

与FilterInputStream相似,FilterReader是实现自定义过滤输入字符流的基类,基本上它仅仅只是简单覆盖了Reader中的全部方法。

就我本身而言,我没发现这个类明显的用途。除了构造函数取一个Reader变量做为参数以外,我没看到FilterReader任何对Reader新增或者修改的地方。若是你选择继承FilterReader实现自定义的类,一样也能够直接继承自Reader从而避免额外的类层级结构。

JavaIO流面试题

什么是IO流?

它是一种数据的流从源头流到目的地。好比文件拷贝,输入流和输出流都包括了。输入流从文件中读取数据存储到进程(process)中,输出流从进程中读取数据而后写入到目标文件。

字节流和字符流的区别。

字节流在JDK1.0中就被引进了,用于操做包含ASCII字符的文件。JAVA也支持其余的字符如Unicode,为了读取包含Unicode字符的文件,JAVA语言设计者在JDK1.1中引入了字符流。ASCII做为Unicode的子集,对于英语字符的文件,能够可使用字节流也可使用字符流。

Java中流类的超类主要由那些?

java.io.InputStream
java.io.OutputStream
java.io.Reader
java.io.Writer

FileInputStream和FileOutputStream是什么?

这是在拷贝文件操做的时候,常常用到的两个类。在处理小文件的时候,它们性能表现还不错,在大文件的时候,最好使用BufferedInputStream (或 BufferedReader) 和 BufferedOutputStream (或 BufferedWriter)

System.out.println()是什么?

println是PrintStream的一个方法。out是一个静态PrintStream类型的成员变量,System是一个java.lang包中的类,用于和底层的操做系统进行交互。

什么是Filter流?

Filter Stream是一种IO流主要做用是用来对存在的流增长一些额外的功能,像给目标文件增长源文件中不存在的行数,或者增长拷贝的性能。

有哪些可用的Filter流?

在java.io包中主要由4个可用的filter Stream。两个字节filter stream,两个字符filter stream. 分别是FilterInputStream, FilterOutputStream, FilterReader and FilterWriter.这些类是抽象类,不能被实例化的。

在文件拷贝的时候,那一种流可用提高更多的性能?

在字节流的时候,使用BufferedInputStream和BufferedOutputStream。
在字符流的时候,使用BufferedReader 和 BufferedWriter

说说管道流(Piped Stream)

有四种管道流, PipedInputStream, PipedOutputStream, PipedReader 和 PipedWriter.在多个线程或进程中传递数据的时候管道流很是有用。

说说File类

它不属于 IO流,也不是用于文件操做的,它主要用于知道一个文件的属性,读写权限,大小等信息。

说说RandomAccessFile?

它在java.io包中是一个特殊的类,既不是输入流也不是输出流,它二者均可以作到。他是Object的直接子类。一般来讲,一个流只有一个功能,要么读,要么写。可是RandomAccessFile既能够读文件,也能够写文件。 DataInputStream 和 DataOutStream有的方法,在RandomAccessFile中都存在。

参考文章

https://www.imooc.com/article/24305

https://www.cnblogs.com/UncleWang001/articles/10454685.html

https://www.cnblogs.com/Jixiangwei/p/Java.html

https://blog.csdn.net/baidu_37107022/article/details/76890019

微信公众号

我的公众号:程序员黄小斜


黄小斜是 985 硕士,阿里巴巴Java工程师,在自学编程、技术求职、Java学习等方面有丰富经验和独到看法,但愿帮助到更多想要从事互联网行业的程序员们。

做者专一于 JAVA 后端技术栈,热衷于分享程序员干货、学习经验、求职心得,以及自学编程和Java技术栈的相关干货。

黄小斜是一个斜杠青年,坚持学习和写做,相信终身学习的力量,但愿和更多的程序员交朋友,一块儿进步和成长!

原创电子书:
关注微信公众号【程序员黄小斜】后回复【原创电子书】便可领取我原创的电子书《菜鸟程序员修炼手册:从技术小白到阿里巴巴Java工程师》这份电子书总结了我2年的Java学习之路,包括学习方法、技术总结、求职经验和面试技巧等内容,已经帮助不少的程序员拿到了心仪的offer!

程序员3T技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 便可免费无套路获取,包括Java、python、C++、大数据、机器学习、前端、移动端等方向的技术资料。同时也包括我原创的【程序员校招大礼包】、【求职面试大礼包】等内容赠送,都是各位求职或者面试路上小伙伴很是实用的内容。

技术公众号:Java技术江湖

若是你们想要实时关注我更新的文章以及分享的干货的话,能够关注个人微信公众号【Java技术江湖】

这是一位阿里 Java 工程师的技术小站。做者黄小斜,专一 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!

Java工程师必备学习资源: 关注公众号【Java技术江湖】后回复 Java 便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源

个人公众号

相关文章
相关标签/搜索