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