JAVA SE(二十二)—— I/O 流3(文件流:NIO)

一、NIO基本概念

1、NIO介绍

  • NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。
  • NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。

2、NIO 核心部分组成

  • Channels:负责连接
  • Buffers: 负责数据的存取
  • Selectors

3、与IO的区别

  • IO面向流(输入流、输出流,单向的),NIO面向缓冲区;
  • IO是阻塞式的,NIO是非阻塞式的;
  • IO无通道,NIO有通道(Channel),双向流通,既可读,也可写,Channel相当于铁路(不能运输东西),必须借助火车运输(缓冲区);
  • IO无选择器,NIO有选择器(Selector)。

二、NIO缓冲区Buffer

1、在java里为除boolean外的基本数据类型都提供了一个缓冲区

ByteBuffer 
ShortBuffer 
IntBuffer 
LongBuffer 
DoubleBuffer 
FloatBuffer

2、Buffer读写数据步骤
(1)写入数据到Buffer;
(2)调用flip()方法;
(3)从Buffer中读取数据;
(4)调用clear()方法或者compact()方法。

3、Buffer四个属性
(1)private int mark = -1;标记

(2)private int position = 0;位置
初始值为0,最大可为capacity - 1,位置会根据get()和put()发生改变。
当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。

(3)private int limit;上限
缓冲区第一个不能被读写的元素的位置
当在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时,limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。

(4)private int capacity;容量
缓冲区能够容纳的最大数量,一旦被创建就不可改变。
一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

4、Buffer基本方法
(1)获取缓冲区:allocate()

ByteBuffer allocate = ByteBuffer.allocate(1024);

(2)存入元素: put()

allocate.put("abcdef".getBytes())

(3)取出元素:get()
使用get方法前,必须将缓冲区切换成读取模式,使用flip()方法,切换后,limit会变成已经存入元素的个数,position变为0。
(4)切换模式:allocate.flip()
取出元素 byte[] bytes = new byte[10]; byte[]里面的数字不能超过limit

allocate.get(bytes);

(5)重写读,做标记: mark() 恢复到标记的位置: reset()

allocate.mark();
allocate.reset();

(6)重复读取:rewind()

allocate.rewind();

(7)重置缓冲区:clear()
回到最初的状态,但里面的数据还在,处于被遗忘的状态

allocate.clear();

5、注意
(1)当向buffer写入数据时,buffer会记录下写了多少数据。
一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。
在读模式下,可以读取之前写入到buffer的所有数据。

(2)一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
有两种方式能清空缓冲区:调用clear()或compact()方法。
clear()方法会清空整个缓冲,
compact()方法只会清除已经读过的数据。
任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

6、直接缓冲区和非直接缓冲区
(1)直接缓冲区将缓冲区建立在物理内存中,可以提高效率。不安全

(2)非直接缓冲区将缓冲区建立在JVM中
创建直接缓冲区: allocateDirect()

ByteBuffer allocateDirect = ByteBuffer.allocateDirect(1024);

7、Demo

public class NIODemo {
	public static void main(String[] args) {
		//获取缓冲区 allocate()
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		
		//存入元素 put()
		allocate.put("abcdef".getBytes());
		
		//取出元素 get(),使用get方法前,必须将缓冲区切换成读取模式
		allocate.flip();	//切换模式
		byte[] bytes = new byte[3];
		allocate.get(bytes);			//get(byte[] dst) 
		System.out.println(new String(bytes));
		System.out.println((char)allocate.get(2));	//get(int index)
		
		//重新读 做标记mark()
		allocate.mark();

		重复读取
		allocate.rewind();
		
		//恢复到标记的位置
		allocate.reset();
		
		//重置缓冲区 clear(),里面的数据还在,处于被遗忘的状态
		allocate.clear();
		byte[] bytes1 = new byte[3];
		allocate.get(bytes1);
	}
}

三、NIO通道Channel

1、NIO通道Channel
NIO通道Channel是负责连接的,IO源与目标打开的连接,与传统IO流类似,只是Channel本身不能直接访问,必须借助缓冲区。

2、Java提供的通道

FileChannel
SocketChannel
ServerSocketChannel
DatagramChannel

3、获取通道:
(1)getChannel()

(2)open()

public class NIOChannelDemo {
	public static void main(String[] args) throws IOException {
		//getChannelDemo();
		//openDemo();
	}
	//获取通道方法1:getChannel()方法示例
	public static void getChannelDemo() throws IOException {
		FileInputStream fis = new FileInputStream("D:\\test\\aa.jpg");
		FileOutputStream fos = new FileOutputStream("D:\\test1\\aa.jpg");
		//获取通道
		FileChannel inchannel = fis.getChannel();
		FileChannel outchannel = fos.getChannel();
		//创建缓冲区
		ByteBuffer bb = ByteBuffer.allocate(1024);
		//循环读取
		while(inchannel.read(bb) != -1) {
			//切换成读模式
			bb.flip();
			outchannel.write(bb);
			//重置缓冲区
			bb.clear();
		}
		inchannel.close();
		outchannel.close();
		fis.close();
		fos.close();
	}
	//获取通道方法2:open()方法
	public static void openDemo() throws IOException {
		FileChannel inChannel = FileChannel.open(Paths.get("D:\\test\\aa.zip"), StandardOpenOption.READ);		//读的操作
		FileChannel outChannel = FileChannel.open(Paths.get("D:\\test1\\aa.zip"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);		//操作
		inChannel.transferTo(0, inChannel.size(), outChannel);	//position 位置
		
		inChannel.close();
		outChannel.close();
	}
}

4、Demo1:NIOChannel文件复制
(1)服务器端

public class SelectorServer {
	public static void main(String[] args) throws IOException {
		ServerSocketChannel open = ServerSocketChannel.open();
		//绑定端口
		open.bind(new InetSocketAddress(12306));
		FileChannel open2 = FileChannel.open(Paths.get("D:\\test1\\aa.zip"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		SocketChannel accept = open.accept();
		while(accept.read(allocate) != -1) {
			allocate.flip();
			open2.write(allocate);
			allocate.clear();
		}
		//关流
		accept.close();
		open2.close();
		open.close();
	}
}

(2)客户端

public class Client {
	public static void main(String[] args) throws IOException {
		SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1",12306));
		//文件的通道
		FileChannel open2 = FileChannel.open(Paths.get("D:\\test\\aa.zip"), StandardOpenOption.READ);
		//创建缓冲区
		ByteBuffer bb = ByteBuffer.allocate(1024);
		while(open2.read(bb) != -1) {
			bb.flip();
			open.write(bb);
			bb.clear();
		}
		//关闭连接
		open.close();
		open2.close();
	}
}

5、Demo2:NIOChannel聊天
(1)聊天室服务器端

public class ChatServer {
	public static void main(String[] args) throws IOException {
		//建立连接,打开通道
		ServerSocketChannel open = ServerSocketChannel.open();
		//绑定端口号
		open.bind(new InetSocketAddress(12306));
		//创建缓冲区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		System.out.println("服务器已就绪。。。");
		Scanner scanner = new Scanner(System.in);
		//接收客户端的连接
		SocketChannel sc = open.accept();
		//监听连接
		while(true) {
			//读取
			sc.read(allocate);
			////切换模式
			allocate.flip();
			System.out.println(new String(allocate.array(), 0, allocate.limit()));
			allocate.clear();
			//写回去
			String next = "服务器说:" + scanner.next();
			//存入缓冲区
			allocate.put(next.getBytes());
			//切换成写模式
			allocate.flip();
			sc.write(allocate);
			allocate.clear();
		}
	}
}

(2)聊天室客户端

public class ChatClient {
	public static void main(String[] args) throws IOException, InterruptedException {
		//与服务端建立连接,打开通道
		SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1",12306));
		//创建缓冲区
		ByteBuffer bb = ByteBuffer.allocate(1024);
		//输入信息
		Scanner sc = new Scanner(System.in);
		System.out.println("已连接到服务器。。");
		//向服务端发送信息
		while(true) {
			String message = "客户端说" + sc.next();
			bb.put(message.getBytes());
			//切换成读模式
			bb.flip();
			open.write(bb);
			bb.clear();
			//接收服务器返回的信息
			open.read(bb);
			//切换
			bb.flip();
			System.out.println(new String(bb.array(), 0, bb.limit()));
			bb.clear();
		}
	}
}

四、NIO选择器Selector

1、NIO选择器Selector
Selector是NIO的核心,解决阻塞问题,是Channel的多路复用器,用于检测通道

2、Selector的创建 open()

Selector selector = Selector.open();

3、Demo:NIOSelector聊天室
(1)服务器端

public class SelectorClient {
	public static void main(String[] args) throws IOException {
		//建立连接,打开通道
		ServerSocketChannel open = ServerSocketChannel.open();
		//绑定端口号
		open.bind(new InetSocketAddress(12306));
		//将服务器的通道切换成非阻塞模式
		open.configureBlocking(false);
		//创建选择器
		Selector select = Selector.open();
		//服务端的通道是用来接收客户端的连接,应该把连接事件注册到选择器
		open.register(select, SelectionKey.OP_ACCEPT);	//sel表示选择器,ops表示注册的事件
		
		//轮询选择器上所注册的事件
		while(select.select() > 0) {
			//取出选择器上的所有事件,对事件进行判断
			Set<SelectionKey> selectedKeys = select.selectedKeys();
			//遍历Set集合
			Iterator<SelectionKey> it = selectedKeys.iterator();
			while(it.hasNext()) {
				//取出给key
				SelectionKey key = it.next();
				it.remove();
				if(key.isAcceptable()) {		//如果该事件是一个接收客户端连接的事件,就接收客户端的连接
					connect(open, select);
				}
				if(key.isReadable()) {		//如果该事件是一个可读的事件,则读取客户端的数据
					read(select, key);
				}
			}
		}
	}
	//接收连接事件
	public static void connect(ServerSocketChannel open,Selector select ) throws IOException {
		SocketChannel accept = open.accept();
		//将接收到的通道切换成非阻塞模式
		accept.configureBlocking(false);
		//将该通道的读取事件注册到选择器
		accept.register(select, SelectionKey.OP_READ);
	}
	//读取数据事件
	public static void read(Selector select,SelectionKey key) throws IOException {
		//首先取出注册该事件的通道
		SocketChannel channel = (SocketChannel)key.channel(); 		//SelectableChannel是SocketChannel的父类,所以必须向下转型
		//创建缓冲区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		while(channel.read(allocate) > 0) {
			allocate.flip();
			//拿到数据后,发给所以客户端,所以要拿到所有客户端的通道
			Set<SelectionKey> keys = select.keys();
			//判断是否是SocketChannel的通道
			for(SelectionKey ch: keys) {
				SelectableChannel channel2 = ch.channel();
				//对这个通道进行判断  channel2是否是SocketChannel的实例化对象
				if(channel2 instanceof SocketChannel) {
					//如果是的话要强转
					SocketChannel schannel = (SocketChannel)channel2;
					schannel.write(allocate);
					//重复写
					allocate.rewind();
				}
			}
			//将数据转发给所有客户端后,重置缓冲区
			allocate.clear();
		}
	}
}

(2)客户端

public class SelectorClient {
	public static void main(String[] args) throws IOException {
		//与服务端建立连接,打开通道
		SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1",12306));
		//将客户端通道切换成非阻塞模式
		open.configureBlocking(false);
		//创建缓冲区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		//创建Scanner对象
		Scanner sc = new Scanner(System.in);
		getMessage(open);
		while(true) {
			String next = sc.next();
			//将数据存入缓冲区
			allocate.put(next.getBytes());
			//切换成读模式
			allocate.flip();
			open.write(allocate);
			allocate.clear();
		}
	}
	
	//创建一个定时器,不断接收服务端发过来的信息
	public static void getMessage(SocketChannel open) {
		Timer time = new Timer();
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		time.schedule(new TimerTask() {
			
			@Override
			public void run() {
				try {
					int read = open.read(allocate);
					if(read > 0) {
						allocate.flip();
						System.out.println(new String(allocate.array(), 0, allocate.limit()));
						allocate.clear();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}, 50,50);
	}
}
发布了40 篇原创文章 · 获赞 0 · 访问量 352

猜你喜欢

转载自blog.csdn.net/baidu_27414099/article/details/104425646