Java技术——I/O知识学习
0. 前言
I/O是Java技术网络中一个比较重要的点,不仅是平时开发中的家常便饭,也是面试的时候经常被问到的话题。本篇将介绍字节IO、字符IO的基本体系和用法,最后总结一下NIO的一些知识。本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52776972
1. 字节I/O
字节流处理单元为1个字节,主要用在处理二进制数据,字节用来与文件打交道,所有文件的储存都是通过字节(byte)的方式,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取形成字节序列。
1.1 字节I/O流体系
OutputStream //输出字节流的抽象类
|--->FileOutputStream://文件输出流
|--->BufferedOutputStream //字节写入流缓冲区
|--->PrintStream //打印流 InputStream //字节输入流的抽象类
|--->FileInputStream //文件字节读取流
|--->BufferedInputStream //字节读取流缓冲区
1.2 字节I/O流应用示例
//文件a中内容拷贝到文件b
//创建一个字节输入流
FileInputStream fis=new FileInputStream("D:/a.txt");
//创建一个字节输出流
FileOutputStream fos=new FileOutputStream("D:/b.txt");
int len=0;
byte[] b=new byte[1024];//字节缓冲区
while((len=fis.read(b))!=-1){ //判断未读到文件尾
fos.write(b, 0, len); //将读出到的文件内容写到fos中
}
//关闭流
2. 字符I/O
实际中很多的数据是文本,因此有了字符流的概念,字符流只用于处理文字数据。字符流处理单元为2个字节的Unicode字符。
字符流中的对象融合了编码表,使用的是默认的编码,即当前系统的编码。在实际开发中出现的汉字乱码问题实际上都是在字符流和字节流之间转化不统一而造成的。
2.1 字符I/O流体系
Reader //读取字符流的抽象类,子类必须实现read(char[], int, int)、close()
|---BufferedReader //从字符输入流中读取文本,缓冲自定义个字符,从而实现字符、数组和行的高效读取
|---LineNumberReader //跟踪行号的缓冲字符输入流,此类的set/getLineNumber()用于设置/获取当前行号
|---InputStreamReader //字节流通向字符流的桥梁,使用指定的charset读取字节并将其解码为字符
|---FileReader //用来读取字符文件的便捷类 Writer //写入字符流的抽象类,子类必须实现write(char[], int, int)、close()和flush()
|---BufferedWriter //将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入
|---PrintWriter //向文本输出流打印对象的格式化表示形式
|---OutputStreamWriter //是字符流通向字节流的桥梁,使用指定的charset将要写入流中的字符编码成字节|---FileWriter //用来写入字符文件的便捷类
2.2 字符I/O流应用示例
//将中文字符写入文件
File file=new File("D:/test.txt");
String charset="UTF-8";
//写字符串转换成字节流
OutputStream out=new FileOutputStream(file);
OutputStreamWriter writer=new OutputStreamWriter(out,charset);//将字符存储为指定编码集的字节
writer.write("这是写入到文件中的中文字符"); //读取字节转换成字符
FileInputStream inputStream=new FileInputStream(file);
InputStreamReader reader=new InputStreamReader(inputStream,charset);
StringBuffer buffer=new StringBuffer();
char[] buf=new char[64];
int count=0;
while((count=reader.read(buf))!=-1){
buffer.append(buf,0,count);
}
System.out.println(buffer.toString());
3. NIO
如果你细细分析,一定会发现阻塞I/O存在一些缺点:
(1)当客户端多时,会创建大量的处理线程。且每个线程都会产生性能消耗。
(2)阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。
综上所述,在某种情况下非阻塞式I/O就有了它的应用前景。NIO是Java 4里面提供的新的API,目的就是用来解决传统IO里的这些问题。
3.1 NIO中的重要概念
在NIO中有几个比较关键的概念:Channel(通道),Buffer(缓冲区),Selector(选择器)。
(1)Channel(通道),在传统IO中我们要读取一个文件中的内容时InputStream实际上就是为读取文件提供了一个通道,因此可以将NIO 中的Channel同传统IO中的Stream来类比。但是要注意的是,传统IO中的Stream是单向的,比如InputStream只能进行读取操作,OutputStream只能进行写操作;而Channel是双向的,既可用来进行读操作又可用来进行写操作。
以下是常用的几种通道
FileChannel //可以从对文件进行读写数据
SocketChanel //以TCP向网络连接的两端读写数据
DatagramChannel //以UDP协议向网络连接的两端读写数据
ServerSocketChannel //监听客户端发起的TCP连接,并为每个连接创建新的SocketChannel来进行数据读写
下面给出通过FileChannel来向文件中写入数据的一个例子:
File file = new File("C:/test.txt");
FileOutputStream outputStream = new FileOutputStream(file);
FileChannel channel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
String string = "NIO";
buffer.put(string.getBytes());
buffer.flip();
channel.write(buffer);
(2)Buffer(缓冲区),实际上是一个容器,是一个连续数组,在NIO中所有数据的读和写都必须经由Buffer。
在NIO中,Buffer是一个顶层父类,它是一个抽象类。
常用的Buffer的子类有:ByteBuffer、IntBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer。
如果是对于文件读写,上面几种Buffer都可能会用到。但是对于网络读写来说,用的最多的是ByteBuffer。
(3)Selector,可以理解为通道管理器,用单线程处理一个Selector以轮询每个注册的Channel,通过Selector.select()方法来获取到达事件(一个SelectionKey表示一个到达的事件),一旦发现Channel有注册的事件发生,便可以逐个地对这些事件进行处理。
比如,某时刻客户端给服务端发送了一些数据,阻塞I/O会调用read()阻塞地读取数据,而NIO的服务端会在Selector中添加一个读事件,服务端的处理线程会轮询地访问Selector,如果访问Selector时有读事件到达,则处理该事件,如果没有则处理线程会一直阻塞直到读事件到达。
3.2 NIO优点
NIO便解决了上述传统IO的两大缺点:不会创建、维护多个处理线程,只用一个专门的线程就可以管理多个通道,也就是管理多个连接,也避免了多线程之间的上下文切换导致的性能开销。同时也使得只有在真正有读写事件发生时,才会调用函数来进行读写,而不是同步地去监听事件,大大地减少了系统开销。
3.3 NIO使用实例介绍
public class NIOClient {
//通道管理器
private Selector selector;
// 获得一个Socket通道,并对该通道做一些初始化工作
public void initClient(String ip,int port) throws IOException{
//获得一个Socket通道
SocketChannel channel = SocketChannel.open();
//设置通道为非阻塞
channel.configureBlocking(false);
//获得一个通道管理器
this.selector = Selector.open();
//客户端连接服务器,其实方法执行并没有实现连接
//在listen()方法中调用channel.finishConnect()才能完成连接
channel.connect(new InetSocketAddress(ip,port));
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件
channel.register(this.selector, SelectionKey.OP_CONNECT);
} //采用轮询的方式监听selector上是否有需要处理的事件
public void listen() throws IOException{
while(true){
/*
* 选择一组可以进行I/O操作的事件,放在selector中,客户端的该方法不会阻塞,
* 这里和服务端的方法不一样,当至少一个通道被选中时,
* selector的wakeup方法被调用,方法返回,而对于客户端来说,通道一直是被选中的
*/
selector.select();
//获得selector中选中的项的迭代器
Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
while(ite.hasNext()){
SelectionKey key = (SelectionKey) ite.next();
//删除已选的key,以防重复处理
ite.remove();
//连接事件发生
if(key.isConnectable()){
SocketChannel channel = (SocketChannel) key.channel();
//如果正在连接,则完成连接
if(channel.isConnectionPending()){
channel.finishConnect();
}
//设置成非阻塞
channel.configureBlocking(false);
//在这里可以给服务端发送信息哦
channel.write(ByteBuffer.wrap(new String("Hello").getBytes()));
//在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限
channel.register(this.selector, SelectionKey.OP_READ);
} else if(key.isReadable()){
//获得了可读的事件
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("客户端收到信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);
}
}
}
}
public static void main(String[] args) throws IOException{
NIOClient client = new NIOClient();
client.initClient("localhost", 8080);
client.listen();
}
} public class NIOServer {
private Selector selector;
public void initServer(int port) throws IOException {
//获得一个ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//设置通道为非阻塞
serverChannel.configureBlocking(false);
//将该通道对应的ServerSocket绑定到port端口
serverChannel.socket().bind(new InetSocketAddress(port));
//获得一个通道管理器
this.selector = Selector.open();
//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件
//注册该事件后,当该事件到达时,selector.select()会返回,否则selector.select()会一直阻塞
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} public void listen() throws IOException {
System.out.println("服务端启动成功!");
while(true){
selector.select();
//获得selector中选中的项的迭代器,选中的项为注册的事件
Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
while(ite.hasNext()){
SelectionKey key = (SelectionKey)ite.next();
//删除已选的key,以防重复处理
ite.remove();
//客户请求连接事件
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//获得和客户端连接的通道
SocketChannel channel = server.accept();
//设置成非阻塞
channel.configureBlocking(false);
//在这里可以给客户端发送信息哦
channel.write(ByteBuffer.wrap(new String("Hello").getBytes()));
//在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读权限
channel.register(this.selector, SelectionKey.OP_READ);
}else if(key.isReadable()){
//服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
//创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:" + msg);
ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
channel.write(outBuffer);//将消息回送给客户端
}
}
}
}
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8080);
server.listen();
}
}
最后引用网上的一个对NIO的比喻来帮助理解:
其中Channel对应以前的流,Buffer不是什么新东西,Selector是因为nio可以使用异步的非堵塞模式才加入的东西。以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上。
NIO的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,都可以得到妥善接纳,这个关键之处就是增加了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到一定程度的时候,就切换一下:临时关上当前水龙头,试着打开另一个水龙头(看看有没有水)。
当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做其它事去,水接满了,接水工会通知他们。
这其实也是非常接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费资源的方式。
关于Java的IO知识就总结到这吧,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52776972
记得点赞哦~
Java技术——I/O知识学习的更多相关文章
- (转)如何学习Java技术?谈Java学习之路
51CTO编者注:这篇文章已经是有数年“网龄”的老文,不过在今天看来仍然经典.如何学习Java?本篇文章可以说也是面对编程初学者的一篇指导文章,其中对于如何学习Java的步骤的介绍,很多也适用于开发领 ...
- Java技术大牛需要学习的25个技能
你需要精通面向对象分析与设计(OOA/OOD).涉及模式(GOF,J2EEDP)以及综合模式.你应该了解UML,尤其是class.object.interaction以及statediagrams. ...
- Java技术学习路线图
一:常见模式与工具 学习Java技术体系,设计模式,流行的框架与组件是必不可少的: 常见的设计模式,编码必备 Spring5,做应用必不可少的最新框架 MyBatis,玩数据库必不可少的组件 二:工程 ...
- Java技术学习路线
转载 作者:David 链接:https://www.zhihu.com/question/25255189/answer/86898400来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商 ...
- 一位资深程序员大牛推荐的Java技术学习路线图
Web应用,最常见的研发语言是Java和PHP. 后端服务,最常见的研发语言是Java和C/C++. 大数据,最常见的研发语言是Java和Python. 可以说,Java是现阶段中国互联网公司中,覆盖 ...
- Java基础知识学习
1.什么是Java编程语言 Java是:一种编程语言.一种开发环境.一种应用环境.一种部署环境 2.Java编程语言的主要目标 (1)提供一种解释环境为:提高开发速度.代码可移植性.使用户能运行不止一 ...
- JVM学习之Java技术体系
目录 一.Java技术体系 1.Java体系构成 2.JDK.JRE.JVM之前的关系 JVM介绍 (1)JVM官方文档定义 (2)中文解释 JVM结构 Java代码执行流程 JVM架构模型 1.指令 ...
- 要学Java,怎么高效地学习,怎么规划
要学Java,怎么高效地学习,怎么规划? 题主是一个个例,99%的人(包括我自己)都没有题主这样的经历,也很难提出具有很强参考性的java学习建议.我倒是之前面试过一个跟题主有点类似的人,拿出来分 ...
- 从程序员到CTO的Java技术路线图 作者:zz563143188
在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样我们清楚的知道我们大概处于那个阶段和水平. Java程序员 高级特性 反射.泛型. ...
随机推荐
- Android(java)学习笔记39:Android 修改字体
首先如果android内部自带的字体不是我们需要的字体,那我们就需要字体文件导入到android开发工程中,下午我们详细讲述: 1.我们首先分析知道,我想要TextView控件中文字的字体是:华文楷体 ...
- POSIX 线程详解(经典必看)
http://www.cnblogs.com/sunminmin/p/4479952.html 总共三部分: 第一部分:POSIX 线程详解 ...
- 2018.10.10 MAC 的Launchpad图标改变大小的设置
mac更改launchpad图标大小 设置每列显示的图标数目为8 defaults write com.apple.dock springboard-columns -int 8 设置每行显示的图标数 ...
- SpringBoot 使用(三): 配置文件详解
代码从开发到测试要经过各种环境,开发环境,测试环境,demo环境,线上环境,各种环境的配置都不一样,同时要方便各种角色如运维,接口测试, 功能测试,全链路测试的配置,hardcode 肯定不合适,如S ...
- html5 ajax Java接口 上传图片
html5图片上传[文件上传]在网上找了很多资料,主要也就2种 1.from表单提交的方式 <form action="pushUserIcon" method=" ...
- 【题解】洛谷 P1525 关押罪犯
题目 https://www.luogu.org/problemnew/show/P1525 思路 把所有边sort一遍从大到小排列 运用并查集思想敌人的敌人就是朋友 从最大边开始查找连着的两个罪犯 ...
- dedecms基础整理,
需求3: 在添加某个商品的时候,我们希望多一个信息,就是付费方式,还希望多一个邮资信息,我们又该怎样处理? 引出修改内容模型的问题 每个模型的字段管理的所有信息 都属于附加表. 步骤: 点击 核心-& ...
- 课时59.体验css(理解)
我们想做这样一个样式,应该怎么做? 分析: 有一个标题(h1),还有一些段落(p) 标题是居中的,段落也是居中的,所以我们可以设置h标签和p标签居的align属性等于center来实现 标题和段落都有 ...
- React Router 4 的使用(2)
Route Rendering Props 对于给定的路由如何渲染组件,有三种选项:component.render.children.你可以查看 <Route> 的文档来获取更多的信息, ...
- 关于Echarts的原生js获取DOM元素与动态加载DOM元素的冲突问题
1.前言: 最近在做的看板项目,因为需要循环加载后台数据,并且用Echarts做数据呈现,所以jQuery和angular等库统统靠边站,Echarts用的是原生js获取DOM元素,至于诸多不兼容等深 ...