关于java的NIO(二)

在上篇博客介绍了NIO的基础概念,这篇博客就用代码来演示NIO到底怎么用


本文内容
  1. 使用NIO实现普通文件读写
  2. 使用NIO实现高并发服务器
使用NIO实现普通文件读写

普通的BIO操作有很多,我就只演示基础的文件读写,我们要做的就是把一个文件的内容读取出来,然后写入到另一个文件中

首先准备两个空文件a.txtb.txt,在a.txt文件中随便输入些内容,然后我们把a.txt中的内容写入到b.txt中去

java写文件很快的方法_java写文件很快的方法


java写文件很快的方法_NIO_02


然后上代码

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 
 * @author Dong
 *
 */
public class Move {
	
	public static void move(File in,File out) throws IOException{
		FileInputStream fis = null;
		FileOutputStream fos = null;
		FileChannel fcin = null;
		FileChannel fcout = null;
		
		ByteBuffer bb = ByteBuffer.allocate(1024);
		
		try {
			fis = new FileInputStream(in);
			fos = new FileOutputStream(out);
			fcin = fis.getChannel();
			fcout = fos.getChannel();
			
			while(fcin.read(bb) > 0){
				bb.flip();
				fcout.write(bb);
				bb.clear();
			}
		} catch (FileNotFoundException e) {
			System.out.println("出现错误");
			e.printStackTrace();
		} finally{
			if(fis != null){
				fis.close();
			}
			if(fos != null){
				fos.close();
			}
			if(fcin != null){
				fcin.close();
			}
			if(fcout != null){
				fcout.close();
			}
		}
	}
}

import java.io.File;
import java.io.IOException;

/**
 * 
 * @author Dong
 *
 */
public class Test {

	public static void main(String[] args) {
		
		String[] paths = {
				"C:\\Users\\15824\\Desktop\\tmp\\a.txt",
				"C:\\Users\\15824\\Desktop\\tmp\\b.txt"
		};
		
		try {
			Move.move(new File(paths[0]), new File(paths[1]));
			System.out.println("操作成功");
		} catch (IOException e) {
			System.out.println("出现错误");
			e.printStackTrace();
		}

	}
}

代码很简单,有一个地方要讲一下,ByteBuffer bb = ByteBuffer.allocate(1024);,这句话的意思就是创建一个缓冲数组,里面的参数就是代表数组的容量,就是我上篇博客写的关于缓冲区的四个属性,其中之一的容量,在创建时指定,并且永远不能更改

运行结果

java写文件很快的方法_高并发服务器_03


java写文件很快的方法_高并发服务器_04


java写文件很快的方法_文件NIO_05

可以看出来,NIO用在文件操作是有点大材小用了,尽管NIO的处理速度会比BIO的速度要快(代码中没有体现,感兴趣的可以自己去试一下),而且NIO里面只有FileChannel 是不能设置成非阻塞,所以文件操作并不是NIO的主要用途

使用NIO实现高并发服务器

下面我们来写一个高并发的服务器,我们都知道,普通的socket编程,里面要通过ServerSocket对象的accept()方法对服务端口进行监听,这个方法是阻塞的,当没有连接的时候,就会一直等待,直到有连接,而且每次监听到连接就会创建一条线程,然后放入线程池,在线程开启后,又会等待连接的数据传输,也就是会阻塞在getInputStream()的read()方法,可以看出来,服务器大部分时间都在等待,资源浪费比较多,而且在上篇博客就介绍过了用多线程去解决高并发会带来的问题,所以我们使用NIO来实现高并发的服务器

上代码

服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

/**
 * 
 * @author Dong
 *
 */
public class NIOServer {

	private Selector selector = null;
	private static final Charset CHARSET = Charset.forName("UTF-8");

	public void start() throws IOException{
		selector = Selector.open();
		ServerSocketChannel ssc = ServerSocketChannel.open();
		InetSocketAddress isa = new InetSocketAddress("localhost",8000);
		ssc.socket().bind(isa);

		//设置ServerSocket为非阻塞
		ssc.configureBlocking(false);

		//注册到指定Selector对象
		ssc.register(selector, SelectionKey.OP_ACCEPT);

		System.out.println("服务器开始监听通道.....");
		// 监听所有通道
		while(selector.select() > 0){
			System.out.println("服务器有通道有数据.....");
			//可能存在同一时间多个连接,所以需要遍历所有通道
			for(SelectionKey sk:selector.selectedKeys()){
				if(sk.isAcceptable()){
					//是连接请求
					System.out.println("服务器来了一个连接.....");
					//接受连接,创建SocketChannel对象
					SocketChannel sc = ssc.accept();
					sc.configureBlocking(false);
					sc.register(selector, SelectionKey.OP_READ);
					//将sk对应的Channel设置成准备接受其他请求
					sk.interestOps(SelectionKey.OP_ACCEPT);
				}
				else if(sk.isReadable()){
					//是发送的数据
					System.out.println("服务器收到一条数据.....");
					//获取SelectionKey对应的Channel,并读取该Channel中的数据
					SocketChannel sc = (SocketChannel) sk.channel();
					ByteBuffer buff = ByteBuffer.allocate(1024);
					StringBuilder sb = new StringBuilder();

					try{
						while(sc.read(buff) > 0){
							buff.flip();
							sb.append(CHARSET.decode(buff));
						}
						System.out.println("接收到客户端的数据为:" + sb);
						sk.interestOps(SelectionKey.OP_READ);
					}catch(IOException e){
						System.out.println("服务器有一个客户端连接中断....");
						//说明连接中断,删除该通道
						sk.cancel();
						if(sk.channel() != null){
							sk.channel().close();
						}
					}

					//如果接收到的数据不为空,服务器转发信息到所有客户端
					if(sb.length() > 0){
						for(SelectionKey key : selector.keys()){
							Channel targetChannel = key.channel();

							//selector管理的通道有两种,一种是负责等待连接的ServerSocketChannel,一种是负责数据传输的SocketChannel
							if(targetChannel instanceof SocketChannel){
								SocketChannel dest = (SocketChannel) targetChannel;
								dest.write(CHARSET.encode(sb.toString()));
							}
						}
					}
				}
				
				//从selector监听到的通道中将已经处理的通道移除
				selector.selectedKeys().remove(sk);
			}
		}
	}


	public static void main(String[]args) throws IOException{
		new NIOServer().start();
	}
}

客户端

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.nio.charset.Charset;
import java.util.Scanner;

/**
 * 
 * @author Dong
 *
 */
public class NIOClient {

	private Selector selector = null;
	private static final Charset CHARSET = Charset.forName("UTF-8");
	private SocketChannel sc = null;

	public void start() throws IOException{
		selector = Selector.open();
		InetSocketAddress isa = new InetSocketAddress("localhost",8000);
		sc = SocketChannel.open(isa);

		//设置非阻塞
		sc.configureBlocking(false);
		//注册到指定Selector
		sc.register(selector, SelectionKey.OP_READ);

		//创建一个线程接收服务器传递的数据
		new Thread(){
			public void run() {
				try{
					System.out.println("客户端开始监听通道.....");
					while(selector.select() > 0){
						for(SelectionKey sk : selector.selectedKeys()){
							// 客户端只有传送数据的通道
							if(sk.isReadable()){
								System.out.println("客户端收到一条数据.....");
								//使用NIO读取数据
								SocketChannel sc = (SocketChannel) sk.channel();
								ByteBuffer buff = ByteBuffer.allocate(1024);
								StringBuilder sb = new StringBuilder();
								while(sc.read(buff) > 0){
									buff.flip();
									sb.append(CHARSET.decode(buff));
								}
								System.out.println("接收到服务器的数据为:" + sb);
								sk.interestOps(SelectionKey.OP_READ);
							}
							
							selector.selectedKeys().remove(sk);
						}
					}
				}catch(IOException ex){
					ex.printStackTrace();
				}
			};
		}.start();

		Scanner in = new Scanner(System.in);
		while(in.hasNextLine()){
			String line = in.nextLine();
			sc.write(CHARSET.encode(line));
		}
	}
	
	public static void main(String[]args) throws IOException{
		new NIOClient().start();
	}
}

虽然在代码量上面没有多少减少,但是可以看出来,在服务端一个selector管理了不止一种通道,就和上篇博客拿快递的例子一样,在效率方面实际上是有了很大的提升

运行结果

java写文件很快的方法_文件NIO_06


java写文件很快的方法_java写文件很快的方法_07

总结
  • 这次算是对java的NIO有了一个比较全面的了解,至少再被问的时候不会一问三不知了
  • 在查NIO的时候,其实还有一些别的收获,比如java的IO模型一共有五种,完全阻塞(BIO),非阻塞(NIO),异步IO(AIO),IO多路复用,SIGIO,所以可以看得出来,IO操作在java中还是很重要的一个操作
  • 最后推荐一下我查资料的时候发现的好的网址吧,这是一个连载的,感觉写的还挺好的,除了NIO,还有很多别的知识点的解释


第一次面试总结
  • 本来还打算写一篇关于网络基础的博客,但是自己对网络部分又不是很熟悉,所以怕写的不对,所以就没写了
  • 第一次投递阿里的简历,算是失败了,但是也的得到了很多经验,发现了自己对于基础部分的不扎实,然后技术面试的时候大概会问些什么东西,心里也有个数了,不会再像这次一样了
  • 面试经验以后应该还会继续更新吧,主要写自己不熟悉的知识点,加深自己的印象