IO:
io流分为字符和字节两种,其实比较好认,一般字节的都是Stream结尾,字符的是Reader或Writer结尾,字符和字节转换用InputStreamReader
字符的用于读取纯文本格式,一次读一个字符,比如utf-8三个字节
字节用来读取二进制文件等,那种人看不懂的,包括图片 视频等
再者就是io流使用了典型的装饰者模式,等等我去偷张图,侵删。
可以清晰的看出FilterInputStream就是装饰者,再不改变原有类的情况下,下面三个Data/Buffered/PushbackInputStream附加实现了不同的功能,比如BufferedInputStream实现了缓冲的功能,如果想深入了解,个人建议去看 https://www.jianshu.com/p/4a530a3c70af 。
具体使用来个例子吧清晰一点:
复制文件
FileInputStream in = new FileInputStream(src); FileOutputStream out = new FileOutputStream(dist); byte[] buffer = new byte[1024]; // read() 最多读取 buffer.length 个字节 返回的是实际读取的个数 返回 -1 的时候表示读到 eof,即文件尾 while (in.read(buffer) != -1) { out.write(buffer); } in.close(); out.close();
如果使用Reader的话,就是读取的用readLine就好了,就不弄样例了
接下来看看
Unix IO的一些模型
先说一下一个输入操作包括,
1.等待数据准备好
2.从内核读取数据,将数据复制到应用程序缓冲内,使用的方法是recvfrom
阻塞式IO:其实很好理解,就是应用程序一直等待直到数据返回,期间不做任何事情,但只是当前程序阻塞,并不是操作系统瘫痪,所以效率还是比较高的
非阻塞式IO:应用程序发送请求,1只要没完成就返回失败,然后进行轮询,轮询就是不停的去请求,这种的其实效率不高,而且应用程序耗费了很多的调用去干轮询这件事
IO复用:使用select或者poll等待数据,等某个套接字返回的时候,就使用recvfrom复制数据,这样的好处就是一个进行就可以处理多个IO事件
信号驱动IO:这个就是发送一个信号告诉内核,数据好了通知我,等数据准备i好了,内核就会发信号给应用程序,然后再recvfrom
异步IO:这个跟信号驱动很像,区别就是内核发信号的时候,信号驱动IO是开始recvfrom,而异步IO是recvfrom已完成。
所以也好理解,同步和异步IO的区别就在于第二步的recvfrom,同步会在recvfrom阻塞,而异步IO不会。
BIO(同步IO)
这个方式其实就类似于伪异步方式,客户端多个请求一起过来时,服务端开启异步线程处理客户端的请求,我写了个小程序用来理解:
客户端代码:
public class SocketClient { public static void main(String[] args) { for(int count = 10; count<20;count++) { new Thread(new Client(count)).start(); } } static class Client implements Runnable{ int count ; public Client(int count){ this.count = count; } @Override public void run() { InputStream inputStream =null; OutputStream outputStream =null; try { Socket socket = new Socket("localhost", 8083); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); //发送信息 outputStream.write((("go---"+count+"---").getBytes())); int len; byte[] bs = new byte[1024]; while ((len = inputStream.read(bs)) != -1) { String s = new java.lang.String(bs); System.out.println(s); } } catch (IOException e) { e.printStackTrace(); }finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
服务端代码:
/** * 多线程实现异步IO */ public class SocketService { public static void main(String[] args) { try { int count = 0; ServerSocket serverSocket = new ServerSocket(8083); Socket accept = null; while (true) { accept = serverSocket.accept(); //开启异步线程处理 new Thread(new Service(accept)).start(); } } catch (IOException e) { e.printStackTrace(); } } static class Service implements Runnable { Socket accept; public Service(Socket accept){ this.accept = accept; } @Override public void run() { try { InputStream in = accept.getInputStream(); int len; byte[] bytes = new byte[1024]; in.read(bytes); String s = new String(bytes); System.out.println(s); OutputStream out = accept.getOutputStream(); out.write(("comming---"+s+"---").getBytes()); in.close(); out.close(); accept.close(); } catch (IOException e) { e.printStackTrace(); } } }
NIO 多路复用:
实现就类似于上面所说的IO复用,使用轮询代理,具体使用要用到Selector,使用select等待请求,具体看程序:
Channel就是通道,也就是多路中的‘路’
还有一点要注意,普通的IO流使用byte[] 而NIO使用的则是缓冲BufferByte 方法allocate就类似byte数组长度,flip方法用来切换读写,以免导致脏读,clear用来清空,以免造成重复读
客户端代码比较简单:
public class NioSelectClient { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 8083); OutputStream outputStream = socket.getOutputStream(); outputStream.write("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".getBytes()); InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; inputStream.read(bytes); String s = new String(bytes); System.out.println(s); outputStream.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
服务端:
* 使用选择器实现nio 将服务端的非阻塞channel注册到选择器上,选择器监听通道(channel)内的事件,然后进行处理
* 实现了一个线程监听多个事件 选择器selector监听的方式也是轮询
public class NioSelectorService { public static void main(String[] args) { try { //创建一个选择器 Selector selector = Selector.open(); //创建管道网络服务端 ServerSocketChannel sChannel = ServerSocketChannel.open(); sChannel.configureBlocking(false);//非阻塞 //首次注册使用OP_ACCEPT //OP_CONECT已连接 OP_ACCEPT已获取数据 OP_READ表示可读取 OP_WRITE可操作 sChannel.register(selector, SelectionKey.OP_ACCEPT); ServerSocket socket = sChannel.socket(); socket.bind(new InetSocketAddress(8083)); while (true) { selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey next = iterator.next(); if (next.isAcceptable()) { iterator.remove(); System.out.println("1-accept"); ServerSocketChannel channel = (ServerSocketChannel) next.channel(); SocketChannel accept = channel.accept(); accept.configureBlocking(false); accept.register(selector, SelectionKey.OP_READ); } else if (next.isReadable()) { iterator.remove(); System.out.println("2-read"); SocketChannel channel = (SocketChannel) next.channel(); StringBuffer data = new StringBuffer(); ByteBuffer allocate = ByteBuffer.allocate(1024); int len; while ((len = channel.read(allocate)) != 0) { allocate.flip(); int position = allocate.position();//目前读的位置 int capacity = allocate.capacity();//总量 byte[] bytes = new byte[capacity]; allocate.get(bytes, position, len); String s = new String(bytes); data.append(s); allocate.clear(); } System.out.println(data); channel.write(ByteBuffer.wrap("3-返回:".getBytes())); channel.close(); } } } } catch (IOException e) { e.printStackTrace(); } } }
除了select还可以使用poll,epoll
select window和linux都支持 适用于要求实时性高的场景
poll linux 适用于小量请求监控
epoll linux linux没有实现异步IO,所以只能使用epoll模拟 适用于大量请求监控
AIO(异步IO)
再强调一下,linux不支持异步IO,只能通过epoll模拟
AIO采用的就是典型的异步IO ‘监听-通知’:下面这段代码可以改进,可以将读数据也弄成监听
客户端:
public class Client {
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
Socket socket = new Socket("localhost", 8083);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("client2 go ".getBytes());
outputStream.close();
}
}
public class AsynchronousServiceSocket { static final Object obj = new Object(); public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(6); try { //创建一个异步IO通道,绑定一个线程池 AsynchronousServerSocketChannel open = AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withThreadPool(executorService)); AsynchronousServerSocketChannel localhost = open.bind(new InetSocketAddress("localhost", 8083)); //监听-通知机制 但是是一次性的 localhost.accept(2, new ServerSocketChannelDeal(open)); synchronized (obj){ try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } static class ServerSocketChannelDeal implements CompletionHandler<AsynchronousSocketChannel, Integer> { private AsynchronousServerSocketChannel serverSocketChannel; public ServerSocketChannelDeal(AsynchronousServerSocketChannel serverSocketChannel) { this.serverSocketChannel = serverSocketChannel; } @Override public void completed(AsynchronousSocketChannel result, Integer attachment) { //因为是一次性的所以要每次都注册一次 serverSocketChannel.accept(attachment, this); int len = 0; ByteBuffer bbs = ByteBuffer.allocate(50); StringBuffer sb = new StringBuffer(); while (true) { Future<Integer> read = result.read(bbs); try { len= read.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } if ( len== -1) { break; } byte[] bs = new byte[50]; int position = bbs.position(); bbs.flip(); bbs.get(bs, 0, len); bbs.clear(); sb.append(new String(bs)); } System.out.println(sb); result.write(ByteBuffer.wrap("server got it".getBytes())); } @Override public void failed(Throwable exc, Integer attachment) { } } }
来源:oschina
链接:https://my.oschina.net/u/4291478/blog/3190934