IO

简介

IO(Input/Output)==“流”。java对设备中数据的操作是通过流的方式
IO流用来处理设备之间的数据传输(设备指硬盘、内存、键盘录入、网络等)
IO流的本质是数据传输,并且流是单向的

IO分阻塞型IO和非阻塞型IO(NIO)
阻塞型IO在读取数据时,如果数据未到达,会一直阻塞到读取到数据为止,所以称为阻塞型IO,在高并发的环境下性能不佳。
NIO不是使用 “流” 来作为数据的传输方式,而是使用通道,通道的数据传输是双向的,且NIO的数据写入和读取都是异步的,不会阻塞线程,所以称为非阻塞型IO,在高并发的环境下性能很好。

分类

  1. 操作数据类型的不同分为:字节流和字符流
  2. 流向分为:输入流、输出流
    1. 字节流:
      1. InputStream字节输入流
        1. FileInputStream
        2. BufferedInputStream
      2. OutputStream字节输入流
        1. FileOutputStream
        2. BufferedOutputStream
    2. 字符流:
      1. Reader字符输入流
        1. InputStreamReader转换流——FileReader
        2. BufferedReader
      2. Writer字符输入流
        1. OutputStreamWriter转换流——FileWriter
        2. BufferedWriter

字节流

字节流读取单个字节
字节流用来处理二进制文件(图片、MP3、视频文件)

基类

1)InputStream
InputStream:字节输入流基类,抽象类是表示字节输入流的所有类的超类
常用方法:

  1. //从输入流中读取数据的下一个字节
  2. abstract int read();
  3. //从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
  4. int read(byte[] b);
  5. 将输入流中最多len个数据字节读入byte数组
  6. int read(byte[] b, int off, int len);
  7. //跳过和丢弃此输入流中数据的n个字节
  8. long skip(long n);
  9. 关闭此输入流并释放与该流关联的所有系统资源
  10. void close();

2)OutputStream
OutputStream:字节输出流基类,抽象类是表示输出字节流的所有类的超类
常用方法;

  1. //将 b.length 个字节从指定的 byte 数组写入此输出流
  2. void write(byte[] b)
  3. //将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
  4. void write(byte[] b, int off, int len)
  5. //将指定的字节写入此输出流
  6. abstract void write(int b)
  7. //关闭此输出流并释放与此流有关的所有系统资源
  8. void close()
  9. //刷新此输出流并强制写出所有缓冲的输出字节
  10. void flush()

字节文件操作流

1)FileInputStream
字节文件输入流,从问价你系统中的某个文件中获得输入字节,用于读取如图像数据之类的原始字节流

构造方法:

  1. //通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定
  2. FileInputStream(File file)
  3. //通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径name指定
  4. FileInputStream(String name)

注意: 一次读取一个字节数组,提高了操作效率,IO流使用完一定要关闭
2)FileOutputStream
字节文件输出流是用于将数据写入到File,从程序中写入到其他位置。

构造方法:

  1. // 创建一个向指定File对象表示的文件中写入数据的文件输出流
  2. FileOutputStream(File file)
  3. // 创建一个向指定File对象表示的文件中写入数据的文件输出流
  4. FileOutputStream(File file, boolean append)
  5. // 创建一个向具有指定名称的文件中写入数据的输出文件流
  6. FileOutputStream(String name)
  7. // 创建一个向具有指定name的文件中写入数据的输出文件流
  8. FileOutputStream(String name, boolean append)
  9. OutputStream outputStream = new FileOutputStream(new File("test.txt"));

注意:输出的目的地文件不存在,则会自动创建,不指定盘符的话,默认创建在项目目录下;输出换行符时一定要写\r\n不能只写\n,因为不同文本编辑器对换行符的识别存在差异性。

字节缓冲流

1)BufferedInputStream
字节缓冲输入流,提高了读取效率

构造方法:

  1. //创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用
  2. BufferedInputStream(InputStream in)
  3. //创建具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流in,以便将来使用
  4. BufferedInputStream(InputStream in , int size)

2)BufferedOutputStream
字节缓冲输出流,提高了写出效率。

  1. 构造方法:
  2. // 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
  3. BufferedOutputStream(OutputStream out)
  4. // 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
  5. BufferedOutputStream(OutputStream out, int size)
  6. 常用方法:
  7. // 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流
  8. void write(byte[] b, int off, int len)
  9. // 将指定的字节写入此缓冲的输出流
  10. void write(int b)
  11. // 刷新此缓冲的输出流
  12. void flush()

字符流

符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)
字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)

基类

1)Reader
读取字符流的抽象类

  1. 常用方法:
  2. //读取单个字符
  3. int read();
  4. //将字符读入数组
  5. int read(char[] cbuf)
  6. //将字符读入数组的某一部分
  7. abstract int read(char[] cbuf, int off, int len)
  8. //跳过字符
  9. long skip(long n)
  10. //关闭该流并释放与之挂念的所有资源
  11. abstract void close()

2)Writer
写入字符流的抽象类

  1. 常用方法:
  2. // 写入字符数组
  3. void write(char[] cbuf)
  4. // 写入字符数组的某一部分
  5. abstract void write(char[] cbuf, int off, int len)
  6. // 写入单个字符
  7. void write(int c)
  8. // 写入字符串
  9. void write(String str)
  10. // 写入字符串的某一部分
  11. void write(String str, int off, int len)
  12. // 将指定字符添加到此 writer
  13. Writer append(char c)
  14. // 将指定字符序列添加到此 writer
  15. Writer append(CharSequence csq)
  16. // 将指定字符序列的子序列添加到此 writer.Appendable
  17. Writer append(CharSequence csq, int start, int end)
  18. // 关闭此流,但要先刷新它
  19. abstract void close()
  20. // 刷新该流的缓冲
  21. abstract void flush()

字符转换流

1)InputStreamReader
字节流转字符流,它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集

  1. 构造方法:
  2. // 创建一个使用默认字符集的 InputStreamReader
  3. InputStreamReader(InputStream in)
  4. // 创建使用给定字符集的 InputStreamReader
  5. InputStreamReader(InputStream in, Charset cs)
  6. // 创建使用给定字符集解码器的 InputStreamReader
  7. InputStreamReader(InputStream in, CharsetDecoder dec)
  8. // 创建使用指定字符集的 InputStreamReader
  9. InputStreamReader(InputStream in, String charsetName)
  10. 特有方法:
  11. //返回此流使用的字符编码的名称
  12. String getEncoding()

2)OutputStreamWriter
字节流转字符流

  1. 构造方法:
  2. // 创建使用默认字符编码的 OutputStreamWriter
  3. OutputStreamWriter(OutputStream out)
  4. // 创建使用给定字符集的 OutputStreamWriter
  5. OutputStreamWriter(OutputStream out, Charset cs)
  6. // 创建使用给定字符集编码器的 OutputStreamWriter
  7. OutputStreamWriter(OutputStream out, CharsetEncoder enc)
  8. // 创建使用指定字符集的 OutputStreamWriter
  9. OutputStreamWriter(OutputStream out, String charsetName)
  10. 特有方法:
  11. //返回此流使用的字符编码的名称
  12. String getEncoding()

字符缓冲流(高效流)

1)BufferedReader
字符缓冲流,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

  1. 构造方法:
  2. // 创建一个使用默认大小输入缓冲区的缓冲字符输入流
  3. BufferedReader(Reader in)
  4. // 创建一个使用指定大小输入缓冲区的缓冲字符输入流
  5. BufferedReader(Reader in, int sz)
  6. 特有方法:
  7. // 读取一个文本行
  8. String readLine()

2)BufferedWriter
字符缓冲流,将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入

  1. 构造方法:
  2. // 创建一个使用默认大小输出缓冲区的缓冲字符输出流
  3. BufferedWriter(Writer out)
  4. // 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
  5. BufferedWriter(Writer out, int sz)
  6. 特有方法:
  7. // 写入一个行分隔符
  8. void newLine()

fileReader fileWriter

  1. FileReader:InputStreamReader类的直接子类,用来读取字符文件的便捷类,使用默认字符编码。
  2. FileWriter:OutputStreamWriter类的直接子类,用来写入字符文件的便捷类,使用默认字符编码

NIO

概述

从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O)。

NIO与原来的IO有同样的作用和目的,但使用的方式完全不同,NIO支持面向缓冲区的,基于通道的IO操作;NIO将以更高效的方式进行文件的读写操作

NIO采用内存映射文件的方式来处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了。
读数据和写数据方式:

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

Buffer缓冲区

简介

缓冲区实际上是一个容器对象,直接说是一个数组。
在NIO库中,所有数据都是用缓冲区处理的。读取数据是,它直接读到缓冲区中的;写入数据时,它也是写入到缓冲区中的;任何时候访问NIO中的数据,都是将它放到缓冲区中,而在面向流IO中,所有数据都是直接写入或者将数据读取到Stream对象中。

  • Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels
  • Buffer本质上是一块内存区
  • 一个Buffer有三个属性:Capacity容量、position位置、limit限制
  • 出了Boolean类型外,其他都有对应的Buffer

image.png

常用方法

  • Buffer clear()
    • 用于写模式,作用是清空Buffer中的内容,清空是指写上限与Buffer的真实容量相同,即limit==capacity
  • Buffer flip()
    • 将写模式转变为读模式,将写模式下的Buffer中内容的最后位置变为读模式下的limit位置,作为读越界位置,同时将当前读位置置为0,表示转换后 重头开始读,同时在消除写模式下的mark标记
  • Buffer rewind()
    • 读写模式都可用,将当前位置置0,取消mark标记

Buffer使用方式

  • 分配缓冲区
  • 写入数据到缓冲区
    • 从Channel中写数据到Buffer
    • 通过put写数据

Channel通道

简介

NIO中的所有IO都是从Channel(通道)开始的;
在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口;

通道是一个对象,通过它可以读取和写入数据,当然所有数据都通过Buffer对象来处理。永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

Channel和Stream的区别:
通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作;

常用方法

最常用的三个类方法就是map、read和write

  • map方法用于将Channel对应的部分或全部数据映射成ByteBuffer
  • read或write方法有一系列的重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据

通道之间的数据传输

  • 在NIO中如果一个Channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel
  • transferFrom(): transferFrom方法把数据从通道源传输到FileChannel
  • transferTo(): transferTo方法把FileChannel数据传输到另一个channel

Selector选择器

简介

Selector类是NIO的核心类

Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销

使用方法

  1. Selector的创建

    1. Selector selector = Selector.open();
  2. 注册Channel到Selector(Channel必须是非阻塞的)

    1. channel.configureBlocking(false);
    2. SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
  3. SelectionKey介绍

一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。
一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑

  1. 从Selector中选择channel

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

  1. 停止选择的方法

wakeup()方法 和close()方法

IO vs NIO

  • IO是面向流的,NIO是面向缓冲区的
    • IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方
    • NIO则能前后移动流中的数据,因为是面向缓冲区的
  • IO流是堵塞的,NIO流是不阻塞的
    • IO的各种流是阻塞的。也就是,当一个线程调用read()或write()时,该线程是被阻塞的,直到有一些数据被读取,或者数据完成写入。该线程在此期间不能再干任何事情。
    • NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。同时,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情
  • 选择器(NIO有,IO无)
    • NIO选择器允许一个单独的线程来监视多个输入通道