NIO

19 篇文章 0 订阅
2 篇文章 0 订阅

声明:学基础,在校学生,本文所有内容来自书本和视频,然后通过自己的理解和筛选编写而来,如有理解不到位的写得不到位的地方,欢迎评论指错!!!(仅做学习交流)
笔者:Fhvk
微信:WindowsC-

所要知道的

  • 同步:同步就是发起一个调用后,被调用者未处理完请求之前调用不返回。
  • 异步:异步就是发起一个调用后,立刻得到被调用者的回应表示己接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通道依靠事件,回调等机制来通知调用者其返回结果;
  • 同步和异步的区别最大在于异步的调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果;
  • 阻塞:阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续;
  • 非阻塞:非阻塞就是发起一个请求,调用者不用一直等待着结果返回,可以先去做其他事情;
  • 同步阻塞、同步非阻塞和异步非阻塞以代表什么意思?
    举个生活的例子,你妈妈让你烧水,小时候你比较笨,在那时傻等着水开(同步阻塞)。等你稍微再长大一点,你知道每次烧水的空隙可以干点别的事情,然后只要时不时回来看一下就好(同步非阻塞)。后来,你们家用上了水开了会发出警报的壶,这样你就只要听到响声后就知道水开了,在这期间你可以随便干自己的事情(异步非阻塞);

NIO

  • 为了提升服务器操作的性能,JAVA提供了一个新的开发包NIO,此包定义在java.nio。*中
  • 在整个JAVA中已有的IO操作大部分都属于阻塞的操作,例如:键盘输入数据,必须一直等待用户输入数据,否则程序无法向下继续执行。Socket程序设计中所有的服务器必须通过accpet()方法一直等待用户的连接,那么这样一来肯定会造成大量的系统资源浪费;所以JAVA在JDK1.4后增加了NIO,在整个NIO中基本上都是使用缓冲区完成的;

缓冲区(Buffer)

  • 在基本IO操作中所有的操作都是直接以流的形式完成的,而在NIO中所有的操作都要使用到缓冲区处理,所有的读写操作都是通过缓冲区来完成的,缓冲区是一个线性的、有序的数据集,只能容纳某种特定的数据类型。
  • 各种数据类型的缓冲区类
    在这里插入图片描述
    都是抽象类,但有静态方法用于返回对应的缓冲区,没有boolean类型
  • 如何使用缓冲区(以IntBuffer为例)
public calss Demo_IntBuffer {
	public static void main(String[] agrs) {
			IntBuffer bur = IntBuffer.allocate(10);//指定缓冲区大小
			System.out.print("1、写入数据之前的 : position、limit和capacity : ");
			System.out.println("position : " + buf.position() + ", limit : " + buf.limit() + ", capacity : " + buf.capacity());
			
			int[] arr = {1, 2, 3};  //定义一个int 数组
			buf.put(arr);  //往里压数据
			buf.put(4);//此时已经存放了四个记录
			System.out.print("2、写入数据之后的 : position、limit和capacity : ");
			System.out.println("position : " + buf.position() + ", limit : " + buf.limit() + ", capacity : " + buf.capacity());
			
			 buf.filp(); //重设缓冲区
			 //position 重置为0,limit为原本的position,capacity不变
			 System.out.print("3、 准备输出数据时的: position、limit和capacity : ");
			System.out.println("position : " + buf.position() + ", limit : " + buf.limit() + ", capacity : " + buf.capacity());
			System.out.print("缓冲区的内容:");
			while(buf.hasRemaining()) {
				System.out.print(buf.get() + ", ");
			}
	}
}
/*
在Buffer中存在一系列的状态变量,这些状态变量随着写入或读取都有可能会改变,在缓冲区中可以使用三个值表示缓冲区的状态:
position:表示下一个缓冲区读取或写入的操作指针,每向缓冲区写入数据时此批针就会改变,例如:如果写入了4个位置的数据,那么指针指向第5个;
limit:表示还有多少数据需要存储或读取, position <= limit
capacity:表示缓冲区最大容量,limit<=capacity 。此值在分配缓冲区时设置,可以改但不会去改;
  • 常用方法
    1、public final int capcity();返回此缓冲区的容量
    2、public final int limit();返回此缓冲区的限制大小
    3、public final Buffer limit(int newLimit);设置缓冲区大小
    4、public final int position();返回缓冲区的操作位置
    5、public final Buffer position(int newPosition);设置缓冲区的操作位置
    6、public final Buffer clear();清空缓冲区
    7、public final Buffer flip();重设缓冲区在写入之前调用,改变缓冲区的指针
    8、public final boolean hasRemaining();判断当前位置和限制之间是否有内容
    9、public final Buffer reset();恢复缓冲区中的标记位置
  • 子缓冲区(用于修改数据)
public class Demo_IntBuffer {
	public static void main(String[] agrs) {
		IntBuffer buf = IntBuffer.allocate(10);  //主缓冲区
		Intbuffer suf = null;   //子缓冲区
		for(int i = 0; i < 10; i++) {
			buf.put(i * 2 + 1);   //在主缓冲区创建10个奇数
		}
		buf.position(2);
		buf.limit(6);
		sub = buf.slice();  //通过slice()方法设置子缓冲的
		for(int i = 0; i < sub.capacity(); i++) {
			int temp = sub.get(i);
			sub.put(temp - 1);
		}
		buf.flip(); //重设缓冲区
		buf.limit(buf.capacity()); //重设limit
		System.out.print("主缓冲内容:");
		while(buf.hasRemaining()) {
			System.out.print(buf.get() + "", ");
		}
	}
}
  • 如果创建的缓冲区不希望被修改,则可以创建只读缓冲区;
public class Demo_IntBuffer {
	public static void main(String[] agrs) {
		IntBuffer buf = IntBuffer.allocate(10);
		IntBuffer read = null;  //声明只读缓冲区
		for(int i = 0; i < 10; i++) {
			buf.put(i);
		}
		read = buf.asReadOnlyBuffer();//创建只读缓冲区
		read.flip();
		while(read.hasRemaining()) {
			System.out.print(read.get() + ", ");
		}
		read.put(1);  //报错因为此缓冲区是只读的,不能修改
	}
}
  • 直接缓冲区,用于尽可能提升性能
语法:上面没有差异
public class Demo_ByteBuffer {
	public static void main(String[] agrs) {
		ByteBuffer buf = ByteBuffer.allocateDirect(10);
		byte[] arr = {1, 2, 3, 4};
		buf,put(arr);
		buf.flip();
		while(buf.hasRemaining()) {
			System.out.print(buf.get() + ", ");
		}
	}
}
  • 总结:一定要明NIO中都是以缓冲区进行操作的;缓冲区争对各个基本数据类型都有实现,除了boolean;缓冲区中存在position\limit\capacity三种状态变量;缓冲区的操作通过allocate()创建缓冲区,或使用直接和只读缓冲区

通道操作

  • 在NIO中通道是一个可以用读取和写入数据的一种形式
  • 通道可以用来读取和写入数据,通道类似于之前的输入\输出流,但是程序不会直接操作通道,所有内容都是先读到或写入到缓冲区中,再通过缓冲区中取得或写入;
  • 通道与传统的操作流不同,传统的流分为输入或输出流,而通道就是一根双向流操作的,可以完成输入,也可以完成输出;
  • 如何实现
    用实现了Channel接口的类
    而FileChannel就实现了此接口的定义,就是一个双向流
    1、public int read(ByteBuffer dst);将内容读到缓冲区中
    2、public int write(ByteBuffer src);将内容从缓冲区中写到通道
    3、public final boolean close(); //关闭通道
    4、public abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size);将通道文件区域映射到内存之中,同时指定映射模式、文件中的映射文件以及要映射的区域大小;
  • FileOutputStream\FileInputStream都支持通道,用它俩做方例如下
//读写操作
public class Demo_FilChannel {
	public static void main(String[] agrs) throws IOException{
		File file1 = new File("put.txt");
		File file2 = new File("out.txt");
		FileInputStream fis = new FileInputStream(file1);
		FileOutputStrean fos = new FileOutputStream(file2);
		FileChannel fin = fis.getChannel();
		FileChannel fon = fos.getChannel();
		ByteBuffer buf = ByteBuffer.allocate(1024);
		int b = 0;
		while((b = fin.read(buf)) != -1) {
			buf.flip();
			fon.write(buf);
			buf.clear();  //清空缓冲区
		}
		fis.close();
		fos.close();
		fin.close();
		fon.close(); 
	}
}
  • 内存映射:内存映射可以把文件映射到内存之中,这样文件内的数据就可以内存读\写指令来访问,而不是用InputStrean\OutputStream这样的IO流操作,此种方法的读取文件速度是最快的;
    RandomAccessFile 较慢
    FileInputStream 比较慢
    缓冲区读取. 速度较快
    内存映射. 速度最快
    如何实现:
    FIleChannel类的三种内存映射模式:
    READ_ONLY:只读映射模式
    READ_WRITE:读取和写入映射模式
    PRIVATE:专用(写入拷贝时)映射模式
public class Demo04_FileChannel {
	public static void main(String[] agrs) throws IOException{
		File file = new File("put.txt");
		FileInputStream fis = new FileInputStream(file);
		FileChannel fin = fis.getChannel();
		MappedByteBuffer mbb = fin.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
		byte arr[] = new byte[(int)file.length()];
		int flag = 0;
		while(mbb.hasRemaining()) {
			arr[flag++] = mbb.get();
		}
		System.out.println(new String(arr));
		fin.close();
		fis.close();
	}
}
  • 总结:掌握通道的使用,一定要明白通道是双向的,可读可写;内存映射读取速度是最快的,只适合读取;通道的操作都是以缓冲区为主的;

文件锁

  • 比如在word中经常会出现多个人同时打开同一个word文件的情况,那么此时,只有第一个人可以进行编辑,而第二个人则必须要么以只读的形式打开,要么关闭;JAVA可以通过文件锁来实现

  • FileLock:在NIO中提拱了文件锁功能;这样当一个线程将文件锁定后。其它线程是无法操作此文件的,要想进行文件的锁操作,则要使用FileLock类完成,此类的对象需要依靠FileChannel进行实例化操作;
    public final FileLock lock();//获得此通道的文件的独占锁定
    public abstract FileLock lock(long psoition, long size, boolean shared);//获得此通道文件给定区域的锁定,并指定锁定位置,锁定大小,是共享锁定(true)或独占锁定(false)
    public final FileLock tryLock();//获取此通道的独占锁定
    public abstract FileLock tryLock(long position, long size, boolean shared)//获得此通道文件给定区域的锁定,并指定锁定位置,锁定大小,是共享锁定(true)或独占锁定(false)

  • 关于锁定方式
    共享锁:允许多个线程进行文件的读取操作
    独占锁:只允许一个线程进行文件的读/写操作

  • 常用的方法
    public final boolean isSared();判断是否为共享锁定
    public final FileChannel channel();返回此锁定的通道
    public abstract void release();解锁(释放锁定)
    public final long size();返回锁定区域的大小

public class Demo_FileLock {
	public static void main(String[] agrs) {
		File file = new File("a.txt");
		FileOutputStream fos = new FileOutputStream(file,true);
		FileChannel fon =  fos.getChannel();
		FileLock flock = fon.trylock();
		if(flock != null) {
			System.out.println(file.getName() + "文件锁定10秒");
			Thread.sleep(10000);
			flock.release();
			System.out.println("解除锁定");
		}
		fos.close();
		fon.close();
	}
}
  • 总结:了解FileLock类的操作;通过FileChannel获取FileLock的实例化对象

字符集

  • 在NIO中,对于不同平台的编码操作,JAVA都可以进行自动适应,因为可以使用字符集进行字符编码的转换操作
  • Charset
    在java中语言中所有的信息都是以UNICODE进行编码的,但是在计算机的世界里并不只单章一个编码,而是多个,而且要是对编码处理不好的话,则有可能产生乱码,在JAVA的NOI中提拱了Charset类来负责处理编码的问题,该类包含了创建编码器(CharsetEncoder)和创建解码器(CharsetDecoder)的操作;
  • 通过代码来获取全部支持的操作编码
public class Demo_Charset {
	public static void main(String[] agrs) {
		SortedMap<String, Charset> all = Charset.availableCharsets();
		Iterator<Map.Entry<String, Charset>> iter = all.entrySet().iterator();
		while(iter.hasNext()) {
			Map.Entry<String, Charset> me = iter.next();
			System.out.println(me.getKey() + "-->" + me.getValue());
		}
	}
}
  • 编码和解码的操作
public class Demo_Charset {
	public static void main(String[] agrs) {
		Charset latinl = Charset.forName("ISO-8859-1");//只能表示英文字符
		ChaesetEncoder en = latinl.newEncoder();  //得到编码器
		CharsetDecoder de = latinl.new Decoder(); //得到解码器
		CharBuffer cd = CharBuffer.wrap("LICHAPWU");
		ByteBuffer buf = en.encode(cd);  //进行编码操作
		System.out.println(de.decode(buf));  //进行解码操作
	}
}
在读取文件的时候会按照指定的格式解码,之后将解码后的文件内容重新编码后输出;
  • 总结:了解Charset类的作用;了解编码和解码的操作;

Selector(选择器)

  • 在Socket网络编程的时候,可以发现,所有的Socket程序在运行的时候,服务器必须始终等待着客户端连接,那么此时造成大量的资源浪费,所以引用了非阻塞的IO操作,此时就可以使用通过Selector完成;

  • 实际上在NIO中主要的功能是解决服务器端的通讯性能;

  • 使用Selector可以构建一个非阻塞的网络服务;

  • 在NIO实现网络程序需要依靠ServerSocketChannel类与SocketChannel类;

  • Selector常用方法
    public static Selector open(); 打开一个选择器
    public abstract int select();选择一个组键,通道已经为IO做好准备
    public abstract Set< SelectionKey > selectedKeys();返回此选择器已选择的键key;

  • serverSocketChannel常用方法
    public abstract SelectableChannel configureBlocking(boolean block);调用此通道的阻塞模式,如果为true将被设置为阻塞模式,如果为false将被会设置为非阻塞模式;
    public final SelectionKey register(Selector sel, int ops);向指定的选择器注册通道并设置Selector域,返回一个选择键;
    public static ServerSocketChannel open();打开服务器套接字通道;
    public abstract ServerSocket socket();返回与此通道关联的服务器套接字;

  • 四种Selector域
    OP_ACCEPT;相当于ServerSocket中的accpet()操作
    OP_CONNECT;连接操作
    OP_READ;读操作
    OP_WRITE;写操作

  • SelectionKey:如果现在要使用服务器向客户端发送信息,则需通过SelectionKey类中提拱的方法判断服务器的操作状态,而要想取得客户端的连接也需要使用SelectionKey类
    public abstract SelectableChannel channle();返回创建此key的通道
    public final boolean isAcceptable();判断此操作是否可以接收新的连接
    public final boolean isConnectable();判断此通道是否完成套接字的连接操作;
    public final boolean isReadable();判断此通道是否可以进行读取操作;
    public final boolean isWritable();判断此通道是否可以进行写操作;

  • 下面使用Selector完成 一个简单的服务器的操作,服务器可以同时在多个端口进行监听,此服务器主要的功能是返回当前的时间;

public class Demo06_Select {
	public static void main(String[] agrs) throws IOException {
		int ports[] = {8000,8001,8002,8003,8005,8006};//表示五个监听端口
		Selector selector = Selector.open();  //通过open()方法找到selector
		for(int i = 0; i < ports.length; i++) {
			//打开服务器通道
			ServerSocketChannel initSer = ServerSocketChannel.open();
			//服务器配置为非阻塞
			initSer.configureBlocking(false); 
			//实例化绑定地址
			ServerSocket initSock = initSer.socket();
			InetSocketAddress address = new InetSocketAddress(ports[i]);
			initSock.bind(address); //进行服务绑定
			//等待连接
			initSer.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("服务器运行,在" + ports[i] + "端口监听.");
		}
		//要接收全部生成的key,并通过连接进行判断是否获取客户端的输出
		int keysAdd = 0;
		//选择一组键,并且相应的通道已经准备就绪 
		while((keysAdd = selector.select()) > 0) {
			//取出全部生成的key
			Set<SelectionKey> selectedkeys = selector.selectedKeys();
			Iterator<SelectionKey> iter = selectedkeys.iterator();
			while(iter.hasNext()) {
				//取出一个key
				SelectionKey key = iter.next();
				if(key.isAcceptable()) {
					ServerSocketChannel server = (ServerSocketChannel)key.channel();
					SocketChannel client = server.accept();//接收新连接
					client.configureBlocking(false);//配置为非阻塞
					ByteBuffer buf = ByteBuffer.allocateDirect(1024);
					buf.put(("当前的时间为: " + new Date()).getBytes());
					buf.flip();
					client.write(buf);  //输出内容
					client.close();//关闭
				}
			}
			selectedkeys.clear();//清除全部的key
		}
	} 
}

  • 服务器完成之后,可以像之前那样编写普通的客户端代码,也可以使用tenet命令完成,此时就完成了一个异步的操作服务器;
  • 总结:本章在实际开发中使用较少,但是对于服务器性能提高的操作代码使用较多,只要了解使用nio可以改善服务器性能就可以了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值