关于java的NIO(二)
在上篇博客介绍了NIO的基础概念,这篇博客就用代码来演示NIO到底怎么用
本文内容
- 使用NIO实现普通文件读写
- 使用NIO实现高并发服务器
使用NIO实现普通文件读写
普通的BIO操作有很多,我就只演示基础的文件读写,我们要做的就是把一个文件的内容读取出来,然后写入到另一个文件中
首先准备两个空文件a.txt和b.txt,在a.txt文件中随便输入些内容,然后我们把a.txt中的内容写入到b.txt中去
然后上代码
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);,这句话的意思就是创建一个缓冲数组,里面的参数就是代表数组的容量,就是我上篇博客写的关于缓冲区的四个属性,其中之一的容量,在创建时指定,并且永远不能更改
运行结果
可以看出来,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有了一个比较全面的了解,至少再被问的时候不会一问三不知了
- 在查NIO的时候,其实还有一些别的收获,比如java的IO模型一共有五种,完全阻塞(BIO),非阻塞(NIO),异步IO(AIO),IO多路复用,SIGIO,所以可以看得出来,IO操作在java中还是很重要的一个操作
- 最后推荐一下我查资料的时候发现的好的网址吧,这是一个连载的,感觉写的还挺好的,除了NIO,还有很多别的知识点的解释
第一次面试总结
- 本来还打算写一篇关于网络基础的博客,但是自己对网络部分又不是很熟悉,所以怕写的不对,所以就没写了
- 第一次投递阿里的简历,算是失败了,但是也的得到了很多经验,发现了自己对于基础部分的不扎实,然后技术面试的时候大概会问些什么东西,心里也有个数了,不会再像这次一样了
- 面试经验以后应该还会继续更新吧,主要写自己不熟悉的知识点,加深自己的印象