I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这个海量数据时代,数据在网络中随处流动。这个流动的过程中都涉及到 I/O 问题,可以说大部分 Web 应用系统的瓶颈都是 I/O 瓶颈。
字节I/O
字节输入流InputStream
InputStream本身是一个抽象类 ,要想使用此类必须依靠其子类。如果需要从文件中读取字节流 ,就用FileInputStream来实现。
public class FileInputStreamTest { public static void main(String[] args) throws IOException { File f = new File( "c:/test.txt" ); InputStream in=new FileInputStream(f); byte [] b= new byte [( int ) f.length()]; in.read(b); in.close(); System.out.println(new String(b)); } }
输出:
test.txt文件的内容!
字节输出流OutputStream
OutputStream也是一个抽象类,想要使用此类必须依靠其子类。如果需要向文件中写入字节流 ,就用FileOutputStream来实现。
public class FileOutputStreamTest { public static void main(String[] args) throws IOException { File f=new File( "c:/test.txt" ); OutputStream out=new FileOutputStream(f); String str="写入的文件内容!" ; byte [] b=str.getBytes(); out.write(b); out.close(); } }
程序执行后,test.txt文件内容变成:“写入的文件内容!”。
字符I/O
字符输入流:Reader
Reader是抽象类,要想使用此类必须依靠其子类。如果现在要从文件中读取字符流 ,则可以直接使用FileReader子类。
public class FileReaderTest { public static void main(String[] args) throws IOException { File f=new File( "c:/test.txt" ); Reader input=new FileReader(f); char [] c= new char [ 1024 ]; int len=input.read(c); input.close(); System.out.println(new String(c, 0 ,len)); } }
字符输出流:Writer
Writer是一个抽象类,如果要使用此类要依赖其子类。如果现在要向文件中写入字符流 ,则可以直接使用FileWriter子类。
public class FileWriterTest { public static void main(String[] args) throws IOException { File f=new File( "c:/test.txt" ); Writer out=new FileWriter(f); String str="hello!" ; out.write(str); out.close(); } }
字节流与字符流
流的概念 :在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。 字节流处理单元为1个字节,操作字节和字节数组。字符流处理单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串。所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。
字节流 主要用在处理二进制数据,它是按字节来处理的。但实际中很多的数据是文本,又提出了字符流 的概念,它是按虚拟机的编码来处理,也就是要进行字符集的转化。总的来说,字节用来与文件打交道,而字符用来和人打交道。
在实际开发中出现的汉字乱码问题实际上都是在字符流和字节流之间转化不统一而造成的。
字节与字符间的转化
另外数据持久化或网络传输都是以字节进行的,所以必须要有字符到字节或字节到字符的转化。
public class OutputStreamWriterTest { public static void main(String[] args) throws IOException { File f=new File( "c:/test.txt" ); String charset="UTF-8" ; OutputStream out=new FileOutputStream(f); OutputStreamWriter writer=new OutputStreamWriter(out,charset); writer.write("这是要保存的中文字符!" ); writer.close(); } }
程序执行后,文件内容为:
这是要保存的中文字符!
public class InputStreamReaderTest { public static void main(String[] args) throws IOException { File f=new File( "c:/test.txt" ); String charset="UTF-8" ; FileInputStream inputStream=new FileInputStream(f); InputStreamReader reader=new InputStreamReader(inputStream,charset); StringBuffer buffer=new StringBuffer(); char [] buf= new char [ 64 ]; int count= 0 ; try { while ((count=reader.read(buf))!=- 1 ){ buffer.append(buf,0 ,count); } } finally { reader.close(); } String s1=buffer.toString(); System.out.println(s1); } }
输出:
这是要保存的中文字符!
如果,我们不指定编码集,采用操作系统 默认的编码集,将会输出乱码:杩欐槸瑕佷繚瀛樼殑涓枃瀛楃锛?
InputStreamReader 类是字节到字符的转化桥梁,InputStream 到 Reader 的过程要指定编码字符集,否则将采用操作系统默认字符集,很可能会出现乱码问题。StreamDecoder 正是完成字节到字符的解码的实现类。FileReader 类就是按照上面的工作方式读取文件的,FileReader 是继承了 InputStreamReader 类,实际上是读取文件流,然后通过 StreamDecoder 解码成 char。
java NIO 和阻塞I/O的区别
阻塞I/O通信模型
假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。 阻塞I/O的通信模型示意图如下:
如果你细细分析,一定会发现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,我总结了它的两点缺点: 1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间 2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。 在这种情况下非阻塞式I/O就有了它的应用前景。
java NIO原理及通信模型
Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是Java NIO的工作原理: 1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。 2. 事件驱动机制:事件到达的时候事件,而不是同步的去监视事件。 3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。 阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:
Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO采用了双向通道(channel) 进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。
事件名 对应值 服务端接收客户端连接事件 SelectionKey.OP_ACCEPT(16) 客户端连接服务端事件 SelectionKey.OP_CONNECT(8) 读事件 SelectionKey.OP_READ(1) 写事件 SelectionKey.OP_WRITE(4)
服务端和客户端各自维护一个管理通道的对象,我们称之为selector ,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。
下面是我理解的java NIO的通信模型示意图:
java NIO服务端和客户端代码实现
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOClient { private Selector selector; public void initClient(String ip, int port) throws IOException{ SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false ); this .selector = Selector.open(); channel.connect(new InetSocketAddress(ip,port)); channel.register(this .selector, SelectionKey.OP_CONNECT); } public void listen() throws IOException{ while ( true ){ selector.select(); Iterator<SelectionKey> ite = this .selector.selectedKeys().iterator(); while (ite.hasNext()){ SelectionKey key = (SelectionKey) ite.next(); ite.remove(); if (key.isConnectable()){ SocketChannel channel = (SocketChannel) key.channel(); if (channel.isConnectionPending()){ channel.finishConnect(); } channel.configureBlocking(false ); channel.write(ByteBuffer.wrap(new String( "abcdefg" ).getBytes())); channel.register(this .selector, SelectionKey.OP_READ); } else if (key.isReadable()){ read(key); } } } } public void read(SelectionKey key) throws IOException{ 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(); } }
import java.io.*; import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.*; public class NIOServer { private Selector selector; public void initServer( int port) throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false ); serverChannel.socket().bind(new InetSocketAddress(port)); this .selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } public void listen() throws IOException { System.out.println("服务端启动成功!" ); while ( true ){ selector.select(); Iterator<SelectionKey> ite = this .selector.selectedKeys().iterator(); while (ite.hasNext()){ SelectionKey key = (SelectionKey)ite.next(); ite.remove(); if (key.isAcceptable()){ ServerSocketChannel server = (ServerSocketChannel)key.channel(); SocketChannel channel = server.accept(); channel.configureBlocking(false ); channel.write(ByteBuffer.wrap(new String( "1234567890" ).getBytes())); channel.register(this .selector, SelectionKey.OP_READ); }else if (key.isReadable()){ read(key); } } } } public void read(SelectionKey key ) throws IOException{ 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(); } }