惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
M
MIT News - Artificial intelligence
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
Recent Announcements
Recent Announcements
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
IT之家
IT之家
F
Fortinet All Blogs
博客园 - 聂微东
U
Unit 42
Martin Fowler
Martin Fowler
腾讯CDC
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky

博客园 - 何锦彬

没开电脑! 只用手机和QQ聊天, 让openClaw帮我"手搓"个AI新闻网站 AI编程从 “猜你想要” 到 “精准生成”, 基于Qoder的Spec驱动开发初探. Java团队Cursor最佳实践:3分钟构建「零泄漏」AI开发环境 再不用手写Commit!AI自动总结代码变更,Git提交效率 MCP赋能,给Cursor插上“外挂翅膀”:实战操作数据库 一个老程序员, 两个小时能用corsur做出什么样的东西 会用 AI 的工程师,效率已经拉开差距了 - “ 我们曾经引以为傲的编码能力,正在被改写。” 1-2 【包子mysql系列】, 对mysql的innoDB加锁分析 1-1, 一个简单的mysql 安装教程,基于mysql 5.7解压版本. Hystrix 服务的隔离策略对比,信号量与线程池隔离的差异 为什么阿里的dubbo注册中心要放弃zookeeper, 而用Nacos? 和spring cloud/boot 学习如何管理自己的组件 利用jmeter+JAVA对RPC的单接口(dubbo接口等)进行性能测试 spring mvc中,直接注入的HttpServletRequst是否安全呢? 基础篇系列,JAVA的并发包 - 锁 1, 本地缓存的实现以及遇到的问题 JAVA的那些数据结构实现总结,实现,扩容说明 JAVA中的数据结构 - 真正的去理解红黑树 对把JDK源码的一些注解,笔记
撸基础篇系列,JAVA的NIO部分
何锦彬 · 2017-03-03 · via 博客园 - 何锦彬

前言:撸基础篇系列,避免每次都要从头开始看,写个自己的知识体系树

NIO 核心就是异步, 比如,复制文件,让操作系统去处理,等通知

BIO核心类

一,BIO

NIO基本操作类

Bytebuffer

构建:

用JVM的内存构建:

ByteBuffer.allocate(bufferSize)

用JVM的直接内存构建:

ByteBuffer.allocateDirect(bufferSize)

内存结构:

 flip()后-->

postion, limit,和captain的处理工具类

flip()    如上, postion=0, limit = wirtCont, captian不变
mark()与reset()方法连用,mark一个postion后,可以通过reset方法返回到原来的订单


缓存的数据处理类
clear()     方法会清空整个缓冲区。

compact()方法只会清除已经读过的数据(0到posstion)

CharSet

用于构建String 和ByteBuffer,以及编码的的一个转换类

构建:
Charset charSet = Charset.forName("gbk");

charSet.decode(butBf)  ,  用于byteBuffer to String

charSet.encode("测试下")  用于String to byteBuffer

	public static void main(String[] args) throws Exception {
		FileInputStream finput = new FileInputStream("E://test.txt");
		FileChannel fchannel = finput.getChannel();
		ByteBuffer byteBf = ByteBuffer.allocate(1024);
		fchannel.read(byteBf);
		Charset charSet = Charset.forName("utf-8");
		byteBf.flip();
		System.out.println(charSet.decode(byteBf));

	}

网络NIO基本操作

selector

一个轮询,实现了 SelectableChannel 接口的都可以在select上添加感兴趣的事件 

NBlocking IO,网络服务

/**
 * selector 只是一个通知,具体各个部分自行处理
 * 
 * NIO , 不阻塞,当有东西来了就开始通知处理,不然能一直select到·
 * 
 * @author hejb
 *
 */
public class Test {
	public static void main(String[] args) throws Exception {
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 阻塞为false
		serverSocketChannel.configureBlocking(false);
		// 绑定IP和端口
		serverSocketChannel.socket().bind(new InetSocketAddress("0.0.0.0", 8080));
		Selector selector = Selector.open();
		// 注册感兴趣事件到selector
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (selector.select() != 0) {

			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
			while (iterator.hasNext()) {

				// 循选中事件
				SelectionKey selecttionKey = iterator.next();
				// 删除已经处理
				iterator.remove();
				if (selecttionKey.isAcceptable()) {
					// 返回注册该事件时的channel ,即SelectableChannel
					ServerSocketChannel channel = (ServerSocketChannel) selecttionKey.channel();
					// 有连接事件来了, 可以处理接收请求了,注意如果不进行accept,select.select()一直能轮询到东西
					// 接收后返回了个socketchannel,开始配置
					SocketChannel socketChannel = channel.accept();
					// 也配置成非阻塞处理
					socketChannel.configureBlocking(false);
					// 复用同一个selector上注册感兴趣的事件,并注册感兴趣的可读事件
					socketChannel.register(selector, selecttionKey.OP_READ);
				}
				// 如果来可以可读事件
				if (selecttionKey.isReadable()) {
					// 返回注册该事件时的channel ,即实现了SelectableChannel的
					SocketChannel socketChannel = (SocketChannel) selecttionKey.channel();
					// 后面就都是通过byteBuffer和channel来读操作了
					ByteBuffer byteBf = ByteBuffer.allocate(1024);
					socketChannel.read(byteBf);
					Charset charset = Charset.forName("utf-8");
					byteBf.flip();
					System.out.println("clinet :" + charset.decode(byteBf));
					// socket是双通道,故也可以直接返回东西了
					socketChannel.write(charset.encode("test only"));
				}
			}
		}
	}
	// 有个疑问,NIO的链接什么时候关闭呢,有文章说是在finalize方法里,待我去找找
}

那么什么时候用IO,什么时候用NIO呢?

大量链接涌入的时候,传的数据比较少,然后处理时间比较长,的时候适合NIO(偏向IO密集型)

如果传入的链接比较少,然后传输数据量大,比如文件上传之类,适合BIO

NIO的网络模型:

用一个thread(selector)做服务接收,和链接的维持

IO的网路模型

用一个thread做服务接收,其它每个链接都用一条线程保持

文件操作流部分

NIO主要是引入了缓冲区和映射来操作文件

JAVA操作文件的过程

1, JAVA读取-> 2,调用native读取--> 3, 调用系统读取(切换到内核态)->  4,磁盘读取文件

每一层都有设置缓存, JAVA操作上的分类

先说缓冲区,以前的IO中也有缓冲区,如 BufferedInputStream 

传统BIO的缓存操作

BIO流的设计,先看连个读取文件例子

没有缓存的FileInputStream

public static String readFileWithoutBuffer() throws IOException {
        // BIO inputstream without buffer
        FileInputStream input = new FileInputStream("E://test.txt");
        byte[] content = new byte[1024];
        int length = input.read(content);
        input.close();
        return new String(content, 0, length);
    }

读取文件时,read的实现是直接通过调用系统的读取数据(native),就是每次读取时都要调用到系统读取。

带缓存的BufferedInputStream

public static String readFileWithBuffer() throws IOException {
        // BIO inputstream with buffer
        FileInputStream input = new FileInputStream("E://test.txt");
        BufferedInputStream binpt = new BufferedInputStream(input);
        byte[] bufferContent = new byte[1024];
        int length = binpt.read(bufferContent);
        return new String(bufferContent, 0, length);
    }

BufferedInputStream是通过内部缓冲区数组实现的缓冲,每次读取时先返回内部数组的数据,默认大小是8192。

具体点用了Decorator模式,继承自FileInputStream ,(覆盖)装饰read方法

代码段:

 public synchronized int read() throws IOException {
        if (pos >= count) {
            fill(); // 如果读取大于缓存了,填充缓冲buff
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

结论:

BufferedInputStream 的作用不是减少 磁盘IO操作次数(这个OS已经帮我们做了),而是通过减少系统调用次数来提高性能的。

NIO的文件映射操作

总结下JAVA文件操作

顺序读取
1, InputStream/OutputStream - 直接调用native方法
2, BufferedInputStream/BufferedOutputStream-  在类里维护了个byte[],先缓存读取到byte中(装饰器的模式)

随机读取
3,RandomAccessFile  - 直接调用native方法
4,FileChannel      -系统的虚拟内存映射文件,讲文件映射到内存中当数组操作

各种文件读取适用场景

--转个实验

在Core Java II中进行了这么一个实验:在同一台机器上,对JDK的jre/lib目录中的37MB的rt.jar文件分别用以上四种操作来计算CRC32校验和,记录下了如下时间

    方法     时间
  普通输入流     110s        
  带缓冲的输入流       9.9s
  随机访问文件     162s
  内存映射文件     7.2s

  这个小实验也验证了内存映射文件这个方法的可行性,由于具有随机访问的功能(映射在内存数组),所以常用来替代RandomAccessFile。

  当然,对于中等尺寸文件的顺序读入则没有必要使用内存映射以避免占用本就有限的I/O资源,这时应当使用带缓冲的输入流。

欢迎关注我的公众号, 一起来构建我们的知识体系