Java IO 以及 NIO 详解(java中io和nio)

createh52个月前 (02-01)技术教程8

各位志同道合的朋友们大家好,我是一个一直在一线互联网踩坑十余年的编码爱好者,现在将我们的各种经验以及架构实战分享出来,如果大家喜欢,就关注我,一起将技术学深学透,我会每一篇分享结束都会预告下一专题

IO 介绍

IO 是 Input/Output 的缩写,它是基于流模型实现的,比如操作文件时使用输入流和输出流来写入和读取文件等。

IO 分类

传统的 IO,按照流类型我们可以分为:

  • 字符流
  • 字节流

其中,字符流包括 Reader、Writer;字节流包括 InputStream、OutputStream。传统 IO 的类关系图,如下图所示:

?

IO 使用

了解了 IO 之间的关系,下面我们正式进入实战环节,分别来看字符流(Reader、Writer)和字节流(InputStream、OutputStream)的使用。

① Writer 使用

Writer 可用来写入文件,请参考以下代码:

//?给指定目录下的文件追加信息Writer?writer?=?new?FileWriter("d:\\io.txt",true);
writer.append("老王");
writer.close();

这几行简单的代码就可以实现把信息 老王 追加到 d:\\io.txt 的文件下,参数二表示的是覆盖文字还是追加文字。

② Reader 使用

Reader 可用来读取文件,请参考以下代码:

Reader?reader?=?new?FileReader("d:\\io.txt");
BufferedReader?bufferedReader?=?new?BufferedReader(reader);
String?str?=?null;
//?逐行读取信息
while?(null?!=?(str?=?bufferedReader.readLine()))?{
????System.out.println(str);
}
bufferedReader.close();
reader.close();

③ InputStream 使用

InputStream 可用来读取文件,请参考以下代码:

InputStream?inputStream?=?new?FileInputStream(new?File("d:\\io.txt"));
byte[]?bytes?=?new?byte[inputStream.available()];
//?读取到?byte?数组
inputStream.read(bytes);
//?内容转换为字符串
String?content?=?new?String(bytes,?"UTF-8");
inputStream.close();

④ OutputStream 使用

OutputStream 可用来写入文件,请参考以下代码:

OutputStream?outputStream?=?new?FileOutputStream(new?File("d:\\io.txt"),true);
outputStream.write("老王".getBytes());
outputStream.close();

NIO 介绍

上面讲的内容都是 java.io 包下的知识点,但随着 Java 的不断发展,在 Java 1.4 时新的 IO 包出现了 java.nio,NIO(Non-Blocking IO)的出现解决了传统 IO,也就是我们经常说的 BIO(Blocking IO)同步阻塞的问题,NIO 提供了 Channel、Selector 和 Buffer 等概念,可以实现多路复用和同步非阻塞 IO 操作,从而大大提升了 IO 操作的性能。前面提到同步和阻塞的问题,那下面来看看同步和阻塞结合都有哪些含义。

组合方式性能分析同步阻塞最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态同步非阻塞提升 I/O 性能的常用手段,就是将 I/O 的阻塞改成非阻塞方式,尤其在网络 I/O 是长连接,同时传输数据也不是很多的情况下,提升性能非常有效。 这种方式通常能提升 I/O 性能,但是会增加 CPU 消耗,要考虑增加的 I/O 性能能不能补偿 CPU 的消耗,也就是系统的瓶颈是在 I/O 还是在 CPU 上异步阻塞这种方式在分布式数据库中经常用到。例如,在往一个分布式数据库中写一条记录,通常会有一份是同步阻塞的记录,而还有两至三份是备份记录会写到其他机器上,这些备份记录通常都是采用异步阻塞的方式写 I/O;异步阻塞对网络 I/O 能够提升效率,尤其像上面这种同时写多份相同数据的情况异步非阻塞这种组合方式用起来比较复杂,只有在一些非常复杂的分布式情况下使用,像集群之间的消息同步机制一般用这种 I/O 组合方式。例如,Cassandra 的 Gossip 通信机制就是采用异步非阻塞的方式。它适合同时要传多份相同的数据到集群中不同的机器,同时数据的传输量虽然不大,但是却非常频繁。这种网络 I/O 用这个方式性能能达到最高

了解了同步和阻塞的含义,下面来看 NIO 的具体使用,请参考以下代码:

int?port?=?6666;
new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{
????????try?(Selector?selector?=?Selector.open();
?????????????ServerSocketChannel?serverSocketChannel?=?ServerSocketChannel.open();)?{
????????????serverSocketChannel.bind(new?InetSocketAddress(InetAddress.getLocalHost(),?port));
????????????serverSocketChannel.configureBlocking(false);
????????????serverSocketChannel.register(selector,?SelectionKey.OP_ACCEPT);
????????????while?(true)?{
????????????????selector.select();?//?阻塞等待就绪的?Channel
????????????????Set?selectionKeys?=?selector.selectedKeys();
????????????????Iterator?iterator?=?selectionKeys.iterator();
????????????????while?(iterator.hasNext())?{
????????????????????SelectionKey?key?=?iterator.next();
????????????????????try?(SocketChannel?channel?=?((ServerSocketChannel)?key.channel()).accept())?{
????????????????????????channel.write(Charset.defaultCharset().encode("老王,你好~"));
????????????????????}
????????????????????iterator.remove();
????????????????}
????????????}
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
}).start();
?
new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{
????????//?Socket?客户端?1(接收信息并打印)
????????try?(Socket?cSocket?=?new?Socket(InetAddress.getLocalHost(),?port))?{
????????????BufferedReader?bufferedReader?=?new?BufferedReader(new?InputStreamReader(cSocket.getInputStream()));
????????????bufferedReader.lines().forEach(s?->?System.out.println("客户端?1?打印:"?+?s));
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
}).start();
?
new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{
????????//?Socket?客户端?2(接收信息并打印)
????????try?(Socket?cSocket?=?new?Socket(InetAddress.getLocalHost(),?port))?{
????????????BufferedReader?bufferedReader?=?new?BufferedReader(new?InputStreamReader(cSocket.getInputStream()));
????????????bufferedReader.lines().forEach(s?->?System.out.println("客户端?2?打印:"?+?s));
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
}).start();

以上代码创建了两个 Socket 客户端,用于收取和打印服务器端的消息。其中,服务器端通过 SelectionKey(选择键)获取到 SocketChannel(通道),而通道都注册到 Selector(选择器)上,所有的客户端都可以获得对应的通道,而不是所有客户端都排队堵塞等待一个服务器连接,这样就实现多路复用的效果了。多路指的是多个通道(SocketChannel),而复用指的是一个服务器端连接重复被不同的客户端使用。

AIO 介绍

AIO(Asynchronous IO)是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。AIO 实现简单的 Socket 服务器,代码如下:

int?port?=?8888;
new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{
????????AsynchronousChannelGroup?group?=?null;
????????try?{
????????????group?=?AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));
????????????AsynchronousServerSocketChannel?server?=?AsynchronousServerSocketChannel.open(group).bind(new?InetSocketAddress(InetAddress.getLocalHost(),?port));
????????????server.accept(null,?new?CompletionHandler()?{
????????????????@Override
????????????????public?void?completed(AsynchronousSocketChannel?result,?AsynchronousServerSocketChannel?attachment)?{
????????????????????server.accept(null,?this);?//?接收下一个请求
????????????????????try?{
????????????????????????Future?f?=?result.write(Charset.defaultCharset().encode("Hi,?老王"));
????????????????????????f.get();
????????????????????????System.out.println("服务端发送时间:"?+?DateFormat.getDateTimeInstance().format(new?Date()));
????????????????????????result.close();
????????????????????}?catch?(InterruptedException?|?ExecutionException?|?IOException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????}
????????????????@Override
????????????????public?void?failed(Throwable?exc,?AsynchronousServerSocketChannel?attachment)?{
????????????????}
????????????});
????????????group.awaitTermination(Long.MAX_VALUE,?TimeUnit.SECONDS);
????????}?catch?(IOException?|?InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????}
}).start();
?
//?Socket?客户端
AsynchronousSocketChannel?client?=?AsynchronousSocketChannel.open();
Future?future?=?client.connect(new?InetSocketAddress(InetAddress.getLocalHost(),?port));
future.get();
ByteBuffer?buffer?=?ByteBuffer.allocate(100);
client.read(buffer,?null,?new?CompletionHandler()?{
????@Override
????public?void?completed(Integer?result,?Void?attachment)?{
????????System.out.println("客户端打印:"?+?new?String(buffer.array()));
????}
?
????@Override
????public?void?failed(Throwable?exc,?Void?attachment)?{
????????exc.printStackTrace();
????????try?{
????????????client.close();
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
});
Thread.sleep(10?*?1000);

相关面试题

1.使用以下哪个方法来判断一个文件是否存在?

A:createFileB:existsC:readD:exist

答:B

2.以下说法错误的是?

A:同步操作不一定会阻塞B:异步操作不一定会阻塞C:阻塞一定是同步操作D:同步或异步都可能会阻塞

答:C

题目解析:异步操作也可能会阻塞,比如分布式集群消息同步,采用的就是异步阻塞的方式。

3.BIO、NIO、AIO 的区别是什么?

答:它们三者的区别如下。

  • BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。它的优点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
  • NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
  • AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,因此人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

简单来说 BIO 就是传统 IO 包,产生的最早;NIO 是对 BIO 的改进提供了多路复用的同步非阻塞 IO,而 AIO 是 NIO 的升级,提供了异步非阻塞 IO。

4.读取和写入文件最简洁的方式是什么?

答:使用 Java 7 提供的 Files 读取和写入文件是最简洁,请参考以下代码:

//?读取文件
byte[]?bytes?=?Files.readAllBytes(Paths.get("d:\\io.txt"));
//?写入文件
Files.write(Paths.get("d:\\io.txt"),?"追加内容".getBytes(),?StandardOpenOption.APPEND);

读取和写入都是一行代码搞定,可以说很简洁了。

5.Files 常用方法都有哪些?

答:Files 是 Java 1.7 提供的,使得文件和文件夹的操作更加方便,它的常用方法有以下几个:

  • Files. exists():检测文件路径是否存在
  • Files. createFile():创建文件
  • Files. createDirectory():创建文件夹
  • Files. delete():删除一个文件或目录
  • Files. copy():复制文件
  • Files. move():移动文件
  • Files. size():查看文件个数
  • Files. read():读取文件
  • Files. write():写入文件

6.FileInputStream 可以实现什么功能?

答:FileInputStream 可以实现文件的读取。

题目解析:因为 FileInputStream 和 FileOutputStream 很容易被记反,FileOutputStream 才是用来写入文件的,所以也经常被面试官问到。

7.不定项选择:为了提高读写性能,可以采用什么流?

A:InputStreamB:DataInputStreamC:BufferedReaderD:BufferedInputStreamE:OutputStreamF:BufferedOutputStream

答:D、F

题目解析:BufferedInputStream 是一种带缓存区的输入流,在读取字节数据时可以从底层流中一次性读取多个字节到缓存区,而不必每次都调用系统底层;同理,BufferedOutputStream 也是一种带缓冲区的输出流,通过缓冲区输出流,应用程序先把字节写入缓冲区,缓存区满后再调用操作系统底层,从而提高系统性能,而不必每次都去调用系统底层方法。

8.FileInputStream 和 BufferedInputStream 的区别是什么?

答:FileInputStream 在小文件读写时性能较好,而在大文件操作时使用 BufferedInputStream 更有优势。

9.以下这段代码运行在 Windwos 平台,执行的结果是?

Files.createFile(Paths.get("c:\\pf.txt"),?PosixFilePermissions.asFileAttribute(
????EnumSet.of(PosixFilePermission.OWNER_READ)));

A:在指定的盘符产生了对应的文件,文件只读B:在指定的盘符产生了对应的文件,文件只写C:在指定的盘符产生了对应的文件,文件可读写D:程序报错

答:D

题目解析:本题目考察的是 Files.createFile 参数传递的问题,PosixFilePermissions 不支持 Windows,因此在 Windows 执行会报错
java.lang.UnsupportedOperationException: 'posix:permissions' not supported as initial attribute。

总结

在 Java 1.4 之前只有 BIO(Blocking IO)可供使用,也就是 java.io 包下的那些类,它的缺点是同步阻塞式运行的。随后在 Java 1.4 时,提供了 NIO(Non-Blocking IO)属于 BIO 的升级,提供了同步非阻塞的 IO 操作方式,它的重要组件是 Selector(选择器)、Channel(通道)、Buffer(高效数据容器)实现了多路复用的高效 IO 操作。而 AIO(Asynchronous IO)也叫 NIO 2.0,属于 NIO 的补充和升级,提供了异步非阻塞的 IO 操作。

还有另一个重要的知识点,是 Java 7.0 时新增的 Files 类,极大地提升了文件操作的便利性,比如读、写文件 Files.write()、Files.readAllBytes() 等,都是非常简便和实用的方法。

下一篇:Java 反射和动态代理

往期精选(全集在wx公号【架构师修炼】)

分布式数据之缓存技术,一起来揭开其神秘面纱

分布式数据复制技术,今天就教你真正分身术

数据分布方式之哈希与一致性哈希,我就是个神算子

分布式存储系统三要素,掌握这些就离成功不远了

想要设计一个好的分布式系统,必须搞定这个理论

分布式通信技术之发布订阅,干货满满

分布式通信技术之远程调用:RPC

消息队列Broker主从架构详细设计方案,这一篇就搞定主从架构

消息中间件路由中心你会设计吗,不会就来学学

消息队列消息延迟解决方案,跟着做就行了

秒杀系统每秒上万次下单请求,我们该怎么去设计

【分布式技术】分布式系统调度架构之单体调度,非掌握不可

CDN加速技术,作为开发的我们真的不需要懂吗?

烦人的缓存穿透问题,今天教就你如何去解决

分布式缓存高可用方案,我们都是这么干的

每天百万交易的支付系统,生产环境该怎么设置JVM堆内存大小

你的成神之路我已替你铺好,没铺你来捶我

相关文章

java 整合jsch使用 远程交互服务器

#头条创作挑战赛#背景java 通过jsch 远程执行命令 jsch 主要是类似Xshell 只不过是代码级别使用,而 Xshell使用界面化jsch可以执行任何shell 脚本,但是弊端是执行一次必...

Arduino通过串口透传ESP 13板与java程序交互

ESP 13 --- 是一个无线板子,配置通过热点通信Arduino通过串口透传ESP13板与java程序交互这个程序最基本的想法是用java把Arduino抽象出来,忙活了好几天,虽然没有达到最后的...

Java业务系统是怎么和MySQL交互的?

很多crud boy眼中的数据库:但使用MySQL时,总会遇到各种烦人问题,什么偶尔死锁、性能丢人、各种异常报错。一般人都会Google博客,尝试解决问题,最后虽然是解决了问题,但可能也没搞懂背后原理...

Java 24 功能已冻结,进入 Rampdown 第二阶段

作者 | Tim Anderson 译者 | 王强 策划 | TinaOracle 的 Java 平台首席架构师 Mark Reinhold 宣布 JDK 24 已处于 Rampdown 第二阶段,这...

基于JAVA的社交聊天室(基于java的社交聊天室设计方案)

基于Java的社交聊天室一、前言随着互联网技术的迅速发展,实时通信和在线社交已成为人们日常生活的重要组成部分。基于Java的社交聊天室系统,凭借其跨平台、高性能和安全性等特点,为用户提供了一个集中、开...

Java-IO流(Javaio流最快多少mb每秒上传)

目录1、流的概念和作用1.2、Java IO所采用的模型1.3、IO流的分类1.4、IO流特性1.5、IO流常用到的五类一接口1.6、Java IO流对象1.6.1、输入字节流InputStream1...