# IO 流

# 一、Java IO 分类

# (1) 按操作方式分类结构图:

IO-操作方式分类

字节流和字符流:

  • 字节流:以字节为单位,每次次读入或读出是 8 位数据。可以读任何类型数据。
  • 字符流:以字符为单位,每次次读入或读出是 16 位数据。其只能读取字符类型数据。

输出流和输入流:

  • 输出流:从内存读出到文件。只能进行写操作。
  • 输入流:从文件读入到内存。只能进行读操作。

注意: 这里的出和入,都是相对于系统内存而言的。

节点流和处理流:

  • 节点流:直接与数据源相连,读入或读出。
  • 处理流:与节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。

**为什么要有处理流?**直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。

分类说明:

1) 输入字节流 InputStream

  • ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从 Byte 数组、StringBuffer、和本地文件中读取数据。
  • PipedInputStream 是从与其它线程共用的管道中读取数据。PipedInputStream 的一个实例要和 PipedOutputStream 的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。
  • DataInputStream: 将基础数据类型读取出来
  • ObjectInputStream 和所有 FilterInputStream 的子类都是装饰流(装饰器模式的主角)。

2)输出字节流 OutputStream:

  • ByteArrayOutputStreamFileOutputStream: 是两种基本的介质流,它们分别向- Byte 数组、和本地文件中写入数据。
  • PipedOutputStream 是向与其它线程共用的管道中写入数据。
  • DataOutputStream 将基础数据类型写入到文件中
  • ObjectOutputStream 和所有 FilterOutputStream 的子类都是装饰流。

节流的输入和输出类结构图:

节流的输入和输出类结构图

3)字符输入流 Reader::

  • FileReader、CharReader、StringReader 是三种基本的介质流,它们分在本地文件、Char 数组、String 中读取数据。
  • PipedReader:是从与其它线程共用的管道中读取数据
  • BufferedReader :加缓冲功能,避免频繁读写硬盘
  • InputStreamReader: 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。

4)字符输出流 Writer:

  • StringWriter:向 String 中写入数据。

  • CharArrayWriter:实现一个可用作字符输入流的字符缓冲区

  • PipedWriter:是向与其它线程共用的管道中写入数据

  • BufferedWriter : 增加缓冲功能,避免频繁读写硬盘。

  • PrintWriterPrintStream 将对象的格式表示打印到文本输出流。 极其类似,功能和使用也非常相似

  • OutputStreamWriter: 是 OutputStream 到 Writer 转换的桥梁,它的子类 FileWriter 其实就是一个实现此功能的具体类(具体可以研究一 SourceCode)。功能和使用和 OutputStream 极其类似,后面会有它们的对应图。

    字符流的输入和输出类结构图:

    img

# (2)按操作对象分类结构图**

IO-操作对象分类

分类说明

对文件进行操作(节点流):

  • FileInputStream(字节输入流),
  • FileOutputStream(字节输出流),
  • FileReader(字符输入流),
  • FileWriter(字符输出流)

对管道进行操作(节点流):

  • PipedInputStream(字节输入流),
  • PipedOutStream(字节输出流),
  • PipedReader(字符输入流),
  • PipedWriter(字符输出流)。
  • PipedInputStream 的一个实例要和 PipedOutputStream 的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。

字节/字符数组流(节点流):

  • ByteArrayInputStream,
  • ByteArrayOutputStream,
  • CharArrayReader,
  • CharArrayWriter;

除了上述三种是节点流,其他都是处理流,需要跟节点流配合使用。

**Buffered 缓冲流(处理流):**带缓冲区的处理流,缓冲区的作用的主要目的是:避免每次和硬盘打交道,提高数据访问的效率。

  • BufferedInputStream,
  • BufferedOutputStream,
  • BufferedReader,
  • BufferedWriter,

转化流(处理流):

  • InputStreamReader:把字节转化成字符;
  • OutputStreamWriter:把字节转化成字符。

基本类型数据流(处理流):用于操作基本数据类型值。

因为平时若是我们输出一个 8 个字节的 long 类型或 4 个字节的 float 类型,那怎么办呢?可以一个字节一个字节输出,也可以把转换成字符串输出,但是这样转换费时间,若是直接输出该多好啊,因此这个数据流就解决了我们输出数据类型的困难。数据流可以直接输出 float 类型或 long 类型,提高了数据读写的效率。

  • DataInputStream,
  • DataOutputStream。

打印流(处理流):

一般是打印到控制台,可以进行控制打印的地方。

  • PrintStream,
  • PrintWriter,

对象流(处理流):

把封装的对象直接输出,而不是一个个在转换成字符串再输出。

  • ObjectInputStream,对象反序列化;
  • ObjectOutputStream,对象序列化;

合并流(处理流):

  • SequenceInputStream:可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。

# 其他类:File(已经被 Java7 的 Path 取代)

File 类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File 类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。

# 其他类:RandomAccessFile

该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点:

  1. 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File 对象。
  2. 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)。

注意: IO 中的很多内容都可以使用 NIO 完成,这些知识点大家知道就好,使用的话还是尽量使用 NIO/AIO。

# javaIO 流体系中常用的流的分类表

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

# java IO 体系的学习总结

# IO 流的分类:

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

# 流的原理浅析:

java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

# 常用的 io 流的用法

下面是整理的常用的 Io 流的特性及使用方法,只有清楚每个 Io 流的特性和方法。才能在不同的需求面前正确的选择对应的 IO 流进行开发。

# Io 体系的基类(InputStream/Reader,OutputStream/Writer)

字节流和字符流的操作方式基本一致,只是操作的数据单元不同——字节流的操作单元是字节,字符流的操作单元是字符。所以字节流和字符流就整理在一起了。

InputStream 和 Reader 是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。 在 InputStream 里面包含如下 3 个方法。

  • int read(); 从输入流中读取单个字节(相当于从图 15.5 所示的水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为 int 类型)。
  • int read(byte[] b)从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数。
  • int read(byte[] b,int off,int len); 从输入流中最多读取 len 个字节的数据,并将其存储在数组 b 中,放入数组 b 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字节数。

在 Reader 中包含如下 3 个方法。

  • int read(); 从输入流中读取单个字符(相当于从图 15.5 所示的水管中取出一滴水),返回所读取的字符数据(字节数据可直接转换为 int 类型)。
  • int read(char[] b)从输入流中最多读取 b.length 个字符的数据,并将其存储在字节数组 b 中,返回实际读取的字符数。
  • int read(char[] b,int off,int len); 从输入流中最多读取 len 个字符的数据,并将其存储在数组 b 中,放入数组 b 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字符数。

对比 InputStream 和 Reader 所提供的方法,就不难发现这两个基类的功能基本是一样的。InputStream 和 Reader 都是将输入数据抽象成如图 15.5 所示的水管,所以程序即可以通过 read()方法每次读取一个”水滴“,也可以通过 read(char[] chuf)或者 read(byte[] b)方法来读取多个“水滴”。当使用数组作为 read()方法中的参数, 我们可以理解为使用一个“竹筒”到如图 15.5 所示的水管中取水,如图 15.8 所示 read(char[] cbuf)方法的参数可以理解成一个”竹筒“,程序每次调用输入流 read(char[] cbuf)或 read(byte[] b)方法,就相当于用“竹筒”从输入流中取出一筒“水滴”,程序得到“竹筒”里面的”水滴“后,转换成相应的数据即可;程序多次重复这个“取水”过程,直到最后。程序如何判断取水取到了最后呢?直到 read(char[] chuf)或者 read(byte[] b)方法返回-1,即表明到了输入流的结束点。

img

InputStream 和 Reader 提供的一些移动指针的方法:

  • void mark(int readAheadLimit); 在记录指针当前位置记录一个标记(mark)。
  • boolean markSupported(); 判断此输入流是否支持 mark()操作,即是否支持记录标记。
  • void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
  • long skip(long n); 记录指针向前移动 n 个字节/字符。

OutputStream 和 Writer:

OutputStream 和 Writer 的用法也非常相似,它们采用如图 15.6 所示的模型来执行输入,两个流都提供了如下三个方法:

  • void write(int c); 将指定的字节/字符输出到输出流中,其中 c 即可以代表字节,也可以代表字符。
  • void write(byte[]/char[] buf); 将字节数组/字符数组中的数据输出到指定输出流中。
  • void write(byte[]/char[] buf, int off,int len ); 将字节数组/字符数组中从 off 位置开始,长度为 len 的字节/字符输出到输出流中。

因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来代替字符数组,即以 String 对象作为参数。Writer 里面还包含如下两个方法。

  • void write(String str); 将 str 字符串里包含的字符输出到指定输出流中。
  • void write (String str, int off, int len); 将 str 字符串里面从 off 位置开始,长度为 len 的字符输出到指定输出流中。

# Io 体系的基类文件流的使用(FileInputStream/FileReader ,FileOutputStream/FileWriter)

前面说过 InputStream 和 Reader 都是抽象类,本身不能创建实例,但它们分别有一个用于读取文件的输入流:FileInputStream 和 FileReader,它们都是节点流——会直接和指定文件关联。下面程序示范使用 FileInputStream 和 FileReader。

使用 FileInputStream 读取文件:

public class MyClass {
  public  static void main(String[] args)throws IOException{
      FileInputStream fis=null;
      try {
          //创建字节输入流
          fis=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
          //创建一个长度为1024的竹筒
          byte[] b=new byte[1024];
          //用于保存的实际字节数
          int hasRead=0;
          //使用循环来重复取水的过程
          while((hasRead=fis.read(b))>0){
              //取出竹筒中的水滴(字节),将字节数组转换成字符串进行输出
            System.out.print(new String(b,0,hasRead));
          }
      }catch (IOException e){
        e.printStackTrace();
      }finally {
          fis.close();
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

注:上面程序最后使用了 fis.close()来关闭该文件的输入流,与 JDBC 编程一样,程序里面打开的文件 IO 资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显示的关闭打开的 IO 资源。Java 7 改写了所有的 IO 资源类,它们都实现了 AntoCloseable 接口,因此都可以通过自动关闭资源的 try 语句来关闭这些 Io 流。

使用 FileReader 读取文件:

public class FileReaderTest {
    public  static void main(String[] args)throws IOException{
        FileReader fis=null;
        try {
            //创建字节输入流
            fis=new FileReader("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
            //创建一个长度为1024的竹筒
            char[] b=new char[1024];
            //用于保存的实际字节数
            int hasRead=0;
            //使用循环来重复取水的过程
            while((hasRead=fis.read(b))>0){
                //取出竹筒中的水滴(字节),将字节数组转换成字符串进行输出
                System.out.print(new String(b,0,hasRead));
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            fis.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

可以看出使用 FileInputStream 和 FileReader 进行文件的读写并没有什么区别,只是操作单元不同而且。

FileOutputStream/FileWriter 是 Io 中的文件输出流,下面介绍这两个类的用法。

FileOutputStream 的用法:

public class FileOutputStreamTest {
    public  static void main(String[] args)throws IOException {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try {
            //创建字节输入流
            fis=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
            //创建字节输出流
            fos=new FileOutputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\newTest.txt");

            byte[] b=new byte[1024];
            int hasRead=0;

            //循环从输入流中取出数据
            while((hasRead=fis.read(b))>0){
                //每读取一次,即写入文件输入流,读了多少,就写多少。
                fos.write(b,0,hasRead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            fis.close();
            fos.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

运行程序可以看到输出流指定的目录下多了一个文件:newTest.txt, 该文件的内容和 Test.txt 文件的内容完全相同。FileWriter 的使用方式和 FileOutputStream 基本类似,这里就带过。

注: 使用 java 的 io 流执行输出时,不要忘记关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,可能还可以将输出流缓冲区中的数据 flush 到物理节点中里(因为在执行 close()方法之前,自动执行输出流的 flush()方法)。java 很多输出流默认都提供了缓存功能,其实我们没有必要刻意去记忆哪些流有缓存功能,哪些流没有,只有正常关闭所有的输出流即可保证程序正常。

缓冲流的使用(BufferedInputStream/BufferedReader, BufferedOutputStream/BufferedWriter):

下面介绍字节缓存流的用法(字符缓存流的用法和字节缓存流一致就不介绍了):

public class BufferedStreamTest {
    public  static void main(String[] args)throws IOException {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        BufferedInputStream bis=null;
        BufferedOutputStream bos=null;
        try {
            //创建字节输入流
            fis=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
            //创建字节输出流
            fos=new FileOutputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\newTest.txt");
            //创建字节缓存输入流
            bis=new BufferedInputStream(fis);
            //创建字节缓存输出流
            bos=new BufferedOutputStream(fos);

            byte[] b=new byte[1024];
            int hasRead=0;
            //循环从缓存流中读取数据
            while((hasRead=bis.read(b))>0){
                //向缓存流中写入数据,读取多少写入多少
                bos.write(b,0,hasRead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            bis.close();
            bos.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

可以看到使用字节缓存流读取和写入数据的方式和文件流(FileInputStream,FileOutputStream)并没有什么不同,只是把处理流套接到文件流上进行读写。缓存流的原理下节介绍。

上面代码中我们使用了缓存流和文件流,但是我们只关闭了缓存流。这个需要注意一下,当我们使用处理流套接到节点流上的使用的时候,只需要关闭最上层的处理就可以了。java 会自动帮我们关闭下层的节点流。

# 转换流的使用(InputStreamReader/OutputStreamWriter):

下面以获取键盘输入为例来介绍转换流的用法。java 使用 System.in 代表输入。即键盘输入,但这个标准输入流是 InputStream 类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用 InputStreamReader 将其包装成 BufferedReader,利用 BufferedReader 的 readLine()方法可以一次读取一行内容,如下代码所示:

public class InputStreamReaderTest {
    public  static void main(String[] args)throws IOException {
        try {
            // 将System.in对象转化为Reader对象
            InputStreamReader reader=new InputStreamReader(System.in);
            //将普通的Reader包装成BufferedReader
            BufferedReader bufferedReader=new BufferedReader(reader);
           String buffer=null;
           while ((buffer=bufferedReader.readLine())!=null){
            // 如果读取到的字符串为“exit”,则程序退出
               if(buffer.equals("exit")){
                   System.exit(1);
               }
               //打印读取的内容
               System.out.print("输入内容:"+buffer);
           }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

上面程序将 System.in 包装成 BufferedReader,BufferedReader 流具有缓存功能,它可以一次读取一行文本——以换行符为标志,如果它没有读到换行符,则程序堵塞。等到读到换行符为止。运行上面程序可以发现这个特征,当我们在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容。

# 对象流的使用(ObjectInputStream/ObjectOutputStream)的使用:

写入对象:

public static void writeObject(){
        OutputStream outputStream=null;
        BufferedOutputStream buf=null;
        ObjectOutputStream obj=null;
        try {
            //序列化文件輸出流
            outputStream=new FileOutputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\myfile.tmp");
            //构建缓冲流
            buf=new BufferedOutputStream(outputStream);
            //构建字符输出的对象流
            obj=new ObjectOutputStream(buf);
            //序列化数据写入
            obj.writeObject(new Person("A", 21));//Person对象
            //关闭流
            obj.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

读取对象:

public static void readObject() throws IOException {
        try {
            InputStream inputStream=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\myfile.tmp");
            //构建缓冲流
            BufferedInputStream buf=new BufferedInputStream(inputStream);
            //构建字符输入的对象流
            ObjectInputStream obj=new ObjectInputStream(buf);
            Person tempPerson=(Person)obj.readObject();
            System.out.println("Person对象为:"+tempPerson);
            //关闭流
            obj.close();
            buf.close();
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

使用对象流的一些注意事项 1.读取顺序和写入顺序一定要一致,不然会读取出错。 2.在对象属性前面加 transient 关键字,则该对象的属性不会被序列化。

# 二、Java IO 面试题

1. 什么是 IO 流?

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

2. 字节流和字符流的区别。

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

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

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

4. FileInputStream 和 FileOutputStream 是什么?

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

5. 字节流和字符流,你更喜欢使用拿一个?

个人来说,更喜欢使用字符流,因为他们更新一些。许多在字符流中存在的特性,字节流中不存在。比如使用 BufferedReader 而不是 BufferedInputStreams 或 DataInputStream,使用 newLine()方法来读取下一行,但是在字节流中我们需要做额外的操作。

6.System.out.println()是什么?

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

7.什么是 Filter 流?

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

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

有些 Filter 流的子类:

  • LineNumberInputStream 给目标文件增加行号
  • DataInputStream 有些特殊的方法如readInt(), readDouble()readLine() 等可以读取一个 int, double 和一个 string 一次性的,
  • BufferedInputStream 增加性能
  • PushbackInputStream 推送要求的字节到系统中

9.SequenceInputStream 的作用? 这个类的作用是将多个输入流合并成一个输入流,通过 SequenceInputStream 类包装后形成新的一个总的输入流。在拷贝多个文件到一个目标文件的时候是非常有用的。可用使用很少的代码实现

10.说说 PrintStream 和 PrintWriter

他们两个的功能相同,但是属于不同的分类。字节流和字符流。他们都有 println()方法。

11. 在文件拷贝的时候,那一种流可用提升更多的性能? 在字节流的时候,使用 BufferedInputStream 和 BufferedOutputStream。 在字符流的时候,使用 BufferedReader 和 BufferedWriter

12 .说说管道流(Piped Stream)

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

13.说说 File 类 它不属于 IO 流,也不是用于文件操作的,它主要用于知道一个文件的属性,读写权限,大小等信息。注意:Java7 中文件 IO 发生了很大的变化,专门引入了很多新的类来取代原来的基于 java.io.File 的文件 IO 操作方式。

14. 说说 RandomAccessFile?

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

# NIO

# 一 Java NIO 概览 (opens new window)

  1. NIO 简介:

    Java NIO 是 java 1.4, 之后新出的一套 IO 接口 NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。

    它支持面向缓冲的,基于通道的 I/O 操作方法。随着 JDK 7 的推出,NIO 系统得到了扩展,为文件系统功能和文件处理提供了增强的支持。 由于 NIO 文件类支持的这些新的功能,NIO 被广泛应用于文件处理。

  2. NIO 的特性/NIO 与 IO 区别:

    • 1)IO 是面向流的,NIO 是面向缓冲区的;
    • 2)IO 流是阻塞的,NIO 流是不阻塞的;
    • 3)NIO 有选择器,而 IO 没有。
  3. 读数据和写数据方式:

    • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。

    • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

  4. NIO 核心组件简单介绍

    • Channels(通道)
    • Buffers(缓冲区)
    • Selectors(选择器)

# 二 Java NIO 之 Buffer(缓冲区) (opens new window)

  1. Buffer(缓冲区)介绍:

    • Java NIO Buffers 用于和 NIO Channel 交互。 我们从 Channel 中读取数据到 buffers 里,从 Buffer 把数据写入到 Channels;
    • Buffer 本质上就是一块内存区;
    • 一个 Buffer 有三个属性是必须掌握的,分别是:capacity 容量、position 位置、limit 限制。
  2. Buffer 的常见方法

    • Buffer clear()
    • Buffer flip()
    • Buffer rewind()
    • Buffer position(int newPosition)
  3. Buffer 的使用方式/方法介绍:

    • 分配缓冲区(Allocating a Buffer):
    ByteBuffer buf = ByteBuffer.allocate(28);//以ByteBuffer为例子
    
    1
    • 写入数据到缓冲区(Writing Data to a Buffer)

      写数据到 Buffer 有两种方法:

      1.从 Channel 中写数据到 Buffer

      int bytesRead = inChannel.read(buf); //read into buffer.
      
      1

      2.通过 put 写数据:

      buf.put(127);
      
      1
  4. Buffer 常用方法测试

    说实话,NIO 编程真的难,通过后面这个测试例子,你可能才能勉强理解前面说的 Buffer 方法的作用。

# 三 Java NIO 之 Channel(通道) (opens new window)

  1. Channel(通道)介绍
    • 通常来说 NIO 中的所有 IO 都是从 Channel(通道) 开始的。
    • NIO Channel 通道和流的区别:
  2. FileChannel 的使用
  3. SocketChannel 和 ServerSocketChannel 的使用
  4. ️DatagramChannel 的使用
  5. Scatter / Gather
    • Scatter: 从一个 Channel 读取的信息分散到 N 个缓冲区中(Buufer).
    • Gather: 将 N 个 Buffer 里面内容按照顺序发送到一个 Channel.
  6. 通道之间的数据传输
    • 在 Java NIO 中如果一个 channel 是 FileChannel 类型的,那么他可以直接把数据传输到另一个 channel。
    • transferFrom() :transferFrom 方法把数据从通道源传输到 FileChannel
    • transferTo() :transferTo 方法把 FileChannel 数据传输到另一个 channel

# 四 Java NIO 之 Selector(选择器) (opens new window)

  1. Selector(选择器)介绍

    • Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是 Java NIO 核心组件中的一个,用于检查一个或多个 NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个 channels,也就是可以管理多个网络链接。
    • 使用 Selector 的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
  2. Selector(选择器)的使用方法介绍

    • Selector 的创建
    Selector selector = Selector.open();
    
    1
    • 注册 Channel 到 Selector(Channel 必须是非阻塞的)
    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
    
    1
    2
    • SelectionKey 介绍

      一个 SelectionKey 键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。

    • 从 Selector 中选择 channel(Selecting Channels via a Selector)

      选择器维护注册过的通道的集合,并且这种注册关系都被封装在 SelectionKey 当中.

    • 停止选择的方法

      wakeup()方法 和 close()方法。

  3. 模板代码

    有了模板代码我们在编写程序时,大多数时间都是在模板代码中添加相应的业务代码。

  4. 客户端与服务端简单交互实例

# 五 Java NIO 之拥抱 Path 和 Files (opens new window)

一 文件 I/O 基石:Path:

  • 创建一个 Path
  • File 和 Path 之间的转换,File 和 URI 之间的转换
  • 获取 Path 的相关信息
  • 移除 Path 中的冗余项

二 拥抱 Files 类:

  • Files.exists() 检测文件路径是否存在
  • Files.createFile() 创建文件
  • Files.createDirectories()和 Files.createDirectory()创建文件夹
  • Files.delete()方法 可以删除一个文件或目录
  • Files.copy()方法可以吧一个文件从一个地址复制到另一个位置
  • 获取文件属性
  • 遍历一个文件夹
  • Files.walkFileTree()遍历整个目录

# 六 NIO 学习总结以及 NIO 新特性介绍 (opens new window)

  • 内存映射:

这个功能主要是为了提高大文件的读写速度而设计的。内存映射文件(memory-mappedfile)能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中,比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到 OS 内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。

# 七 Java NIO AsynchronousFileChannel 异步文件通 (opens new window)

Java7 中新增了 AsynchronousFileChannel 作为 nio 的一部分。AsynchronousFileChannel 使得数据可以进行异步读写。

# 八 高并发 Java(8):NIO 和 AIO (opens new window)