I/O 流
IO(Input Output)用于实现对数据的输入与输出操作,Java把不同的输入/输出源(键盘、文件、网络等)抽象表述为流(Stream)。流是从起源到接收的有序数据,有了它程序就可以采用同一方式访问不同的输入/输出源。
- 按照数据流向,可以将流分为输入流和输出流,其中输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。
- 按照数据类型,可以将流分为字节流和字符流,其中字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
- 按照处理功能,可以将流分为节点流和处理流,其中节点流可以直接从/向一个特定的IO设备(磁盘、网络等)读/写数据,也称为低级流,而处理流是对节点流的连接或封装,用于简化数据读/写功能或提高效率,也称为高级流。
Java提供了大量的类来支持IO操作,下表给大家整理了其中比较常用的一些类。其中,黑色字体的是抽象基类,其他所有的类都继承自它们。红色字体的是节点流,蓝色字体的是处理流。
根据命名很容易理解各个流的作用:
- 以File开头的文件流用于访问文件;
- 以ByteArray/CharArray开头的流用于访问内存中的数组;
- 以Piped开头的管道流用于访问管道,实现进程之间的通信;
- 以String开头的流用于访问内存中的字符串;
- 以Buffered开头的缓冲流,用于在读写数据时对数据进行缓存,以减少IO次数;
- InputStreamReader、InputStreamWriter是转换流,用于将字节流转换为字符流;
- 以Object开头的流是对象流,用于实现对象的序列化;
- 以Print开头的流是打印流,用于简化打印操作;
- 以Pushback开头的流是推回输入流,用于将已读入的数据推回到缓冲区,从而实现再次读取;
- 以Data开头的流是特殊流,用于读写Java基本类型的数据。
怎么用流打开一个大文件?
打开大文件,应避免直接将文件中的数据全部读取到内存中,可以采用分次读取的方式。
- 使用缓冲流。缓冲流内部维护了一个缓冲区,通过与缓冲区的交互,减少与设备的交互次数。使用缓冲输入流时,它每次会读取一批数据将缓冲区填满,每次调用读取方法并不是直接从设备取值,而是从缓冲区取值,当缓冲区为空时,它会再一次读取数据,将缓冲区填满。使用缓冲输出流时,每次调用写入方法并不是直接写入到设备,而是写入缓冲区,当缓冲区填满时它会自动刷入设备。
- 使用NIO。NIO采用内存映射文件的方式来处理输入/输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入/输出比传统的输入/输出要快得多。
普通 Socket 方法处理的 Server
服务端
public class Server {
public static void main(String[] args) {
try {
// 创建一个端口来监听
ServerSocket server = new ServerSocket(8080);
// 等待请求
Socket socket = server.accept();
// 接收的请求后使用socket进行通信,创建BufferedReader
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = is.readLine();
System.out.println("received from client: " + line);
// 创建PrintWrite,用于发送数据
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("received data: " + line);
pw.flush();
// 关闭资源
pw.close();
is.close();
socket.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
public class Client {
public static void main(String[] args) {
String msg = "Clinet data";
try {
// 创建一个socket,跟本机8080相连
Socket socket = new Socket("127.0.0.1",8080);
PrintWriter pw = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 发送数据
pw.println(msg);
pw.flush();
// 接收数据
String line = is.readLine();
System.out.println("received from server :" + line);
// 关闭资源
pw.close();
is.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO
程序执行效率更多的是由IO的效率决定的,JVM在IO操作方面效率不足。在操作系统中,可以从硬件上直接读取大块的数据,然而JVM的IO更喜欢小块数据的读取。在JDK4中引入了NIO,可以最大限度的满足java程序IO的需求。
java.nio包中定义了各种Buffer相关的类。
NIO中有三大组件:Channel ,Buffer ,Selector
Channel
程序和数据源之间的传输不是直接的,是经过buffer的,程序的读写数据都是针对buffer,然后buffer通过channel与数据源进行交互。
- IO流是线程阻塞的,在调用read() / write() 读写数据的时候,线程阻塞,直到数据读取完毕或者数据完全写入,在读写过程中,线程不能做其他任务。
- NIO不是线程阻塞的,当线程从Channel中读取数据时,如果通道中没有可用的数据,线程不阻塞,可以做其他的任务。
Buffer
buffer本质就是一个数组,把数组的内容与信息包装成一个buffer对象,它提供了一组访问这些信息的方法。
buffer缓冲区的重要属性:
- capacity容量,创建缓冲区的时候指定大小,创建后不能再修改,如果缓冲区满了,需要清空后才能继续写入。
- position表示当前位置
- limit上限 指第一个不能被读出或写入的位置。limit上限后面的单元既不能读也不能写。
- mark标记,设置一个标记位置,可以调用mark方法。
Selector
Selector选择器是java NIO中能够检测一到多个NIO通道,并能够知晓通道是都为诸如读写事件做好准备的组件。