├── .gitignore ├── README.md ├── doc ├── README.md ├── cache │ ├── README.md │ ├── memcache │ │ └── README.md │ └── redis │ │ ├── README.md │ │ └── img │ │ ├── 2.png │ │ ├── 3.png │ │ ├── data.png │ │ ├── hashtable.png │ │ ├── io.png │ │ ├── redis.png │ │ ├── redis1.png │ │ ├── skipList.png │ │ └── ziplist.png ├── container │ └── nginx │ │ └── img │ │ └── nginx.png ├── jdk │ ├── README.md │ ├── juc │ │ └── README.md │ ├── jvm │ │ └── README.md │ └── multi-thread │ │ └── README.md ├── mq │ ├── README.md │ ├── kafka │ │ ├── README.md │ │ ├── img │ │ │ ├── Sendfile.png │ │ │ ├── ar.png │ │ │ ├── c-read.png │ │ │ └── io.png │ │ ├── interview.md │ │ ├── lose.md │ │ └── principle.md │ └── rabbitmq │ │ └── README.md ├── rdbs │ ├── README.md │ ├── engine │ │ └── README.md │ ├── mysql │ │ ├── README.md │ │ ├── img │ │ │ ├── 11.png │ │ │ ├── 12.png │ │ │ ├── InnoDB.png │ │ │ ├── InnoDB2.png │ │ │ ├── MyISAM.png │ │ │ ├── MyISAM2.png │ │ │ ├── Row-Overflow-in-Barracuda.jpg │ │ │ ├── Row-Overflow.jpg │ │ │ ├── b+tree2.png │ │ │ ├── b-tree.jpg │ │ │ ├── btree.png │ │ │ ├── hash.png │ │ │ ├── page2.jpg │ │ │ ├── row-store.jpg │ │ │ ├── row-store1.jpg │ │ │ └── store.png │ │ └── index.md │ ├── optimize │ │ └── README.md │ └── plain │ │ └── README.md ├── search │ ├── README.md │ ├── es │ │ └── README.md │ └── solr │ │ └── README.md └── structure │ ├── README.md │ └── img │ ├── array.jpg │ ├── line.jpg │ ├── link.jpg │ ├── p.jpg │ ├── set.jpg │ └── tree.jpg ├── pom.xml ├── public ├── Elasticsearch.xmind ├── For-interview .xmind ├── IO.xmind ├── Spring.xmind ├── Tomcat.xmind ├── img │ ├── java-IO-2.png │ └── java-io-stream.png ├── java 基础.xmind ├── java基础.xmind ├── mybatis.xmind ├── 分布式事务.xmind ├── 微服务.xmind ├── 数据库.xmind ├── 消息中间件.xmind ├── 算法.xmind ├── 缓存.xmind ├── 设计模式.xmind └── 设计模式1.xmind └── src ├── main ├── java │ └── com │ │ └── lanux │ │ ├── collection │ │ ├── ArrayListCase.java │ │ ├── QueueCase.java │ │ └── README.md │ │ ├── drools │ │ ├── DroolsRuleDomain.java │ │ ├── PointDomain.java │ │ ├── PointRuleEngine.java │ │ ├── README.md │ │ └── RuleTest.java │ │ ├── io │ │ ├── NetConfig.java │ │ ├── README.md │ │ ├── bio │ │ │ ├── BioBasic.java │ │ │ ├── BioClient.java │ │ │ └── BioServer.java │ │ ├── netty │ │ │ ├── Header.java │ │ │ ├── JsonUtil.java │ │ │ ├── NettyClient.java │ │ │ ├── NettyMessage.java │ │ │ ├── NettyMessageDecoder.java │ │ │ ├── NettyMessageEncoder.java │ │ │ ├── NettyMessageHandler.java │ │ │ ├── NettyServer.java │ │ │ ├── README.md │ │ │ └── 粘包拆包.md │ │ ├── nio │ │ │ ├── NioBasic.java │ │ │ ├── NioClient.java │ │ │ ├── NioServer.java │ │ │ ├── NioServer2.java │ │ │ └── README.md │ │ └── stream │ │ │ └── README.md │ │ ├── juc │ │ ├── SynchronizedCase.java │ │ ├── ThreadInterruptCase.java │ │ └── WaitNotifySemaphore.java │ │ ├── pattern │ │ ├── ProxyCase.java │ │ └── ProxyDemo.java │ │ ├── rxjava │ │ └── Test.java │ │ └── tool │ │ ├── ByteUtil.java │ │ └── StringTool.java └── resources │ └── addpoint.drl └── test └── java └── com └── lanux └── io └── bio ├── TestBioClient.java ├── TestBioServer.java ├── TestNioClient.java └── TestNioServer.java /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java template 2 | *.class 3 | *.jar 4 | *.war 5 | *.ear 6 | 7 | 8 | ### Eclipse template 9 | .metadata 10 | bin/ 11 | .settings/ 12 | .loadpath 13 | .recommenders 14 | 15 | # Eclipse Core 16 | .project 17 | .classpath 18 | .target 19 | 20 | ### Maven template 21 | target/ 22 | .mvn 23 | 24 | .idea 25 | *.iml 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java 技术基础 2 | 3 | ## [知识整理](./doc/) 4 | 5 | ## java IO知识 6 | - [linux IO模型](./src/main/java/com/lanux/io) 7 | - BIO 8 | - [java NIO](./src/main/java/com/lanux/io/nio) 9 | - [netty](./src/main/java/com/lanux/io/netty) 10 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | ## 知识整理 2 | 3 | 1. [jdk](./jdk/) 4 | 1. [并发容器](./jdk/juc/) 5 | 1. [多线程](./jdk/multi-thread/) 6 | 1. [JVM](./jdk/jvm/) 7 | 8 | 1. [消息队列](./mq/) 9 | 1. [RabbitMQ](./mq/rabbitmq/) 10 | 1. [Kafka](./mq/kafka/) 11 | 12 | 1. [数据库](./rdbs/) 13 | 1. [mysql](./rdbs/mysql) 14 | 1. [存储引擎](./rdbs/engine/) 15 | 1. [索引](./rdbs/index.md) 16 | 1. [执行计划](./rdbs/plain/) 17 | 1. [性能优化](./rdbs/optimize/) 18 | 19 | 1. [分布式缓存](./cache/) 20 | 1. [redis](cache/redis/) 21 | 1. [memcache](./cache/memcache/) 22 | 23 | 1. [搜索引擎](./search/) 24 | 1. [Elasticsearch](./search/es/) 25 | 1. [Solr](./search/solr/) 26 | 27 | 1. [数据结构与算法](./structure/) 28 | 1. [数组](./structure/) 29 | 1. [栈](./structure/) 30 | 1. [队列](./structure/) 31 | 1. [链表](./structure/) 32 | 1. [树](./structure/) 33 | 1. [散列表](./structure/) 34 | 1. [堆](./structure/) 35 | 1. [图](./structure/) 36 | -------------------------------------------------------------------------------- /doc/cache/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [Redis](#redis) 4 | - [原理](#%E5%8E%9F%E7%90%86) 5 | - [数据结构](#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84) 6 | - [内存淘汰机制](#%E5%86%85%E5%AD%98%E6%B7%98%E6%B1%B0%E6%9C%BA%E5%88%B6) 7 | - [过期键的删除策略](#%E8%BF%87%E6%9C%9F%E9%94%AE%E7%9A%84%E5%88%A0%E9%99%A4%E7%AD%96%E7%95%A5) 8 | - [集群部署方式](#%E9%9B%86%E7%BE%A4%E9%83%A8%E7%BD%B2%E6%96%B9%E5%BC%8F) 9 | - [主从复制原理和优化策略](#%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E5%8E%9F%E7%90%86%E5%92%8C%E4%BC%98%E5%8C%96%E7%AD%96%E7%95%A5) 10 | - [持久化原理](#%E6%8C%81%E4%B9%85%E5%8C%96%E5%8E%9F%E7%90%86) 11 | - [缓存](#%E7%BC%93%E5%AD%98) 12 | - [缓存和数据库双写一致性问题](#%E7%BC%93%E5%AD%98%E5%92%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98) 13 | - [缓存雪崩问题](#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9%E9%97%AE%E9%A2%98) 14 | - [缓存击穿问题](#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF%E9%97%AE%E9%A2%98) 15 | - [缓存的并发竞争问题](#%E7%BC%93%E5%AD%98%E7%9A%84%E5%B9%B6%E5%8F%91%E7%AB%9E%E4%BA%89%E9%97%AE%E9%A2%98) 16 | 17 | 18 | 19 | ## Redis 20 | #### 原理 21 | #### 数据结构 22 | #### 内存淘汰机制 23 | #### 过期键的删除策略 24 | - **定时删除**(redis不使用): 25 | 在设置键的过期时间的同时,创建一个定时器,让定时器执行对键的删除操作,`占用大量CPU` 26 | - **惰性删除**: 27 | 每次取的时候先判断 expires 对象里面的键是否已经过期,如果过期,则删除键,否则,返回该键 28 | - **定期删除**: 29 | 每隔一段时间,程序对数据库遍历检查一遍,然后删除过期的键 30 | 31 | #### 集群部署方式 32 | #### 主从复制原理和优化策略 33 | #### 持久化原理 34 | #### client端并发原子性 35 | #### 分布式锁实现 36 | 37 | ``` 38 | setnx redisLock true 39 | ...业务逻辑执行... 40 | del redisLock 41 | ``` 42 | 43 | 异常情况下锁自动释放 44 | 45 | ``` 46 | setnx redisLock true 47 | expire redisLock 5 48 | ... 业务逻辑执行 ... 49 | del redisLock 50 | ``` 51 | 52 | 原子命令 53 | 54 | ``` 55 | set redisLock true ex 10 nx 56 | ... 业务逻辑执行 ... 57 | del redisLock 58 | ``` 59 | 60 | - 在zookeeper中一但服务器进程down掉或者心跳超时,zk中的临时序列会自动释放。但是Redis中没有这样的机制。 61 | - 如果业务在加锁和释放锁之间的逻辑执行的太长,超出了锁的超时时间,锁就会自动超时释放。甚至在第一个业务执行结束后,释放了后进入业务的分布式锁,打乱了整个锁的持有和释放。 62 | 63 | 合理设定锁持有时间,使用lua脚本,乐观锁的方式删除锁 64 | ``` 65 | String random = Math.random() + ""; 66 | jedis.set("redisLock", random, "NX", "EX", 5); 67 | ... 业务逻辑执行 ... 68 | String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; 69 | jedis.eval(script,Collections.singletonList(lockKey), Collections.singletonList(random)); 70 | ``` 71 | 72 | ## 缓存 73 | #### 缓存和数据库双写一致性问题 74 | #### 缓存雪崩问题 75 | 缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常 76 | 77 | 缓存雪崩解决方案: 78 | - 给缓存的失效时间,加上一个随机值,避免集体失效。 79 | - 使用互斥锁,但是该方案吞吐量明显下降了。 80 | - 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。 81 | - 然后细分以下几个小点:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。 82 | 83 | #### 缓存击穿问题 84 | 缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。 85 | 缓存穿透解决方案: 86 | - 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。 87 | - 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。 88 | - 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。 89 | 90 | #### 缓存的并发竞争问题 91 | -------------------------------------------------------------------------------- /doc/cache/memcache/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/memcache/README.md -------------------------------------------------------------------------------- /doc/cache/redis/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [1. Redis线程模型](#1-redis%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B) 4 | - [Redis线程模型](#redis%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B) 5 | - [redis单线程模型也能效率这么高?](#redis%E5%8D%95%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E4%B9%9F%E8%83%BD%E6%95%88%E7%8E%87%E8%BF%99%E4%B9%88%E9%AB%98%EF%BC%9F) 6 | - [2. I/O 多路复用程序的实现](#2-io-%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%AE%9E%E7%8E%B0) 7 | - [select](#select) 8 | - [poll](#poll) 9 | - [epoll](#epoll) 10 | - [epoll 相关函数:](#epoll-%E7%9B%B8%E5%85%B3%E5%87%BD%E6%95%B0%EF%BC%9A) 11 | - [epoll的三大关键要素:](#epoll%E7%9A%84%E4%B8%89%E5%A4%A7%E5%85%B3%E9%94%AE%E8%A6%81%E7%B4%A0%EF%BC%9A) 12 | - [epoll对文件描述符的操作有两种模式:](#epoll%E5%AF%B9%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6%E7%9A%84%E6%93%8D%E4%BD%9C%E6%9C%89%E4%B8%A4%E7%A7%8D%E6%A8%A1%E5%BC%8F%EF%BC%9A) 13 | - [3. Redis数据结构](#3-redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84) 14 | - [哈希对象](#%E5%93%88%E5%B8%8C%E5%AF%B9%E8%B1%A1) 15 | - [字符串对象](#%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AF%B9%E8%B1%A1) 16 | - [集合对象](#%E9%9B%86%E5%90%88%E5%AF%B9%E8%B1%A1) 17 | - [列表对象](#%E5%88%97%E8%A1%A8%E5%AF%B9%E8%B1%A1) 18 | - [有序集合对象](#%E6%9C%89%E5%BA%8F%E9%9B%86%E5%90%88%E5%AF%B9%E8%B1%A1) 19 | 20 | 21 | 22 | 23 | 24 | ## 1. Redis线程模型 25 | #### Redis线程模型 26 | 27 | Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler) 28 | 29 | ![](./img/redis1.png) 30 | I/O 多路复用程序负责监听多个套接字(Socket), 并向文件事件分派器传送那些产生了事件的套接字。 31 | 32 | ![](./img/2.png) 33 | 尽管多个文件事件可能会并发地出现, 但 I/O 多路复用程序总是会将所有产生事件的套接字都入队到一个队列里面, 34 | 然后通过这个队列, 以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字 35 | 36 | 37 | #### redis单线程模型也能效率这么高? 38 | 39 | 1. 纯内存操作 40 | 2. 基于非阻塞的IO多路复用机制 41 | 3. 单线程避免了多线程的切换(上下文切换)性能损耗问题 42 | 43 | 44 | ## 2. I/O 多路复用程序的实现 45 | 46 | ![](./img/3.png) 47 | 48 | #### select 49 | 50 | #### poll 51 | 52 | #### epoll 53 | 54 | ``` 55 |   int epfd,nfds; 56 |   struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件 57 |   epfd = epoll_create(1); //只需要监听一个描述符——标准输入 58 |   ev.data.fd = STDIN_FILENO; 59 |   ev.events = EPOLLIN|EPOLLET; //监听读状态同时设置ET模式 60 |   epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件 61 |   for(;;) 62 |   { 63 |     nfds = epoll_wait(epfd, events, 5, -1); 64 |     for(int i = 0; i < nfds; i++) 65 |     { 66 |       if(events[i].data.fd==STDIN_FILENO) 67 |       {   68 | printf("welcome to epoll's word!\n"); 69 | } 70 | 71 |     } 72 |   } 73 | 74 | ``` 75 | 76 | ##### epoll 相关函数: 77 | - `int epoll_create(int size);` 78 | 用于创建一个 epoll 句柄,会占用一个 fd 描述符 79 | 80 | - `int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);` 81 | 添加或者删除所有需监控的连接 82 | 83 | - `int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);` 84 | 可以理解为收集监控的所有活跃的连接 85 | 86 | ##### epoll的三大关键要素: 87 | 88 | - mmap 89 | 将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址 90 | (用户空间和内核空间都是虚拟地址,要通过地址映射映射到物理地址),使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换 91 | - 红黑树 92 | epoll在实现上采用红黑树去存储所有套接字,当添加或者删除一个套接字时(epoll_ctl), 93 | 都在红黑树上去处理,红黑树本身插入和删除性能比较好,时间复杂度O(logN) 94 | - 链表 95 | 一旦有事件发生,epoll就会将该事件添加到双向链表中。 96 | 那么当我们调用epoll_wait时,epoll_wait只需要检查rdlist双向链表中是否有存在注册的事件。 97 | 98 | ![](./img/io.png) 99 | 100 | - evport:Solaries 10 101 | - epoll:Linux 102 | - kqueue: macOS/FreeBSD 103 | - select:如果当前编译环境没有上述函数,就会选择 104 | 105 | 前三个函数都使用了内核内部的结构,并且能够服务几十万的文件描述符 106 | 107 | 108 | ##### epoll对文件描述符的操作有两种模式: 109 | 110 | **ET(边缘触发)**:Nginx 就是采用 ET 触发方式,只支持 no-block 方式,当一个 fd 缓冲区就绪的时候,只会发送一次事件触发, 而不会管缓冲区的数据是否已经被读取,都不会再发送第二次 111 | 112 | **LT(电平触发)**:LT模式是默认模式。支持no-block 和 block 两种方式,当一个 fd 缓冲区就绪时,只要缓冲区有数据,就会不停的发送就绪通知 113 | 114 | 115 | ## 3. Redis数据结构 116 | 117 | ![](./img/data.png) 118 | 119 | #### 哈希对象 120 | 哈希对象的编码可以是 ziplist 或者 hashtable 121 | 122 | ziplist: 123 | ![](./img/ziplist.png) 124 | 125 | - 保存了同一键值对的两个节点总是紧挨在一起, 保存键的节点在前, 保存值的节点在后; 126 | - 先添加到哈希对象中的键值对会被放在压缩列表的表头方向, 而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。 127 | 128 | hashtable: 129 | ![](./img/hashtable.png) 130 | 131 | 当哈希对象可以同时满足以下两个条件时, 哈希对象使用 ziplist 编码: 132 | 133 | - 哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节; 134 | - 哈希对象保存的键值对数量小于 512 个; 135 | 136 | 不能满足这两个条件的哈希对象需要使用 hashtable 编码。 137 | 138 | > 这两个条件的上限值是可以修改的, 具体请看配置文件中关于 hash-max-ziplist-value 选项和 hash-max-ziplist-entries 选项的说明。 139 | 140 | #### 字符串对象 141 | 字符串对象的编码可以是 int 、 raw 或者 embstr 142 | 143 | #### 集合对象 144 | 集合对象的编码可以是 intset 或者 hashtable 145 | 146 | #### 列表对象 147 | 列表对象的编码可以是 ziplist 或者 linkedlist 148 | 149 | 当列表对象可以同时满足以下两个条件时, 列表对象使用 ziplist 编码: 150 | 151 | - 列表对象保存的所有字符串元素的长度都小于 64 字节; 152 | - 列表对象保存的元素数量小于 512 个; 153 | 154 | 不能满足这两个条件的列表对象需要使用 linkedlist 编码。 155 | > 以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 list-max-ziplist-value 选项和 list-max-ziplist-entries 选项的说明。 156 | 157 | 158 | #### 有序集合对象 159 | 有序集合的编码可以是 ziplist 或者 skiplist 。 160 | 161 | skiplist: 162 | ![](./img/skipList.png) 163 | 164 | 当有序集合对象可以同时满足以下两个条件时, 对象使用 ziplist 编码: 165 | 166 | - 有序集合保存的元素数量小于 128 个; 167 | - 有序集合保存的所有元素成员的长度都小于 64 字节; 168 | 169 | 不能满足以上两个条件的有序集合对象将使用 skiplist 编码。 170 | 171 | > 以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 zset-max-ziplist-entries 选项和 zset-max-ziplist-value 选项的说明。 172 | -------------------------------------------------------------------------------- /doc/cache/redis/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/2.png -------------------------------------------------------------------------------- /doc/cache/redis/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/3.png -------------------------------------------------------------------------------- /doc/cache/redis/img/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/data.png -------------------------------------------------------------------------------- /doc/cache/redis/img/hashtable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/hashtable.png -------------------------------------------------------------------------------- /doc/cache/redis/img/io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/io.png -------------------------------------------------------------------------------- /doc/cache/redis/img/redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/redis.png -------------------------------------------------------------------------------- /doc/cache/redis/img/redis1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/redis1.png -------------------------------------------------------------------------------- /doc/cache/redis/img/skipList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/skipList.png -------------------------------------------------------------------------------- /doc/cache/redis/img/ziplist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/cache/redis/img/ziplist.png -------------------------------------------------------------------------------- /doc/container/nginx/img/nginx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/container/nginx/img/nginx.png -------------------------------------------------------------------------------- /doc/jdk/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/jdk/README.md -------------------------------------------------------------------------------- /doc/jdk/juc/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/jdk/juc/README.md -------------------------------------------------------------------------------- /doc/jdk/jvm/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/jdk/jvm/README.md -------------------------------------------------------------------------------- /doc/jdk/multi-thread/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/jdk/multi-thread/README.md -------------------------------------------------------------------------------- /doc/mq/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/README.md -------------------------------------------------------------------------------- /doc/mq/kafka/README.md: -------------------------------------------------------------------------------- 1 | 2 | - [kafka原理](./principle.md) 3 | - [丢消息](./lose.md) 4 | - [面试常见问题](./interview.md) 5 | -------------------------------------------------------------------------------- /doc/mq/kafka/img/Sendfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/kafka/img/Sendfile.png -------------------------------------------------------------------------------- /doc/mq/kafka/img/ar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/kafka/img/ar.png -------------------------------------------------------------------------------- /doc/mq/kafka/img/c-read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/kafka/img/c-read.png -------------------------------------------------------------------------------- /doc/mq/kafka/img/io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/kafka/img/io.png -------------------------------------------------------------------------------- /doc/mq/kafka/interview.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/kafka/interview.md -------------------------------------------------------------------------------- /doc/mq/kafka/lose.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/kafka/lose.md -------------------------------------------------------------------------------- /doc/mq/kafka/principle.md: -------------------------------------------------------------------------------- 1 | 2 | #### 1 Kafka的优点? 3 | - 高吞吐,低延迟 4 | - 持久性、可靠性 5 | - 容错性 6 | - 高并发 7 | - 可扩展性 8 | 9 | 10 | ![图片](./img/ar.png) 11 | 12 | #### 2 名词解析 13 | - **Producer** :消息生产者,就是向 kafka broker 发消息的客户端。 14 | - **Consumer** :消息消费者,向 kafka broker 取消息的客户端。 15 | - **Topic** :可以理解为一个队列,一个 Topic 又分为一个或多个分区, 16 | - **Consumer Group**:一个 topic 可以有多个 Consumer Group。一个partition同一时间只能绑定consumer group里面的一个consumer。一个partition可以被多个不同group的consumer同时消费 17 | - **Broker** :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。 18 | - **Partition**:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker上,每个 partition 是一个有序的队列。 19 | partition 中的每条消息都会被分配一个有序的id(offset)。 20 | 将消息发给 consumer,kafka 只保证按一个 partition 中的消息的顺序,不保证一个 topic 的整体(多个 partition 间)的顺序。partition可以动态增加但不能减少。 21 | - **Offset**:kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。例如你想找位于 2049 的位置,只要找到 2048.kafka 的文件即可。 22 | 当然 the first offset 就是 00000000000.kafka。 23 | 24 | 25 | ### 3 高吞吐低延时 26 | 27 | #### 3.1 写入 28 | - 3.1.1 **利用Partition实现并行处理** 29 | - Kafka中的每个Topic都包含一个或多个Partition,且它们位于不同节点。 30 | - Partition在物理上对应一个本地文件夹,每个Partition包含一个或多个Segment,其中包含一个数据文件与一个索引文件。 31 | - Partition像一个数组,可以通过索引(offset)去访问其数据。 32 |    - Kafka可以通过配置让同一节点的不同Partition置于不同的disk drive上,从而实现磁盘间的并行处理。 33 | > 具体方法:将不同磁盘mount到不同目录,在server.properties中,将log.dirs设置为多目录(逗号分隔),Kafka会自动将所有Partition均匀分配到不同disk上。 34 | 35 | - 3.1.2 **顺序写入(Sequence I/O)** 36 | 37 | 磁盘操作有以下几个好处: 38 | > 1. 磁盘顺序读写速度超过内存随机读写 39 | > 1. JVM的GC效率低,内存占用大。使用磁盘可以避免这一问题 40 | > 1. 系统冷启动后,磁盘数据依然可用 41 | 42 | - 3.1.3 **Memory Mapped Files(PageCache)** 43 | 当上层有写操作时,操作系统只是将数据写入 PageCache,同时标记 Page 属性为 Dirty。当读操作发生时,先从 PageCache 中查找,如果发生缺页才进行磁盘调度,最终返回需要的数据。 44 | 实际上 PageCache 是把尽可能多的空闲内存都当做了磁盘缓存来使用。 45 | 46 | - 3.1.4 **NIO** 47 | ![](./img/io.png) 48 | 49 | #### 3.2 读取 50 | 51 | - 3.2.1 **基于sendfile实现Zero Copy** 52 | 传统的网络 I/O 操作流程: 53 | ![](./img/c-read.png) 54 | 55 | Sendfile 优化后,整个 I/O 过程: 56 | ![](./img/Sendfile.png) 57 | 58 | - 3.2.2 **批量压缩** 59 | - Kafka使用了批量压缩,即将多个消息一起压缩而不是单个消息压缩 60 | - Kafka允许使用递归的消息集合,批量的消息可以通过压缩的形式传输并且在日志中也可以保持压缩格式,直到被消费者解压缩 61 | - Kafka支持多种压缩协议,包括Gzip和Snappy压缩协议 62 | 63 | #### 3.3 总结 64 | Kafka速度的秘诀在于,它把所有的消息都变成一个批量的文件,并且进行合理的批量压缩,减少网络IO损耗,通过mmap提高I/O速度,写入数据的时候由于单个Partion是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。 65 | 66 | 67 | #### 4 Rebalance 68 | 为Consumer分配partition的过程就被称为Consumer Rebalance 69 | 1. zk会话过期 70 | 1. 有新的消费者加入Consumer Group 71 | 1. 有消费者主动退出Consumer Group 72 | 1. Consumer Group订阅的任何一个Topic出现分区数量的变化 73 | 74 | #### 5 选举 75 | - 5.1 控制器(Broker)选主 76 | - 集群中第一个启动的broker会通过在zookeeper中创建临时节点/controller来让自己成为控制器 77 | - 其他broker启动时也会在zookeeper中创建临时节点,但是发现节点已经存在,所以它们会收到一个异常,那么就会在创建watch对象,便于它们收到zk上节点变更的通知。 78 | - 如果leader异常,zk上面节点变更,其它节点监听到变化就会去尝试创建临时节点/controller,如果有一个broker创建成功则成为新leader,其它节点继续watch 79 | - 5.2 分区多副本选主 80 | 81 | - 5.3 消费组选主 82 | 83 | 84 | [参考](https://www.jianshu.com/p/d0fc412bcf46) 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/mq/rabbitmq/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/mq/rabbitmq/README.md -------------------------------------------------------------------------------- /doc/rdbs/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/README.md -------------------------------------------------------------------------------- /doc/rdbs/engine/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/engine/README.md -------------------------------------------------------------------------------- /doc/rdbs/mysql/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### 1. Mysql 执行过程 3 | 4 | ![](./img/11.png) 5 | 6 | 1. 客户端发送一条查询给服务器。 7 | 2. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。 8 | 3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。 9 | 4. MySQL根据优化器生成的执行计划,再调用存储引擎的API来执行查询。 10 | 5. 将结果返回给客户端。 11 | 12 | ### 2. 查询优化器 13 | 1. 查询优化器会将解析树转化成执行计划。 14 | 1. 一条查询可以有多种执行方法,最后都是返回相同结果。 15 | 1. 优化器的作用就是找到这其中最好的执行计划。 16 | 1. MySQL使用基于成本的查询优化器(Cost-Based Optimizer,CBO)。它会尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最少的一个 17 | 18 | ![](./img/12.png) 19 | -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/11.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/12.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/InnoDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/InnoDB.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/InnoDB2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/InnoDB2.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/MyISAM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/MyISAM.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/MyISAM2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/MyISAM2.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/Row-Overflow-in-Barracuda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/Row-Overflow-in-Barracuda.jpg -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/Row-Overflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/Row-Overflow.jpg -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/b+tree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/b+tree2.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/b-tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/b-tree.jpg -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/btree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/btree.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/hash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/hash.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/page2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/page2.jpg -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/row-store.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/row-store.jpg -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/row-store1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/row-store1.jpg -------------------------------------------------------------------------------- /doc/rdbs/mysql/img/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/mysql/img/store.png -------------------------------------------------------------------------------- /doc/rdbs/mysql/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [1. 索引的本质](#1-%E7%B4%A2%E5%BC%95%E7%9A%84%E6%9C%AC%E8%B4%A8) 4 | - [**B 树**: 平衡树](#b-%E6%A0%91-%E5%B9%B3%E8%A1%A1%E6%A0%91) 5 | - [**B- 树**: 多路搜索树(并不是二叉的)](#b--%E6%A0%91-%E5%A4%9A%E8%B7%AF%E6%90%9C%E7%B4%A2%E6%A0%91%EF%BC%88%E5%B9%B6%E4%B8%8D%E6%98%AF%E4%BA%8C%E5%8F%89%E7%9A%84%EF%BC%89) 6 | - [**B+ 树**:](#b-%E6%A0%91%EF%BC%9A) 7 | - [**B\* 树**](#b-%E6%A0%91) 8 | - [2. MySQL索引实现](#2-mysql%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B0) 9 | - [2.1 MyISAM索引实现](#21-myisam%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B0) 10 | - [优点](#%E4%BC%98%E7%82%B9) 11 | - [缺点](#%E7%BC%BA%E7%82%B9) 12 | - [行存储](#%E8%A1%8C%E5%AD%98%E5%82%A8) 13 | - [2.2 InnoDB索引实现](#22-innodb%E7%B4%A2%E5%BC%95%E5%AE%9E%E7%8E%B0) 14 | - [特点](#%E7%89%B9%E7%82%B9) 15 | - [页存储](#%E9%A1%B5%E5%AD%98%E5%82%A8) 16 | - [行存储](#%E8%A1%8C%E5%AD%98%E5%82%A8-1) 17 | - [2.3 索引算法](#23-%E7%B4%A2%E5%BC%95%E7%AE%97%E6%B3%95) 18 | - [Record Lock 记录锁](#record-lock-%E8%AE%B0%E5%BD%95%E9%94%81) 19 | - [Gap Lock 间隙锁](#gap-lock-%E9%97%B4%E9%9A%99%E9%94%81) 20 | - [Next-Key Lock](#next-key-lock) 21 | 22 | 23 | 24 | 25 | 26 | ## 1. 索引的本质 27 | MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构 28 | 29 | ### **B 树**: 平衡树 30 | 31 | 32 | 33 | ### **B- 树**: 多路搜索树(并不是二叉的) 34 | 35 | 1. 非叶子结点最多只有M个儿子,且M>2; 36 | 2. 根结点的儿子数为[2, M]; 37 | 3. 除根结点以外的非叶子结点的儿子数最少M/2(向上取整); 38 | 4. 所有叶子结点位于同一层; 39 | 5. 每个结点存放至少M/2-1(向上取整)和至多M-1个关键字;(如下图根节点中25,50,75) 40 | 6. 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];(如下图根节点中25,50,75) 41 | 7. 非叶子结点的指针:P[1], P[2], …, P[M];(如下图根节点中灰色部分) 42 | 8. 非叶子结点的关键字个数=指向儿子的指针个数-1; 43 | 44 | 45 | ![四阶B-tree](./img/b-tree.jpg) 46 | > 四阶B-tree(M=4) 47 | 48 | 特点: 49 | 1. 关键字集合分布在整颗树中; 50 | 2. 任何一个关键字出现且只出现在一个结点中; 51 | 3. 搜索有可能在非叶子结点结束; 52 | 4. 其搜索性能等价于在关键字全集内做一次二分查找; 53 | 5. 自动层次控制; 54 | 55 | ### **B+ 树**: 56 | 57 | ![](./img/b+tree2.png) 58 | > 三阶B+tree(M=3) 59 | 60 | 1. 其定义基本与B-树同,除了: 61 | 2. 非叶子结点的子树指针与关键字个数相同; 62 | 3. 为所有叶子结点增加一个链指针; 63 | 4. 所有关键字都在叶子结点出现; 64 | 65 | 特性: 66 | 67 | 1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的; 68 | 2. 不可能在非叶子结点命中; 69 | 3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层; 70 | 4. 为所有叶子结点增加一个链指针; 71 | 5. 所有关键字都在叶子结点出现; 72 | 6. 非叶子结点的子树指针与关键字个数相同 73 | 7. 更适合文件索引系统; 74 | 75 | 76 | ### **B\* 树** 77 | 1. B+ 树的变体,在 B+ 树的非根和非叶子结点再增加指向兄弟的指针; 78 | 2. B* 树定义了非叶子结点关键字个数至少为 (2/3)*M ,即块的最低使用率为 2/3 (代替 B+ 树的 1/2 ); 79 | 80 | > B+ 树的分裂: 81 | > 当一个结点满时,分配一个新的结点,并将原结点中 1/2 的数据复制到新结点,最后在父结点中增加新结点的指针; 82 | > B+ 树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针; 83 | > 84 | > B* 树的分裂: 85 | > 当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字, 86 | > 最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了); 87 | > 如果兄弟也满了,则在原结点与兄弟结点之间增加新结点, 88 | > 并各复制 1/3 的数据到新结点,最后在父结点增加新结点的指针; 89 | > 所以, B* 树分配新结点的概率比 B+ 树要低,空间使用率更高; 90 | 91 | 92 | ## 2. MySQL索引实现 93 | B+ 树索引可以分为聚集索引(clustered index)和辅助/二级索引(secondary index) 94 | 95 | ### 2.1 MyISAM索引实现 96 | 97 | 默认储存引擎(5.5之前),每个表对应三个文件: 98 | 1. .frm 文件保存表的定义,但是这个文件并不是MyISAM引擎的一部分,而是服务器的一部分; 99 | 1. .MYD 保存表的数据; 100 | 1. .MYI 是表的索引文件。 101 | 102 | 主索引 103 | ![](./img/MyISAM.png) 104 | 105 | 二级索引 106 | ![](./img/MyISAM2.png) 107 | 108 | 109 | #### 优点 110 | 1. 高性能读取; 111 | 2. 因为它保存了表的行数,当使用COUNT统计时不会扫描全表(包含where条件除外); 112 | 113 | #### 缺点 114 | 1. 锁级别为表锁,表锁优点是开销小,加锁快;缺点是锁粒度大,发生锁冲动概率较高,容纳并发能力低,适合查询为主的业务。 115 | 2. 此引擎不支持事务,也不支持外键。 116 | 117 | #### 行存储 118 | MyISAM有3种行存储格式:`fixed`、`dynamic`、`compressed`; 119 | 120 | - **fixed**:为默认格式,只有当表不包含变长字段(varchar/varbinary/blob/text)时使用,该每行都是固定的,所以很容易获取行在页上的具体位置,存取效率比较高,但是占用磁盘空间较多; 121 | 122 | - **dynamic**:每行都有一个行头部,包含bitmap,用以记录那些列为空(NULL列不算为空); 123 | 124 | 相比于fixed,其有如下特性: 125 | > 1. 所有字符串列都是动态存储的,除非长度小于4; 126 | > 1. 字符类型若长度为0/数字类型为0都会不占用存储空间,由bitmap标注,NULL值不包含在内; 127 | > 1. 如果要update行,其扩展后很容易导致行链接既而产生碎片,一旦crash若link丢失则比较难恢复,fixed模式update不会产生碎片; 128 | 129 | - **compressed**:只能通过myisampack创建且为只读; 130 | 131 | ### 2.2 InnoDB索引实现 132 | 使用InnoDB时,会将数据表分为.frm(表结构) 和 idb(表数据和索引)两个文件进行存储。 133 | InnoDB表主键索引是基于聚簇索引建立的,聚簇索引对主键的查询有很高的性能,不过他的二级索引(非主键索引)必须包含主键列。 134 | 135 | 主键索引 136 | 137 | ![](./img/InnoDB.png) 138 | > InnoDB表数据文件本身就是主索引 139 | 140 | 二级索引 141 | 142 | ![](./img/InnoDB2.png) 143 | > 索引的key是数据表的主键 144 | 145 | #### 特点 146 | 1. 支持事务处理、ACID事务特性; 147 | 2. 实现了SQL标准的四种隔离级别; 148 | 3. 支持行级锁和外键约束; 149 | 4. 可以利用事务日志进行数据恢复。 150 | 5. 锁级别为行锁,行锁优点是适用于高并发的频繁表修改,高并发性能优于 MyISAM。缺点是系统消耗较大。 151 | 6. 索引不仅缓存自身,也缓存数据,相比 MyISAM 需要更大的内存。 152 | 7. 第辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域 153 | 154 | 155 | #### 页存储 156 | 157 | 在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位, 158 | 在表空间的下面又包括段(segment)、区(extent)、页(page) 159 | ![](./img/store.png) 160 | 161 | > 同一个数据库实例的所有表空间都有相同的页大小 162 | > 默认情况下,表空间中的页大小都为 16KB,可以通过innodb_page_size 选项对默认大小进行修改.`show variables like 'innodb_page_size'` 163 | > 不同的页大小最终也会导致区大小的不同 164 | 165 | ![](./img/page2.jpg) 166 | 167 | 系统表空间文件,包括 `ibdata1`、`ibdata2` 等文件,其中存储了 InnoDB 系统信息和用户数据库表数据和索引,是所有表公用的。 168 | 169 | 当打开`innodb_file_per_table`选项时,.ibd 文件就是每一个表独有的表空间,文件存储了当前表的数据和相关的索引数据。 170 | 171 | #### 行存储 172 | InnoDB只支持两种文件格式:`Antelope` 和 `Barracuda`。 173 | 174 | **Antelope羚羊**: 原始的InnoDB文件格式。它支持两种行格式:compact 和 redundant。 175 | MySQL5.6的默认文件格式。 176 | 177 | **Barracuda梭子鱼**: 新的文件格式。它支持InnoDB的所有行格式,包括新的行格式:compressed 和 dynamic。 178 | 179 | ![](./img/row-store.jpg) 180 | 181 | ![](./img/row-store1.jpg) 182 | 183 | > **Compact** 和 **Redundant** 格式最大的不同就是记录格式的第一个部分; 184 | > **Compact**:行记录的第一部分倒序存放了一行数据中列的长度(Length); 185 | > **Redundant**:中存的是每一列的偏移量(Offset),从总体上上看,Compact 行记录格式相比 Redundant 格式能够减少 20% 的存储空间。 186 | 187 | 使用 Compact 或者 Redundant 格式存储变长的 VARCHAR 或者 BLOB 这类大对象时,我们并不会直接将所有的内容都存放在数据页节点中,而是将行数据中的前 **768** 个字节存储在数据页中,后面会通过偏移量指向溢出页。 188 | ![](./img/Row-Overflow.jpg) 189 | 190 | 但是当我们使用新的行记录格式 Compressed 或者 Dynamic 时都只会在行记录中保存 20 个字节的指针,实际的数据都会存放在溢出页面中。 191 | ![](./img/Row-Overflow-in-Barracuda.jpg) 192 | 193 | > 当然在实际存储中,可能会对不同长度的 TEXT 和 BLOB 列进行优化 194 | 195 | 196 | ### 2.3 索引算法 197 | #### Record Lock 记录锁 198 | 单个行记录上的锁,会锁住索引记录,如果建表时没有设置添加索引,Innodb会去锁定隐式的主键 199 | 200 | #### Gap Lock 间隙锁 201 | 间隙锁是对索引记录中的一段连续区域的锁;锁定一个范围,但不包含记录本身 202 | 当使用类似 `SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;` 的 SQL 语句时,就会阻止其他事务向表中插入 id = 15 的记录,因为整个范围都被间隙锁锁定了。 203 | 204 | **阻止的就是其他事务向这个范围中添加新的记录** 205 | 206 | #### Next-Key Lock 207 | Next-Key 它是记录锁和记录前的间隙锁的结合,锁定一个范围,并且包含记录本身 208 | ``` 209 | +------|-------------|--------------|-------+ 210 | | id | last_name | first_name | age | 211 | |------|-------------|--------------|-------| 212 | | 4 | stark | tony | 21 | 213 | | 1 | tom | hiddleston | 30 | 214 | | 3 | morgan | freeman | 40 | 215 | | 5 | jeff | dean | 50 | 216 | | 2 | donald | trump | 80 | 217 | +------|-------------|--------------|-------+ 218 | ``` 219 | Next-Key 锁就可以在需要的时候锁定以下的范围 220 | ``` 221 | (-∞, 21] 222 | (21, 30] 223 | (30, 40] 224 | (40, 50] 225 | (50, 80] 226 | (80, ∞) 227 | ``` 228 | > Next-Key 锁锁定的是当前值及其前面的范围 229 | 230 | `SELECT * FROM users WHERE age = 30 FOR UPDATE;` 231 | InnoDB 不仅会在范围 (21, 30] 上加 Next-Key 锁,还会在这条记录后面的范围 (30, 40] 加间隙锁,所以插入 (21, 40] 范围内的记录都会被锁定 232 | 233 | 234 | ## 3. B+tree作为数据库索引 235 | 236 | B+tree的磁盘读写代价更低: 237 | 238 | B+tree的查询效率更加稳定 239 | 240 | 241 | InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节), 242 | 指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值 243 | (因为是估值,为方便计算,这里的K取值为10^3)。也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录。 244 | 245 | 实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2~4层。mysql的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。 246 | -------------------------------------------------------------------------------- /doc/rdbs/optimize/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/optimize/README.md -------------------------------------------------------------------------------- /doc/rdbs/plain/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/rdbs/plain/README.md -------------------------------------------------------------------------------- /doc/search/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/search/README.md -------------------------------------------------------------------------------- /doc/search/es/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/search/es/README.md -------------------------------------------------------------------------------- /doc/search/solr/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/search/solr/README.md -------------------------------------------------------------------------------- /doc/structure/README.md: -------------------------------------------------------------------------------- 1 | ## 数据结构 2 | 3 | ### 逻辑结构(数据对象中数据元素之间的相互关系): 4 | 5 | - 集合结构:集合结构中的数据元素除了属于一个集合外,他们之间没有其他关系。 6 | ![](./img/set.jpg) 7 | - 线性结构:线性结构中的数据元素之间是一对一的关系。 8 | ![](./img/line.jpg) 9 | - 树形结构:树形结构中的数据元素之间存在一对多的层次关系。 10 | ![](./img/tree.jpg) 11 | - 图形结构:图像结构的数据元素是多对多的关系。 12 | ![](./img/p.jpg) 13 | 14 | 15 | ### 物理结构(指数据的逻辑结构在计算机的存储形式): 16 | 17 | - 顺序存储结构:是把数据元素存储到地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。 18 | ![](./img/array.jpg) 19 | - 链式存储结构:是把数据元素放到任意的存储单元中,这组存储单元可以是连续的,也可以是不连续的。 20 | ![](./img/link.jpg) 21 | 22 | 数据结构 | 查找 | 更新 | 插入 | 删除 23 | ---|---|---|---|--- 24 | 数组 | O(1) | O(1) | O(n) | O(n) 25 | 链表 | O(n) | O(1) | O(1) | O(1) 26 | 27 | ### 1、数组 28 | 数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的 29 | 30 | 优点: 31 | 1. 按照索引查询元素速度快 32 | 1. 按照索引遍历数组方便 33 | 34 | 缺点: 35 | 1. 数组的大小固定后就无法扩容了 36 | 1. 数组只能存储一种类型的数据 37 | 1. 添加,删除的操作慢,因为要移动其他的元素。 38 | 39 | 适用场景: 40 | > 频繁查询,对存储空间要求不大,很少增加和删除的情况。 41 | 42 | ### 2、栈 43 | 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。 44 | 45 | ### 3、队列 46 | 队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队,示例图如下: 47 | 48 | 使用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。 49 | 50 | ### 4、链表 51 | 链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。 52 | 53 | 链表的优点: 54 | 链表是很常用的一种数据结构,不需要初始化容量,可以任意加减元素; 55 | 添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快; 56 | 57 | 缺点: 58 | 1. 因为含有大量的指针域,占用空间较大; 59 | 2. 查找元素需要遍历链表来查找,非常耗时。 60 | 61 | 适用场景: 62 | 1. 数据量较小,需要频繁增加,删除操作的场景 63 | 64 | ### 5、树 65 | 树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。 66 | 把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点: 67 | 68 | 每个节点有零个或多个子节点; 69 | 没有父节点的节点称为根节点; 70 | 每一个非根节点有且只有一个父节点; 71 | 除了根节点外,每个子节点可以分为多个不相交的子树; 72 | 在日常的应用中,我们讨论和用的更多的是树的其中一种结构,就是二叉树。 73 | 74 | 二叉树是树的特殊一种,具有如下特点: 75 | 76 | 1、每个结点最多有两颗子树,结点的度最大为2。 77 | 2、左子树和右子树是有顺序的,次序不能颠倒。 78 | 3、即使某结点只有一个子树,也要区分左右子树。 79 | 80 | 二叉树是一种比较有用的折中方案,它添加,删除元素都很快,并且在查找方面也有很多的算法优化,所以,二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面非常有用。 81 | 82 | 扩展: 83 | 二叉树有很多扩展的数据结构,包括平衡二叉树、红黑树、B+树等,这些数据结构二叉树的基础上衍生了很多的功能,在实际应用中广泛用到,例如mysql的数据库索引结构用的就是B+树,还有HashMap的底层源码中用到了红黑树。这些二叉树的功能强大,但算法上比较复杂,想学习的话还是需要花时间去深入的。 84 | 85 | 二叉树: 86 | - 满二叉树:所有非叶子节点都是2个儿子,而且所有叶子节点在同一层 87 | - 完全二叉树:除去最后一层必须是满二叉树,最后一层从左到右必须不能留空 88 | 89 | 90 | - 二叉查找树/二叉搜索树/二叉排序树: 左节点小于根节点,右节点大于根节点 91 | 深度优先(前序遍历,中序遍历,后序遍历),广度优先(从上到下从左到右) 92 | 93 | 二叉堆-本质上是完全二叉树 94 | 95 | ### 6、散列表 96 | 散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。 97 | 98 | 记录的存储位置=f(key) 99 | 100 | 这里的对应关系 f 成为散列函数,又称为哈希 (hash函数),而散列表就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里,这种存储空间可以充分利用数组的查找优势来查找元素,所以查找的速度很快。 101 | 102 | 哈希表在应用中也是比较常见的,就如Java中有些集合类就是借鉴了哈希原理构造的,例如HashMap,HashTable等,利用hash表的优势,对于集合的查找元素时非常方便的,然而,因为哈希表是基于数组衍生的数据结构,在添加删除元素方面是比较慢的,所以很多时候需要用到一种数组链表来做,也就是拉链法。拉链法是数组结合链表的一种结构,较早前的hashMap底层的存储就是采用这种结构,直到jdk1.8之后才换成了数组加红黑树的结构,其示例图如下: 103 | 104 | 从图中可以看出,左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。 105 | 106 | 哈希表的应用场景很多,当然也有很多问题要考虑,比如哈希冲突的问题,如果处理的不好会浪费大量的时间,导致应用崩溃。 107 | 108 | ### 7、堆 109 | 堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质: 110 | 111 | 堆中某个节点的值总是不大于或不小于其父节点的值; 112 | 113 | 堆总是一棵完全二叉树。 114 | 115 | 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。 116 | 117 | 堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。 118 | (ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2),满足前者的表达式的成为小顶堆,满足后者表达式的为大顶堆,这两者的结构图可以用完全二叉树排列出来,示例图如下: 119 | 120 | 因为堆有序的特点,一般用来做数组中的排序,称为堆排序。 121 | 122 | ### 8、图 123 | 图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。 124 | 125 | 按照顶点指向的方向可分为无向图和有向图: 126 | 127 | 128 | 图是一种比较复杂的数据结构,在存储数据上有着比较复杂和高效的算法,分别有邻接矩阵 、邻接表、十字链表、邻接多重表、边集数组等存储结构,这里不做展开,读者有兴趣可以自己学习深入。 129 | -------------------------------------------------------------------------------- /doc/structure/img/array.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/structure/img/array.jpg -------------------------------------------------------------------------------- /doc/structure/img/line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/structure/img/line.jpg -------------------------------------------------------------------------------- /doc/structure/img/link.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/structure/img/link.jpg -------------------------------------------------------------------------------- /doc/structure/img/p.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/structure/img/p.jpg -------------------------------------------------------------------------------- /doc/structure/img/set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/structure/img/set.jpg -------------------------------------------------------------------------------- /doc/structure/img/tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/doc/structure/img/tree.jpg -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.lanux 8 | java-demo 9 | 1.0-SNAPSHOT 10 | 11 | 12 | org.apache.commons 13 | commons-lang3 14 | 3.4 15 | 16 | 17 | com.google.guava 18 | guava 19 | 19.0 20 | 21 | 22 | org.projectlombok 23 | lombok 24 | 1.16.16 25 | 26 | 27 | junit 28 | junit 29 | 4.12 30 | 31 | 32 | io.netty 33 | netty-all 34 | 4.1.15.Final 35 | 36 | 37 | com.fasterxml.jackson.core 38 | jackson-databind 39 | 2.8.6 40 | 41 | 42 | org.slf4j 43 | slf4j-api 44 | 1.7.25 45 | 46 | 47 | 48 | 49 | org.drools 50 | drools-core 51 | 7.0.0.Final 52 | 53 | 54 | org.drools 55 | drools-compiler 56 | 7.0.0.Final 57 | 58 | 59 | 60 | 61 | io.reactivex.rxjava2 62 | rxjava 63 | 2.1.7 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-compiler-plugin 71 | 3.1 72 | 73 | 1.8 74 | 1.8 75 | 1.8 76 | UTF-8 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /public/Elasticsearch.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/Elasticsearch.xmind -------------------------------------------------------------------------------- /public/For-interview .xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/For-interview .xmind -------------------------------------------------------------------------------- /public/IO.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/IO.xmind -------------------------------------------------------------------------------- /public/Spring.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/Spring.xmind -------------------------------------------------------------------------------- /public/Tomcat.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/Tomcat.xmind -------------------------------------------------------------------------------- /public/img/java-IO-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/img/java-IO-2.png -------------------------------------------------------------------------------- /public/img/java-io-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/img/java-io-stream.png -------------------------------------------------------------------------------- /public/java 基础.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/java 基础.xmind -------------------------------------------------------------------------------- /public/java基础.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/java基础.xmind -------------------------------------------------------------------------------- /public/mybatis.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/mybatis.xmind -------------------------------------------------------------------------------- /public/分布式事务.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/分布式事务.xmind -------------------------------------------------------------------------------- /public/微服务.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/微服务.xmind -------------------------------------------------------------------------------- /public/数据库.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/数据库.xmind -------------------------------------------------------------------------------- /public/消息中间件.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/消息中间件.xmind -------------------------------------------------------------------------------- /public/算法.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/算法.xmind -------------------------------------------------------------------------------- /public/缓存.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/缓存.xmind -------------------------------------------------------------------------------- /public/设计模式.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/设计模式.xmind -------------------------------------------------------------------------------- /public/设计模式1.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanux/java-demo/14cec990d38a461007f671a10bf805625cfce5f0/public/设计模式1.xmind -------------------------------------------------------------------------------- /src/main/java/com/lanux/collection/ArrayListCase.java: -------------------------------------------------------------------------------- 1 | package com.lanux.collection; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.util.HashMap; 6 | 7 | public class ArrayListCase { 8 | private String name; 9 | 10 | class Inner { 11 | private String name; 12 | 13 | public void get(String[] args) { 14 | System.out.println("this.name = " + ArrayListCase.this.name); 15 | } 16 | } 17 | public static void main(String[] args) { 18 | HashMap map = new HashMap(); 19 | for (int i = 0; i < 1_000_000; i++) { 20 | map.put(i, null); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/collection/QueueCase.java: -------------------------------------------------------------------------------- 1 | package com.lanux.collection; 2 | 3 | import java.util.concurrent.*; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | public class QueueCase { 7 | static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); 8 | 9 | static LinkedBlockingQueue queue2 = new LinkedBlockingQueue<>(); 10 | 11 | static volatile int ctt; 12 | 13 | static ExecutorService executorService1 = Executors 14 | .newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 3, new ThreadFactory() { 15 | final AtomicInteger threadNumber = new AtomicInteger(1); 16 | 17 | @Override 18 | public Thread newThread(Runnable r) { 19 | return new Thread(r, "s-" + threadNumber.getAndIncrement()); 20 | } 21 | }); 22 | static ExecutorService executorService2 = Executors 23 | .newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 3, new ThreadFactory() { 24 | final AtomicInteger threadNumber = new AtomicInteger(1); 25 | 26 | @Override 27 | public Thread newThread(Runnable r) { 28 | return new Thread(r, "p-" + threadNumber.getAndIncrement()); 29 | } 30 | }); 31 | 32 | public static void main(String[] args) throws Exception { 33 | long startTime = System.nanoTime(); 34 | for (int i = 0; i < 5; i++) { 35 | executorService2.submit((Runnable) () -> { 36 | for (int count = 0; count < 100000; count++) { 37 | String x = count + " = " + Thread.currentThread().getName(); 38 | queue.offer(x); 39 | } 40 | }); 41 | } 42 | for (int i = 0; i < 5; i++) { 43 | executorService1.submit(() -> { 44 | for (int count = 0; count < 100000; count++) { 45 | String take = queue.poll(); 46 | System.out.println(Thread.currentThread().getName() + " consumer : " + take); 47 | } 48 | ctt++; 49 | }); 50 | } 51 | while (true) { 52 | if (ctt==5) { 53 | long estimatedTime = System.nanoTime() - startTime; 54 | System.out.println("estimatedTime = " + estimatedTime); 55 | executorService1.shutdown(); 56 | executorService2.shutdown(); 57 | break; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/collection/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/drools/DroolsRuleDomain.java: -------------------------------------------------------------------------------- 1 | package com.lanux.drools; 2 | 3 | public class DroolsRuleDomain { 4 | /** 数据库记录ID */ 5 | private long id; 6 | /** 规则名称 */ 7 | private String ruleName; 8 | /** 规则正文 */ 9 | private String ruleContext; 10 | /** 规则版本 */ 11 | private int version; 12 | /** 规则脚本状态 */ 13 | private int status; 14 | 15 | public long getId() { 16 | return id; 17 | } 18 | 19 | public void setId(long id) { 20 | this.id = id; 21 | } 22 | 23 | public String getRuleName() { 24 | return ruleName; 25 | } 26 | 27 | public void setRuleName(String ruleName) { 28 | this.ruleName = ruleName; 29 | } 30 | 31 | public String getRuleContext() { 32 | return ruleContext; 33 | } 34 | 35 | public void setRuleContext(String ruleContext) { 36 | this.ruleContext = ruleContext; 37 | } 38 | 39 | public int getVersion() { 40 | return version; 41 | } 42 | 43 | public void setVersion(int version) { 44 | this.version = version; 45 | } 46 | 47 | public int getStatus() { 48 | return status; 49 | } 50 | 51 | public void setStatus(int status) { 52 | this.status = status; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/drools/PointDomain.java: -------------------------------------------------------------------------------- 1 | package com.lanux.drools; 2 | 3 | public class PointDomain { 4 | // 用户名 5 | private String userName; 6 | // 是否当日生日 7 | private boolean birthDay; 8 | // 增加积分数目 9 | private long point; 10 | // 当月购物次数 11 | private int buyNums; 12 | // 当月退货次数 13 | private int backNums; 14 | // 当月购物总金额 15 | private double buyMoney; 16 | // 当月退货总金额 17 | private double backMondy; 18 | // 当月信用卡还款次数 19 | private int billThisMonth; 20 | 21 | /** 22 | * 记录积分发送流水,防止重复发放 23 | * 24 | * @param userName 用户名 25 | * @param type 积分发放类型 26 | */ 27 | public void recordPointLog(String userName, String type) { 28 | System.out.println("增加对" + userName + "的类型为" + type + "的积分操作记录."); 29 | } 30 | 31 | public String getUserName() { 32 | return userName; 33 | } 34 | 35 | public void setUserName(String userName) { 36 | this.userName = userName; 37 | } 38 | 39 | public boolean isBirthDay() { 40 | return birthDay; 41 | } 42 | 43 | public void setBirthDay(boolean birthDay) { 44 | this.birthDay = birthDay; 45 | } 46 | 47 | public long getPoint() { 48 | return point; 49 | } 50 | 51 | public void setPoint(long point) { 52 | this.point = point; 53 | } 54 | 55 | public int getBuyNums() { 56 | return buyNums; 57 | } 58 | 59 | public void setBuyNums(int buyNums) { 60 | this.buyNums = buyNums; 61 | } 62 | 63 | public int getBackNums() { 64 | return backNums; 65 | } 66 | 67 | public void setBackNums(int backNums) { 68 | this.backNums = backNums; 69 | } 70 | 71 | public double getBuyMoney() { 72 | return buyMoney; 73 | } 74 | 75 | public void setBuyMoney(double buyMoney) { 76 | this.buyMoney = buyMoney; 77 | } 78 | 79 | public double getBackMondy() { 80 | return backMondy; 81 | } 82 | 83 | public void setBackMondy(double backMondy) { 84 | this.backMondy = backMondy; 85 | } 86 | 87 | public int getBillThisMonth() { 88 | return billThisMonth; 89 | } 90 | 91 | public void setBillThisMonth(int billThisMonth) { 92 | this.billThisMonth = billThisMonth; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/drools/PointRuleEngine.java: -------------------------------------------------------------------------------- 1 | package com.lanux.drools; 2 | 3 | public interface PointRuleEngine { 4 | /** 5 | * 初始化规则引擎 6 | */ 7 | public void initEngine(); 8 | 9 | /** 10 | * 刷新规则引擎中的规则 11 | */ 12 | public void refreshEnginRule(); 13 | 14 | /** 15 | * 执行规则引擎 16 | * @param pointDomain 积分Fact 17 | */ 18 | public void executeRuleEngine(final PointDomain pointDomain); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/drools/README.md: -------------------------------------------------------------------------------- 1 | Drools是一个基于java的规则引擎,开源的 2 | Drools是Jboss公司旗下一款开源的规则引擎,有如下特点; 3 | 4 | 1. 完整的实现了Rete算法; 5 | 2. 提供了强大的Eclipse Plugin开发支持; 6 | 3. 通过使用其中的DSL(Domain Specific Language),可以实现用自然语言方式来描述业务规则,使得业务分析人员也可以看懂业务规则代码; 7 | 4. 提供了基于WEB的BRMS——Guvnor,Guvnor提供了规则管理的知识库,通过它可以实现规则的版本控制,及规则的在线修改与编译,使得开发人员和系统管理人员可以在线管理业务规则。 8 | 9 | Drools 是业务逻辑集成平台,被分为4个项目: 10 | 11 | 1. Drools Guvnor (BRMS/BPMS):业务规则管理系统 12 | 1. Drools Expert (rule engine):规则引擎,drools的核心部分 13 | 1. Drools Flow (process/workflow):工作流引擎 14 | 1. Drools Fusion (cep/temporal reasoning):事件处理 15 | 16 | 17 | 1、Drools语法 18 | 开始语法之前首先要了解一下drools的基本工作过程,通常而言我们使用一个接口来做事情,首先要传参数进去, 19 | 其次要获取到接口的实现执行完毕后的结果,而drools也是一样的,我们需要传递进去数据,用于规则的检查,调用外部接口, 20 | 同时还可能需要获取到规则执行完毕后得到的结果。在drools中,这个传递数据进去的对象,术语叫 Fact对象。 21 | Fact对象是一个普通的java bean,规则中可以对当前的对象进行任何的读写操作,调用该对象提供的方法, 22 | 当一个java bean插入到workingMemory中,规则使用的是原有对象的引用,规则通过对fact对象的读写, 23 | 实现对应用数据的读写,对于其中的属性,需要提供getter setter访问器, 24 | 规则中,可以动态的往当前workingMemory中插入删除新的fact对象。 25 | 26 | 规则文件可以使用 .drl文件,也可以是xml文件,这里我们使用drl文件。 27 | 28 | ## 规则语法: 29 | ``` 30 | /*包名,必须的,只限于逻辑上的管理,若自定义query和function属于同一个package,不管规则文件物理位置如何都可以相互调用*/ 31 | package com.lanux.drools.demo; 32 | 33 | /*需要导入的类名和类的可访问静态方法*/ 34 | import com.lanux.drools.entity.User; 35 | 36 | /*全局变量*/ 37 | global java.util.List userList 38 | 39 | /*函数*/ 40 | function void functionName(String name){ 41 | System.out.println(name) 42 | } 43 | 44 | query // 45 | 46 | /*规则,可以有多个*/ 47 | rule "规则名称" 48 | <属性名> <属性值> 49 | when 50 | 条件 Left Hand Side 51 | then 52 | 结果 Right Hand Side 53 | end 54 | ``` 55 | 56 | ### package: 57 | > package是`必须`的,放在规则文件第一行。
58 | > package的名字是随意的,不必完全对应物理路径。
59 | > 相同package下定义的function和query等可以直接使用。
60 | ``` 61 | package com.drools.demo.point 62 | ``` 63 | 64 | ### import: 65 | 导入规则文件需要使用到的外部变量,这里的使用方法跟java相同,但是不同于java的是, 66 | 这里的import导入的不仅仅可以是一个类,也可以是这个类中的某一个可访问的`静态方法`。 67 | 68 | ``` 69 | import com.drools.demo.point.PointDomain; 70 | import com.drools.demo.point.PointDomain.getById; 71 | ``` 72 | 73 | ### global: 74 | 定义global全局变量,通常用于返回数据和提供服务。全局变量与fact不一样,引擎不能知道全局变量的改变,必须要在插入fact之前,设置global变量 75 | 76 | 77 | ### function: 78 | 规则中的代码块,封装业务操作,提高代码复用。 79 | 函数以function开头,其它与JAVA方法类似 80 | 业务代码书写采用的是标准JAVA语法 81 | 82 | ### rule: 83 | 定义一个规则,包含三个部分: 84 | 85 | 1. **属性部分**: 86 | 定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效时间等。 87 | `activation-group、agenda-group、auto-focus、date-effective、date-expires、dialect、duration、duration-value、enabled、lock-on-active、no-loop、ruleflow-group、salience` 88 | 1. **条件部分**: 89 | 即LHS,定义当前规则的条件,如 when Message(); 判断当前workingMemory中是否存在Message对象。 90 | 1. **结果部分**: 91 | 即RHS,这里可以写普通java代码,即当前规则条件满足后执行的操作,可以直接调用Fact对象的方法来操作应用。 92 | 93 | 规则示例: 94 | 95 | ``` 96 | rule "name" 97 | no-loop true 98 | when 99 | $message:Message(status == 0) 100 | then 101 | System.out.println("fit"); 102 | $message.setStatus(1); 103 | update($message); 104 | end 105 | ``` 106 | 107 | #### 属性部分 108 | - **no-loop**(默认是false) 109 | 定义当前的规则是否不允许多次循环执行,也就是当前的规则只要满足条件,可以无限次执行。 110 | 什么情况下会出现一条规则执行过一次又被多次重复执行呢?drools提供了一些api,可以对当前传入workingMemory中的Fact对象进行修改或者个数的增减, 111 | 比如上述的update方法,就是将当前的workingMemory中的Message类型的Fact对象进行属性更新,这种操作会触发规则的重新匹配执行, 112 | 可以理解为Fact对象更新了,所以规则需要重新匹配一遍,那么疑问是之前规则执行过并且修改过的那些Fact对象的属性的数据会不会被重置? 113 | 结果是不会,已经修改过了就不会被重置,update之后,之前的修改都会生效。当然对Fact对象数据的修改并不是一定需要调用update才可以生效, 114 | 简单的使用set方法设置就可以完成,这里类似于java的引用调用,所以何时使用update是一个需要仔细考虑的问题,一旦不慎,极有可能会造成规则的死循环。 115 | 上述的no-loop true,即设置当前的规则,只执行一次,如果本身的RHS部分有update等触发规则重新执行的操作,也不要再次执行当前规则。 116 | 但是其他的规则会被重新执行,岂不是也会有可能造成多次重复执行,数据紊乱甚至死循环? 117 | 答案是使用其他的标签限制,也是可以控制的:lock-on-active true 118 | - **lock-on-active**:通过这个标签,可以控制当前的规则只会被执行一次,因为一个规则的重复执行不一定是本身触发的,也可能是其他规则触发的, 119 | 所以这个是no-loop的加强版。当然该标签正规的用法会有其他的标签的配合,后续提及。 120 | - **date-expires**:设置规则的过期时间,默认的时间格式:`dd-MM-yyyy`, 121 | 比如中文:"29-七月-2010",英文:"25-Sep-2009" 122 | 但是还是推荐使用更为精确和习惯的格式,这需要手动在java代码中设置当前系统的时间格式。 123 | ``` 124 | date-expires "2011-01-31 23:59:59" // 这里我们使用了更为习惯的时间格式 125 | ``` 126 | - **date-effective**:设置规则的生效时间,时间格式同上。在没有设置该属性的情况下,规则随时可以触发,没有这种限制。当系统时间>=date-effective 设置的时间值时,规则才会触发执行 127 | - **duration**:规则定时,duration 3000 3秒后执行规则。一个长整型,单位是毫秒 128 | - **salience**(默认是0):优先级,数值越大越先执行,这个可以控制规则的执行顺序。同时它的值可以是一个负数。不手动设置规则的salience属性,那么它的执行顺序是随机的。 129 | - **enabled** 130 | enabled 属性比较简单,它是用来定义一个规则是否可用的。该属性的值是一个布尔值,默认该属性的值为true,表示规则是可用的,如果手工为一个规则添加一个enabled 属性,并且设置其enabled 属性值为false,那么引擎就不会执行该规则。 131 | - **dialect** 132 | 该属性用来定义规则当中要使用的语言类型,目前Drools5 版本当中支持两种类型的语言:mvel 和java,默认情况下,如果没有手工设置规则的dialect,那么使用的java 语言。 133 | - lock-on-active 当在规则上使用ruleflow-group 属性或agenda-group 属性的时候,将lock-on-action 属性的值设置为true,可能避免因某些Fact 对象被修改而使已经执行过的规则再次被激活执行。可以看出该属性与no-loop 属性有相似之处,no-loop 属性是为了避免Fact 修改或调用了insert、retract、update 之类而导致规则再次激活执行,这里的lock-on-action 属性也是起这个作用,lock-on-active 是no-loop 的增强版属性,它主要作用在使用ruleflow-group 属性或agenda-group 属性的时候。lock-on-active 属性默认值为false。 134 | - activation-group 该属性的作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同activation-group 属性的规则中只要有一个会被执行,其它的规则都将不再执行。也就是说,在一组具有相同activation-group 属性的规则当中,只有一个规则会被执行,其它规则都将不会被执行。当然对于具有相同activation-group 属性的规则当中究竟哪一个会先执行,则可以用类似salience 之类属性来实现。 135 | - agenda-group 规则的调用与执行是通过StatelessSession 或StatefulSession 来实现的,一般的顺序是创建一个StatelessSession 或StatefulSession,将各种经过编译的规则的package 添加到session当中,接下来将规则当中可能用到的Global 对象和Fact 对象插入到Session 当中,最后调用fireAllRules 方法来触发、执行规则。在没有调用最后一步fireAllRules 方法之前,所有的规则及插入的Fact 对象都存放在一个名叫Agenda 表的对象当中,这个Agenda 表中每一个规则及与其匹配相关业务数据叫做Activation,在调用fireAllRules 方法后,这些Activation 会依次执行,这些位于Agenda 表中的Activation 的执行顺序在没有设置相关用来控制顺序的属性时(比如salience 属性),它的执行顺序是随机的,不确定的。 Agenda Group 是用来在Agenda 的基础之上,对现在的规则进行再次分组,具体的分组方法可以采用为规则添加agenda-group 属性来实现。agenda-group 属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个Agenda Group,默认情况下,引擎在调用这些设置了agenda-group 属性的规则的时候需要显示的指定某个Agenda Group 得到Focus(焦点),这样位于该Agenda Group 当中的规则才会触发执行,否则将不执行。 136 | - auto-focus 前面我们也提到auto-focus 属性,它的作用是用来在已设置了agenda-group 的规则上设置该规则是否可以自动独取Focus,如果该属性设置为true,那么在引擎执行时,就不需要显示的为某个Agenda Group 设置Focus,否则需要。对于规则的执行的控制,还可以使用Agenda Filter 来实现。在Drools 当中,提供了一个名为org.drools.runtime.rule.AgendaFilter 的Agenda Filter 接口,用户可以实现该接口,通过规则当中的某些属性来控制规则要不要执行。org.drools.runtime.rule.AgendaFilter 接口只有一个方法需要实现,方法体如下: public boolean accept(Activation activation); 在该方法当中提供了一个Activation 参数,通过该参数我们可以得到当前正在执行的规则对象或其它一些属性,该方法要返回一个布尔值,该布尔值就决定了要不要执行当前这个规则,返回true 就执行规则,否则就不执行。 137 | - ruleflow-group 在使用规则流的时候要用到ruleflow-group 属性,该属性的值为一个字符串,作用是用来将规则划分为一个个的组,然后在规则流当中通过使用ruleflow-group 属性的值,从而使用对应的规则。 138 | - 其他的属性可以参照相关的api文档查看具体用法,此处略。 139 | 140 | #### 条件部分,即LHS部分: 141 | 142 | when:规则条件开始。条件可以单个,也可以多个,多个条件依次排列,比如 143 | 144 | when 145 | eval(true) 146 | $customer:Customer() 147 | $message:Message(status==0) 148 | 149 | 上述罗列了三个条件,当前规则只有在这三个条件都匹配的时候才会执行RHS部分,三个条件中第一个 150 | 151 | eval(true):是一个默认的api,true 无条件执行,类似于 while(true) 152 | 153 | $message:Message(status==0) 这句话标示的:当前的workingMemory存在Message类型并且status属性的值为0的Fact对象, 154 | 这个对象通常是通过外部java代码插入或者自己在前面已经执行的规则的RHS部分中insert进去的。 155 | 156 | 前面的$message代表着当前条件的引用变量,在后续的条件部分和RHS部分中,可以使用当前的变量去引用符合条件的FACT对象,修改属性或者调用方法等。 157 | 可选,如果不需要使用,则可以不写。 158 | 159 | 条件可以有组合,比如: 160 | 161 | Message(status==0 || (status > 1 && status <=100)) 162 | 163 | RHS中对Fact对象private属性的操作必须使用getter和setter方法,而RHS中则必须要直接用.的方法去使用,比如 164 | 165 | $order:Order(name=="qu") 166 | $message:Message(status==0 && orders contains $order && $order.name=="qu") 167 | 168 | 特别的是,如果条件全部是 &&关系,可以使用“,”来替代,但是两者不能混用 169 | 170 | 如果现在Fact对象中有一个List,需要判断条件,如何判断呢? 171 | 172 | 看一个例子: 173 | 174 | Message { 175 | 176 | int status; 177 | 178 | List names; 179 | 180 | } 181 | 182 | $message:Message(status==0 && names contains "网易" && names.size >= 1) 183 | 184 | 上述的条件中,status必须是0,并且names列表中含有“网易”并且列表长度大于等于1 185 | 186 | contains:对比是否包含操作,操作的被包含目标可以是一个复杂对象也可以是一个简单的值。 187 | 188 | Drools提供了十二中类型比较操作符: 189 | `> >= < <= == != contains / not contains / memberOf / not memberOf /matches/ not matches` 190 | not contains:与contains相反。 191 | 192 | memberOf:判断某个Fact属性值是否在某个集合中,与contains不同的是他被比较的对象是一个集合,而contains被比较的对象是单个值或者对象。 193 | 194 | not memberOf:正好相反。 195 | 196 | matches:正则表达式匹配,与java不同的是,不用考虑'/'的转义问题 197 | 198 | not matches:正好相反。 199 | 200 | 201 | 202 | #### 规则的结果部分 203 | 204 | 当规则条件满足,则进入规则结果部分执行,结果部分可以是纯java代码,比如: 205 | ``` 206 | then 207 | System.out.println("OK"); //会在控制台打印出ok 208 | end 209 | ``` 210 | 当然也可以调用Fact的方法,比如 $message.execute();操作数据库等等一切操作。 211 | 212 | 结果部分也有drools提供的方法: 213 | 214 | - insert:往当前workingMemory中插入一个新的Fact对象,会触发规则的再次执行,除非使用no-loop限定; 215 | - update:更新 216 | - modify:修改,与update语法不同,结果都是更新操作 217 | - retract:删除 218 | 219 | RHS部分除了调用Drools提供的api和Fact对象的方法,也可以调用规则文件中定义的方法function 220 | 221 | Drools还有一个可以定义类的关键字: 222 | 223 | declare 可以再规则文件中定义一个class,使用起来跟普通java对象相似,你可以在RHS部分中new一个并且使用getter和setter方法去操作其属性。 224 | 225 | declare Address 226 | @author(quzishen) // 元数据,仅用于描述信息 227 | 228 | @createTime(2011-1-24) 229 | city : String @maxLengh(100) 230 | postno : int 231 | end 232 | 233 | 上述的'@'是什么呢?是元数据定义,用于描述数据的数据~,没什么执行含义 234 | 235 | 你可以在RHS部分中使用Address address = new Address()的方法来定义一个对象。 236 | 237 | 238 | ---- 239 | 240 | 2、Drools应用实例: 241 | 现在我们模拟一个应用场景:网站伴随业务产生而进行的积分发放操作。比如支付宝信用卡还款奖励积分等。 242 | 243 | 发放积分可能伴随不同的运营策略和季节性调整,发放数目和规则完全不同,如果使用硬编码的方式去伴随业务调整而修改,代码的修改、管理、优化、测试、上线将是一件非常麻烦的事情,所以,将发放规则部分提取出来,交给Drools管理,可以极大程度的解决这个问题。 244 | 245 | (注意一点的是,并非所有的规则相关内容都建议使用Drools,这其中要考虑系统会运行多久,规则变更频率等一系列条件,如果你的系统只会在线上运行一周,那根本没必要选择Drools来加重你的开发成本,java硬编码的方式则将是首选) 246 | 247 | 我们定义一下发放规则: 248 | 249 | 积分的发放参考因素有:交易笔数、交易金额数目、信用卡还款次数、生日特别优惠等。 250 | 251 | 定义规则: 252 | 253 | // 过生日,则加10分,并且将当月交易比数翻倍后再计算积分 254 | 255 | // 2011-01-08 - 2011-08-08每月信用卡还款3次以上,每满3笔赠送30分 256 | 257 | // 当月购物总金额100以上,每100元赠送10分 258 | 259 | // 当月购物次数5次以上,每五次赠送50分 260 | 261 | // 特别的,如果全部满足了要求,则额外奖励100分 262 | 263 | // 发生退货,扣减10分 264 | 265 | // 退货金额大于100,扣减100分 266 | 267 | 在事先分析过程中,我们需要全面的考虑对于积分所需要的因素,以此整理抽象Fact对象,通过上述的假设条件,我们假设积分计算对象如下: 268 | 269 | [java] view plaincopy 270 | /** 271 | * 积分计算对象 272 | * @author quzishen 273 | */ 274 | public class PointDomain { 275 | // 用户名 276 | private String userName; 277 | // 是否当日生日 278 | private boolean birthDay; 279 | // 增加积分数目 280 | private long point; 281 | // 当月购物次数 282 | private int buyNums; 283 | // 当月退货次数 284 | private int backNums; 285 | // 当月购物总金额 286 | private double buyMoney; 287 | // 当月退货总金额 288 | private double backMondy; 289 | // 当月信用卡还款次数 290 | private int billThisMonth; 291 | 292 | /** 293 | * 记录积分发送流水,防止重复发放 294 | * @param userName 用户名 295 | * @param type 积分发放类型 296 | */ 297 | public void recordPointLog(String userName, String type){ 298 | System.out.println("增加对"+userName+"的类型为"+type+"的积分操作记录."); 299 | } 300 | 301 | public String getUserName() { 302 | return userName; 303 | } 304 | // 其他getter setter方法省略 305 | } 306 | 定义积分规则接口 307 | 308 | ``` 309 | /** 310 | * 规则接口 311 | * @author quzishen 312 | */ 313 | public interface PointRuleEngine { 314 | 315 | /** 316 | * 初始化规则引擎 317 | */ 318 | public void initEngine(); 319 | 320 | /** 321 | * 刷新规则引擎中的规则 322 | */ 323 | public void refreshEnginRule(); 324 | 325 | /** 326 | * 执行规则引擎 327 | * @param pointDomain 积分Fact 328 | */ 329 | public void executeRuleEngine(final PointDomain pointDomain); 330 | } 331 | ``` 332 | 规则接口实现,Drools的API很简单,可以参考相关API文档查看具体用法: 333 | 334 | ``` 335 | import java.io.File; 336 | import java.io.FileNotFoundException; 337 | import java.io.FileReader; 338 | import java.io.IOException; 339 | import java.io.Reader; 340 | import java.util.ArrayList; 341 | import java.util.List; 342 | 343 | import org.drools.RuleBase; 344 | import org.drools.StatefulSession; 345 | import org.drools.compiler.DroolsParserException; 346 | import org.drools.compiler.PackageBuilder; 347 | import org.drools.spi.Activation; 348 | 349 | /** 350 | * 规则接口实现类 351 | * @author quzishen 352 | */ 353 | public class PointRuleEngineImpl implements PointRuleEngine { 354 | private RuleBase ruleBase; 355 | 356 | /* (non-Javadoc) 357 | * @see com.drools.demo.point.PointRuleEngine#initEngine() 358 | */ 359 | public void initEngine() { 360 | // 设置时间格式 361 | System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss"); 362 | ruleBase = RuleBaseFacatory.getRuleBase(); 363 | try { 364 | PackageBuilder backageBuilder = getPackageBuilderFromDrlFile(); 365 | ruleBase.addPackages(backageBuilder.getPackages()); 366 | } catch (DroolsParserException e) { 367 | e.printStackTrace(); 368 | } catch (IOException e) { 369 | e.printStackTrace(); 370 | } catch (Exception e) { 371 | e.printStackTrace(); 372 | } 373 | } 374 | 375 | /* (non-Javadoc) 376 | * @see com.drools.demo.point.PointRuleEngine#refreshEnginRule() 377 | */ 378 | public void refreshEnginRule() { 379 | ruleBase = RuleBaseFacatory.getRuleBase(); 380 | org.drools.rule.Package[] packages = ruleBase.getPackages(); 381 | for(org.drools.rule.Package pg : packages) { 382 | ruleBase.removePackage(pg.getName()); 383 | } 384 | 385 | initEngine(); 386 | } 387 | 388 | /* (non-Javadoc) 389 | * @see com.drools.demo.point.PointRuleEngine#executeRuleEngine(com.drools.demo.point.PointDomain) 390 | */ 391 | public void executeRuleEngine(final PointDomain pointDomain) { 392 | if(null == ruleBase.getPackages() || 0 == ruleBase.getPackages().length) { 393 | return; 394 | } 395 | 396 | StatefulSession statefulSession = ruleBase.newStatefulSession(); 397 | statefulSession.insert(pointDomain); 398 | 399 | // fire 400 | statefulSession.fireAllRules(new org.drools.spi.AgendaFilter() { 401 | public boolean accept(Activation activation) { 402 | return !activation.getRule().getName().contains("_test"); 403 | } 404 | }); 405 | 406 | statefulSession.dispose(); 407 | } 408 | 409 | /** 410 | * 从Drl规则文件中读取规则 411 | * @return 412 | * @throws Exception 413 | */ 414 | private PackageBuilder getPackageBuilderFromDrlFile() throws Exception { 415 | // 获取测试脚本文件 416 | List drlFilePath = getTestDrlFile(); 417 | // 装载测试脚本文件 418 | List readers = readRuleFromDrlFile(drlFilePath); 419 | 420 | PackageBuilder backageBuilder = new PackageBuilder(); 421 | for (Reader r : readers) { 422 | backageBuilder.addPackageFromDrl(r); 423 | } 424 | 425 | // 检查脚本是否有问题 426 | if(backageBuilder.hasErrors()) { 427 | throw new Exception(backageBuilder.getErrors().toString()); 428 | } 429 | 430 | return backageBuilder; 431 | } 432 | 433 | /** 434 | * @param drlFilePath 脚本文件路径 435 | * @return 436 | * @throws FileNotFoundException 437 | */ 438 | private List readRuleFromDrlFile(List drlFilePath) throws FileNotFoundException { 439 | if (null == drlFilePath || 0 == drlFilePath.size()) { 440 | return null; 441 | } 442 | 443 | List readers = new ArrayList(); 444 | 445 | for (String ruleFilePath : drlFilePath) { 446 | readers.add(new FileReader(new File(ruleFilePath))); 447 | } 448 | 449 | return readers; 450 | } 451 | 452 | /** 453 | * 获取测试规则文件 454 | * 455 | * @return 456 | */ 457 | private List getTestDrlFile() { 458 | List drlFilePath = new ArrayList(); 459 | drlFilePath 460 | .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/addpoint.drl"); 461 | drlFilePath 462 | .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/subpoint.drl"); 463 | 464 | return drlFilePath; 465 | } 466 | } 467 | ``` 468 | 为了获取单实例的RuleBase,我们定义一个工厂类 469 | 470 | ``` 471 | import org.drools.RuleBase; 472 | import org.drools.RuleBaseFactory; 473 | 474 | /** 475 | * RuleBaseFacatory 单实例RuleBase生成工具 476 | * @author quzishen 477 | */ 478 | public class RuleBaseFacatory { 479 | private static RuleBase ruleBase; 480 | 481 | public static RuleBase getRuleBase(){ 482 | return null != ruleBase ? ruleBase : RuleBaseFactory.newRuleBase(); 483 | } 484 | } 485 | ``` 486 | 剩下的就是定义两个规则文件,分别用于积分发放和积分扣减 487 | 488 | addpoint.drl 489 | 490 | ``` 491 | package com.drools.demo.point 492 | 493 | import com.drools.demo.point.PointDomain; 494 | 495 | rule birthdayPoint 496 | // 过生日,则加10分,并且将当月交易比数翻倍后再计算积分 497 | salience 100 498 | lock-on-active true 499 | when 500 | $pointDomain : PointDomain(birthDay == true) 501 | then 502 | $pointDomain.setPoint($pointDomain.getPoint()+10); 503 | $pointDomain.setBuyNums($pointDomain.getBuyNums()*2); 504 | $pointDomain.setBuyMoney($pointDomain.getBuyMoney()*2); 505 | $pointDomain.setBillThisMonth($pointDomain.getBillThisMonth()*2); 506 | 507 | $pointDomain.recordPointLog($pointDomain.getUserName(),"birthdayPoint"); 508 | end 509 | 510 | rule billThisMonthPoint 511 | // 2011-01-08 - 2011-08-08每月信用卡还款3次以上,每满3笔赠送30分 512 | salience 99 513 | lock-on-active true 514 | date-effective "2011-01-08 23:59:59" 515 | date-expires "2011-08-08 23:59:59" 516 | when 517 | $pointDomain : PointDomain(billThisMonth >= 3) 518 | then 519 | $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBillThisMonth()/3*30); 520 | $pointDomain.recordPointLog($pointDomain.getUserName(),"billThisMonthPoint"); 521 | end 522 | 523 | rule buyMoneyPoint 524 | // 当月购物总金额100以上,每100元赠送10分 525 | salience 98 526 | lock-on-active true 527 | when 528 | $pointDomain : PointDomain(buyMoney >= 100) 529 | then 530 | $pointDomain.setPoint($pointDomain.getPoint()+ (int)$pointDomain.getBuyMoney()/100 * 10); 531 | $pointDomain.recordPointLog($pointDomain.getUserName(),"buyMoneyPoint"); 532 | end 533 | 534 | rule buyNumsPoint 535 | // 当月购物次数5次以上,每五次赠送50分 536 | salience 97 537 | lock-on-active true 538 | when 539 | $pointDomain : PointDomain(buyNums >= 5) 540 | then 541 | $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBuyNums()/5 * 50); 542 | $pointDomain.recordPointLog($pointDomain.getUserName(),"buyNumsPoint"); 543 | end 544 | 545 | rule allFitPoint 546 | // 特别的,如果全部满足了要求,则额外奖励100分 547 | salience 96 548 | lock-on-active true 549 | when 550 | $pointDomain:PointDomain(buyNums >= 5 && billThisMonth >= 3 && buyMoney >= 100) 551 | then 552 | $pointDomain.setPoint($pointDomain.getPoint()+ 100); 553 | $pointDomain.recordPointLog($pointDomain.getUserName(),"allFitPoint"); 554 | end 555 | subpoint.drl 556 | 557 | [java] view plaincopy 558 | package com.drools.demo.point 559 | 560 | import com.drools.demo.point.PointDomain; 561 | 562 | rule subBackNumsPoint 563 | // 发生退货,扣减10分 564 | salience 10 565 | lock-on-active true 566 | when 567 | $pointDomain : PointDomain(backNums >= 1) 568 | then 569 | $pointDomain.setPoint($pointDomain.getPoint()-10); 570 | $pointDomain.recordPointLog($pointDomain.getUserName(),"subBackNumsPoint"); 571 | end 572 | 573 | rule subBackMondyPoint 574 | // 退货金额大于100,扣减100分 575 | salience 9 576 | lock-on-active true 577 | when 578 | $pointDomain : PointDomain(backMondy >= 100) 579 | then 580 | $pointDomain.setPoint($pointDomain.getPoint()-10); 581 | $pointDomain.recordPointLog($pointDomain.getUserName(),"subBackMondyPoint"); 582 | end 583 | ``` 584 | 585 | 测试方法: 586 | 587 | ``` 588 | public static void main(String[] args) throws IOException { 589 | PointRuleEngine pointRuleEngine = new PointRuleEngineImpl(); 590 | while(true){ 591 | InputStream is = System.in; 592 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 593 | String input = br.readLine(); 594 | 595 | if(null != input && "s".equals(input)){ 596 | System.out.println("初始化规则引擎..."); 597 | pointRuleEngine.initEngine(); 598 | System.out.println("初始化规则引擎结束."); 599 | }else if("e".equals(input)){ 600 | final PointDomain pointDomain = new PointDomain(); 601 | pointDomain.setUserName("hello kity"); 602 | pointDomain.setBackMondy(100d); 603 | pointDomain.setBuyMoney(500d); 604 | pointDomain.setBackNums(1); 605 | pointDomain.setBuyNums(5); 606 | pointDomain.setBillThisMonth(5); 607 | pointDomain.setBirthDay(true); 608 | pointDomain.setPoint(0l); 609 | 610 | pointRuleEngine.executeRuleEngine(pointDomain); 611 | 612 | System.out.println("执行完毕BillThisMonth:"+pointDomain.getBillThisMonth()); 613 | System.out.println("执行完毕BuyMoney:"+pointDomain.getBuyMoney()); 614 | System.out.println("执行完毕BuyNums:"+pointDomain.getBuyNums()); 615 | 616 | System.out.println("执行完毕规则引擎决定发送积分:"+pointDomain.getPoint()); 617 | } else if("r".equals(input)){ 618 | System.out.println("刷新规则文件..."); 619 | pointRuleEngine.refreshEnginRule(); 620 | System.out.println("刷新规则文件结束."); 621 | } 622 | } 623 | } 624 | ``` 625 | 执行结果: 626 |
627 | 增加对hello kity的类型为birthdayPoint的积分操作记录.
628 | 增加对hello kity的类型为billThisMonthPoint的积分操作记录.
629 | 增加对hello kity的类型为buyMoneyPoint的积分操作记录.
630 | 增加对hello kity的类型为buyNumsPoint的积分操作记录.
631 | 增加对hello kity的类型为allFitPoint的积分操作记录.
632 | 增加对hello kity的类型为subBackNumsPoint的积分操作记录.
633 | 增加对hello kity的类型为subBackMondyPoint的积分操作记录.
634 | 执行完毕BillThisMonth:10
635 | 执行完毕BuyMoney:1000.0
636 | 执行完毕BuyNums:10
637 | 执行完毕规则引擎决定发送积分:380
638 | 
-------------------------------------------------------------------------------- /src/main/java/com/lanux/drools/RuleTest.java: -------------------------------------------------------------------------------- 1 | package com.lanux.drools; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | 8 | public class RuleTest { 9 | public static void main(String[] args) throws IOException { 10 | PointRuleEngine pointRuleEngine = null; 11 | while(true){ 12 | InputStream is = System.in; 13 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 14 | String input = br.readLine(); 15 | 16 | if(null != input && "s".equals(input)){ 17 | System.out.println("初始化规则引擎..."); 18 | pointRuleEngine.initEngine(); 19 | System.out.println("初始化规则引擎结束."); 20 | }else if("e".equals(input)){ 21 | final PointDomain pointDomain = new PointDomain(); 22 | pointDomain.setUserName("hello kity"); 23 | pointDomain.setBackMondy(100d); 24 | pointDomain.setBuyMoney(500d); 25 | pointDomain.setBackNums(1); 26 | pointDomain.setBuyNums(5); 27 | pointDomain.setBillThisMonth(5); 28 | pointDomain.setBirthDay(true); 29 | pointDomain.setPoint(0l); 30 | 31 | pointRuleEngine.executeRuleEngine(pointDomain); 32 | 33 | System.out.println("执行完毕BillThisMonth:"+pointDomain.getBillThisMonth()); 34 | System.out.println("执行完毕BuyMoney:"+pointDomain.getBuyMoney()); 35 | System.out.println("执行完毕BuyNums:"+pointDomain.getBuyNums()); 36 | 37 | System.out.println("执行完毕规则引擎决定发送积分:"+pointDomain.getPoint()); 38 | } else if("r".equals(input)){ 39 | System.out.println("刷新规则文件..."); 40 | pointRuleEngine.refreshEnginRule(); 41 | System.out.println("刷新规则文件结束."); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/NetConfig.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io; 2 | 3 | /** 4 | * Created by lanux on 2017/8/6. 5 | */ 6 | public class NetConfig { 7 | public static final String SERVER_IP = "127.0.0.1"; 8 | public static final int SERVER_PORT = 3338; 9 | public static final int SO_TIMEOUT = 30*1000; 10 | public static final int BUFFER_SIZE = 1024; 11 | public static final int LENGTH_VALUE_BYTES = 4;// 报文内容长度值 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/README.md: -------------------------------------------------------------------------------- 1 | # Linux 5种IO模型 2 | 3 | #### 一、概念 4 | 网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。 5 | 6 | 对于一次IO访问(以read为例): 7 | > 第一阶段:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
8 | > 第二阶段:把数据从内核缓冲区复制到应用进程缓冲区。 9 | 10 | ##### 数据流: 11 | 磁盘\键盘\鼠标\网卡 ==> 操作系统内核缓冲区 ==> 用户进程缓冲区 12 | 13 | ##### 同步vs异步 14 | 同步和异步关注的是消息通信机制。 15 | 16 | > **同步**,在发出调用时,调用者主动等待这个调用的结果。
17 | > **异步**,调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。 18 | 19 | IO中的同步与异步是针对应用程序与内核的交互而言的。 20 | 21 | 1. **同步**是指用户线程发起 I/O 请求后需要等待或者轮询内核 I/O是否完成,然后再继续。
22 | 1. **异步**过程中用户线程发起 I/O 请求,直接返回,做自己的事情,IO交给内核来处理,当内核 I/O 操作完成后会通知用户线程完成。 23 | 24 | 25 | ##### 阻塞vs非阻塞 26 | 阻塞和非阻塞关注的是程序(线程)在等待调用结果(消息/返回值,无所谓同步还是异步)时的状态。 27 | > 阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能做其他事情。 28 | 29 | 30 | 1. 如果这个线程在等待当前函数返回时,仍在执行其他消息处理,那这种情况就叫做同步非阻塞;
31 | 1. 如果这个线程在等待当前函数返回时,没有执行其他消息处理,而是处于挂起等待状态,那这种情况就叫做同步阻塞; 32 | 33 | 34 | >- 阻塞的方式可以明显的提高CPU的利用率(不占用CPU时间片),但是也带了另外一种后果就是系统的线程切换增加。增加的CPU执行时间能不能补偿系统的切换成本需要好好评估。
35 | >- ==阻塞和同步不同==, *同步调用来说,很多时候当前线程可能还是激活的没有被挂起,只是从逻辑上当前函数没有返回而已,此时,这个线程可能也会处理其他的消息。* 36 | 37 | ##### 上下文切换 38 | > 线程是由CPU进行调度的,单独CPU(多核处理器当作多个单独CPU来识别的)一个时间片内只执行一个线程上下文内的线程,当CPU由执行线程A切换到执行线程B的过程中会发生一些列的操作,这些操作主要有”保存线程A的执行现场“然后”载入线程B的执行现场”,这个过程称之为“上下文切换(context switch)”,这个上下文切换过程并不廉价,如果没有必要,应该尽量减少上下文切换的发生。 39 | 40 | 41 | #### 二、IO分类 42 | - **同步IO(synchronous IO)** 43 | > 1. 阻塞IO(bloking IO)
44 | > 1. 非阻塞IO(non-blocking IO)
45 | > 1. 多路复用IO(multiplexing IO)
46 | > 1. 信号驱动式IO(signal-driven IO)
47 | 48 | - **异步IO(asynchronous IO)** 49 | 50 | ==*注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model*。== 51 | 52 | ![IO分类对比](https://static.oschina.net/uploads/img/201604/21095604_vhHX.png) 53 | 54 | 55 | 1. 同步阻塞 56 | 57 | 用户线程同步等待结果,线程被挂起,不做其他事情。在linux中,默认情况下所有的socket都是blocking 58 | 59 | ``` 60 | //read 阻塞 61 | byte[] buf = new byte[2048]; 62 | int count = socket.getInputStream().read(buf); 63 | 64 | // readline()阻塞直到整行读完 65 | BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 66 | String data = reader.readLine(); 67 | 68 | ``` 69 | ![同步阻塞](https://static.oschina.net/uploads/img/201604/20150405_VKYH.png) 70 | 71 | --- 72 | 73 | 1. 同步非阻塞 74 | 75 | 用户线程不断询问内核,直到内核缓冲区有数据。在相邻两次询问间隙,用户线程可以释放cpu时间片给其它用户线程线程使用(相当于java 里面wait操作),会带来频繁的上下文切换。 76 | 77 | ![同步非阻塞](https://static.oschina.net/uploads/img/201604/20152818_DXcj.png) 78 | 79 | --- 80 | 81 | 1. 多路复用 82 | 83 | 多路就是一个线程管理多路IO,此线程还是被阻塞调用,其中一路或几路IO有任何事件发生就通知用户线程处理即可。相当于用户线程只剩下IO的第二阶段,避免传统的 I/O 阻塞。**最大化 CPU 利用率,减少用户线程切换** 84 | 85 | ![多路复用](https://static.oschina.net/uploads/space/2017/0522/112804_1mhz_2939155.png) 86 | 87 | --- 88 | 89 | 1. 信号驱动 90 | 91 | 给一个IO注册一个信号和信号触发的回调函数,一旦信号被触发,回调函数里读取数据。 92 | 93 | 例如给 socket注册一个“可读”的信号,当数据来了,可读的时候,信号被触发,执行回调函数从内核cache复制数据到用户空间。 94 | ![信号驱动](https://static.oschina.net/uploads/img/201604/21091434_DsZb.png) 95 | 96 | --- 97 | 98 | 1. 异步IO 99 | 100 | 异步IO中,操作系统完成了数据从内核到用户空间的拷贝后,以信号的方式通知用户线程可以下一步操作。省去了用户线程阻塞下来拷贝数据的过程。 101 | 102 | ![异步IO](https://static.oschina.net/uploads/space/2017/0522/114136_u5gw_2939155.png) 103 | 104 | 105 | 106 | **高性能,且业界普遍使用的方案,也就是后两种。** 107 | I/O 多路复用往往对应 Reactor 模式,异步 I/O 往往对应 Proactor。 108 | 109 | Reactor 一般使用 epoll+ 事件驱动 的经典模式,通过 分治 的手段,把耗时的网络连接、安全认证、编码等工作交给专门的线程池或者进程去完成,然后再去调用真正的核心业务逻辑层,这在 *nix 系统中被广泛使用。 110 | 111 | 著名的 Redis、Nginx、Node.js 的 Socket I/O 都用的这个,Java 的 NIO 框架 Netty 也是,Spark 2.0 RPC 所依赖的同样采用了 Reactor 模式。 112 | 113 | 114 | 115 | #### 三、JAVA NIO 116 | 流是单向的,通道是双向的。 117 | IO vs NIO: 118 | 119 | IO | NIO | 说明 120 | ------------ | ------------- | ------------- 121 | 面向流 | 面向缓冲 | Java IO每次从流中读字节,直至读取所有字节,它们没有被缓存在任何地方。 java NIO数据读取到一个缓冲区,需要时可在缓冲区中前后移动。 122 | 阻塞IO | 非阻塞IO | java IO线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。java NIO线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作 123 | 无 | 选择器 | 无 124 | 125 | 126 | [参考] 127 | 128 | [深入剖析 Netty 源码设计(一)——深入理解 select poll epoll 机制](http://www.6aiq.com/article/1548222475606) 129 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/bio/BioBasic.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | import com.lanux.io.NetConfig; 4 | import com.lanux.tool.ByteUtil; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | 11 | /** 12 | * Created by lanux on 2017/9/18. 13 | */ 14 | public class BioBasic { 15 | 16 | public byte[] readStream2(InputStream inputStream) throws IOException { 17 | ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 18 | byte[] bytes = new byte[NetConfig.LENGTH_VALUE_BYTES]; 19 | int readLen;//本次读取的字节数 20 | int hadRead = 0;//已经读取数据长度 21 | int bodyLength = -1; 22 | while ((readLen = inputStream.read(bytes)) != -1) { 23 | hadRead += readLen; 24 | if (bodyLength == -1) { 25 | if (hadRead < NetConfig.LENGTH_VALUE_BYTES) continue; 26 | bodyLength = ByteUtil.byteArrayToInt(bytes); 27 | bytes = new byte[NetConfig.BUFFER_SIZE];// 开始读取报文内容,字节数组用长一点的 28 | hadRead = 0;// 开始读取报文内容,hadRead重置0 29 | } else { 30 | outSteam.write(bytes, 0, readLen); 31 | if (hadRead == bodyLength) break; 32 | } 33 | } 34 | outSteam.flush();// ByteArrayOutputStream.flush() 无意义,空函数 35 | return outSteam.toByteArray(); 36 | } 37 | 38 | public byte[] readStream(InputStream inputStream) throws IOException { 39 | byte[] bytes = new byte[NetConfig.LENGTH_VALUE_BYTES]; 40 | int readLen;//本次读取的字节数 41 | int bodyLength = -1;// 报文内容长度 42 | int offset = 0;//已经读取数据的偏移量 43 | int limit = NetConfig.LENGTH_VALUE_BYTES;// input stream 读取长度逐渐减少 44 | while ((readLen = inputStream.read(bytes, offset, limit)) != -1) { 45 | offset += readLen; 46 | limit -= readLen; 47 | if (bodyLength == -1) { 48 | // 报文内容长度值还未读取出来 49 | if (offset < NetConfig.LENGTH_VALUE_BYTES)continue; 50 | bodyLength = ByteUtil.byteArrayToInt(bytes); 51 | bytes = new byte[bodyLength];// 开始读取报文内容,初始化报文长度相当的字节数组来承载报文内容 52 | limit = bodyLength;// 初始化 报文内容剩余待读长度= bodyLength 53 | offset = 0;// 开始读取报文内容,offset重置0 54 | } else { 55 | if (offset == bodyLength) { 56 | break;// 报文内容已经读取完毕 57 | } 58 | } 59 | } 60 | return bytes; 61 | } 62 | 63 | public void writeStream(OutputStream ops, byte[] bytes) throws IOException { 64 | ops.write(ByteUtil.intToByteArray(bytes.length)); 65 | ops.write(bytes); 66 | ops.flush(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/bio/BioClient.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | import com.lanux.io.NetConfig; 4 | import com.lanux.tool.StringTool; 5 | 6 | import java.io.Closeable; 7 | import java.io.IOException; 8 | import java.net.Socket; 9 | 10 | /** 11 | * Created by lanux on 2017/8/6. 12 | */ 13 | public class BioClient extends BioBasic implements Closeable { 14 | 15 | public Socket socket; 16 | 17 | public BioClient init() throws Exception { 18 | socket = new Socket(NetConfig.SERVER_IP, NetConfig.SERVER_PORT); 19 | socket.setKeepAlive(true); 20 | socket.setSoTimeout(NetConfig.SO_TIMEOUT); 21 | return this; 22 | } 23 | 24 | @Override 25 | public void close() throws IOException { 26 | if (socket != null && !socket.isClosed()) { 27 | socket.close(); 28 | } 29 | } 30 | 31 | public void write(String value) throws IOException { 32 | byte[] bytes = value.getBytes(); 33 | System.out.println("client write " + bytes.length + " : " + StringTool.maxString(value, 50)); 34 | super.writeStream(socket.getOutputStream(), bytes); 35 | } 36 | 37 | public String read() throws IOException { 38 | byte[] bytes = super.readStream(socket.getInputStream()); 39 | String s = new String(bytes); 40 | System.out.println("client read " + bytes.length + " : " + StringTool.maxString(s, 50)); 41 | return s; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/bio/BioServer.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | import com.lanux.io.NetConfig; 4 | import com.lanux.tool.StringTool; 5 | 6 | import java.io.IOException; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.ThreadFactory; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | /** 15 | * Created by lanux on 2017/8/6. 16 | */ 17 | public class BioServer extends BioBasic { 18 | 19 | private ServerSocket serverSocket; 20 | private volatile boolean running; 21 | 22 | private static ExecutorService executor = Executors 23 | .newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { 24 | final AtomicInteger threadNumber = new AtomicInteger(1); 25 | 26 | @Override 27 | public Thread newThread(Runnable r) { 28 | return new Thread(r, "server-thread-" + threadNumber.getAndIncrement()); 29 | } 30 | }); 31 | 32 | public BioServer() { 33 | try { 34 | serverSocket = new ServerSocket(NetConfig.SERVER_PORT); 35 | // SO_TIMEOUT:表示等待客户连接的超时时间。 36 | // SO_REUSEADDR:表示是否允许重用服务器所绑定的地址。 37 | // SO_RCVBUF:表示接收数据的缓冲区的大小。 38 | // serverSocket.setSoTimeout(NetConfig.SO_TIMEOUT); 39 | // serverSocket.setReuseAddress(true); 40 | // serverSocket.setReceiveBufferSize(64*1024); 41 | running = true; 42 | while (true) { 43 | final Socket socket = serverSocket.accept(); 44 | executor.submit(() -> handle(socket)); 45 | } 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } finally { 49 | running = false; 50 | if (serverSocket != null && !serverSocket.isClosed()) { 51 | try { 52 | serverSocket.close(); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | } 58 | 59 | } 60 | 61 | private void handle(Socket socket) { 62 | try { 63 | //循环read write Stream,一直没有close可能会爆内存。 64 | //所以阻塞IO一般一个连接使用一次,不会循环利用。 65 | while (running) { 66 | byte[] bytes = readStream(socket.getInputStream()); 67 | if (bytes != null && bytes.length > 0) { 68 | String s = new String(bytes); 69 | System.out.println(Thread.currentThread().getName() + " received " + bytes.length + " response : " + 70 | StringTool.maxString(s, 50)); 71 | writeStream(socket.getOutputStream(), s.getBytes()); 72 | } 73 | } 74 | } catch (IOException e) { 75 | e.printStackTrace(); 76 | } finally { 77 | if (socket != null) { 78 | try { 79 | socket.close(); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/Header.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by lanux on 2017/9/16. 8 | */ 9 | public class Header { 10 | private int crcCode = 0xABEF0101; 11 | 12 | private int length; 13 | 14 | private long sessionID = 394689346l; 15 | 16 | private byte type = 1; 17 | 18 | private byte priority; 19 | 20 | private Map attachment = new HashMap(); 21 | 22 | public int getCrcCode() { 23 | return crcCode; 24 | } 25 | 26 | public void setCrcCode(int crcCode) { 27 | this.crcCode = crcCode; 28 | } 29 | 30 | public int getLength() { 31 | return length; 32 | } 33 | 34 | public void setLength(int length) { 35 | this.length = length; 36 | } 37 | 38 | public long getSessionID() { 39 | return sessionID; 40 | } 41 | 42 | public void setSessionID(long sessionID) { 43 | this.sessionID = sessionID; 44 | } 45 | 46 | public byte getType() { 47 | return type; 48 | } 49 | 50 | public void setType(byte type) { 51 | this.type = type; 52 | } 53 | 54 | public byte getPriority() { 55 | return priority; 56 | } 57 | 58 | public void setPriority(byte priority) { 59 | this.priority = priority; 60 | } 61 | 62 | public Map getAttachment() { 63 | return attachment; 64 | } 65 | 66 | public void setAttachment(Map attachment) { 67 | this.attachment = attachment; 68 | } 69 | 70 | public String toString() { 71 | return "Header [crcCode=" + crcCode + ", length=" + length + ", sessionID=" + sessionID 72 | + ", type=" + type + ", priority=" + priority + ", attachment=" + attachment + "]"; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.JavaType; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.text.SimpleDateFormat; 14 | 15 | public class JsonUtil { 16 | 17 | private static Logger log = LoggerFactory.getLogger(JsonUtil.class); 18 | 19 | private static ObjectMapper mapper = new ObjectMapper(); 20 | 21 | static { 22 | mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 23 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 24 | //mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); 25 | // mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 26 | 27 | } 28 | 29 | /* 30 | * public static void writeValue(HttpServletResponse response, Object o) throws Exception { 31 | * mapper.writeValue(response.getWriter(), o); } 32 | */ 33 | 34 | public static ObjectMapper getMapper() { 35 | return mapper; 36 | } 37 | 38 | /** 39 | * json 转 JsonNode 40 | * 41 | * @param jsonString 42 | * @return 43 | */ 44 | public static JsonNode toJsonNode(String jsonString) { 45 | try { 46 | return mapper.readTree(jsonString); 47 | } catch (Exception e) { 48 | log.error(jsonString, e); 49 | } 50 | return null; 51 | } 52 | 53 | /** 54 | * 从json中抽取子json 55 | * 56 | * @param fieldName 57 | * @param jsonString 58 | * @return 59 | */ 60 | public static JsonNode getJsonNode(String fieldName, String jsonString) { 61 | JsonNode jn = toJsonNode(jsonString); 62 | if (jn != null) { 63 | return jn.get(fieldName); 64 | } 65 | return null; 66 | } 67 | 68 | /** 69 | * 从json中抽取子json,转Object 70 | * 71 | * @param fieldName 72 | * @param jsonString 73 | * @return 74 | */ 75 | public static T fromJsonNode(String fieldName, String jsonString, Class clazz) { 76 | JsonNode jn = getJsonNode(fieldName, jsonString); 77 | if (jn != null) { 78 | return fromJson(jn.get(fieldName).toString(), clazz); 79 | } 80 | return null; 81 | } 82 | 83 | /** 84 | * 从json中抽取子json,转Object 85 | * 86 | * @param fieldName 87 | * @param jsonString 88 | * @return 89 | */ 90 | public static T fromJsonNode(String fieldName, String jsonString, TypeReference tr) { 91 | JsonNode jn = getJsonNode(fieldName, jsonString); 92 | if (jn != null) { 93 | return fromJson(jn.get(fieldName).toString(), tr); 94 | } 95 | return null; 96 | } 97 | 98 | /** 99 | * T 可以是List,Map 100 | * 101 | * @param 102 | * @param jsonString 103 | * @param tr 104 | * @return 105 | */ 106 | public static T fromJson(String jsonString, TypeReference tr) { 107 | try { 108 | return mapper.readValue(jsonString, tr); 109 | } catch (Exception e) { 110 | log.error(jsonString, e); 111 | } 112 | return null; 113 | } 114 | 115 | /** 116 | * Object可以是POJO,也可以是Collection或数组。 如果对象为Null, 返回"null". 如果集合为空集合, 返回"[]". 117 | * 118 | * @param Object 119 | */ 120 | public static String toJson(Object object) { 121 | try { 122 | return mapper.writeValueAsString(object); 123 | } catch (Exception e) { 124 | log.error("toJson", e); 125 | } 126 | return null; 127 | } 128 | 129 | /** 130 | * 反序列化POJO或简单Collection如List. 131 | *

132 | * 如果JSON字符串为Null或"null"字符串, 返回Null. 如果JSON字符串为"[]", 返回空集合. 133 | *

134 | * 如需反序列化复杂Collection如List, 请使用fromJson(String,JavaType) 135 | * 136 | * @see #fromJson(String, JavaType) 137 | */ 138 | @JsonCreator 139 | public static T fromJson(String jsonString, Class clazz) { 140 | try { 141 | return mapper.readValue(jsonString, clazz); 142 | } catch (Exception e) { 143 | log.error(jsonString, e); 144 | } 145 | return null; 146 | } 147 | 148 | /** 149 | * 反序列化复杂Collection如List, 先使用函数createCollectionType构造类型,然后调用本函数. 150 | * 151 | * @see #createCollectionType(Class, Class...) 152 | */ 153 | public static T fromJson(String jsonString, JavaType javaType) { 154 | try { 155 | return mapper.readValue(jsonString, javaType); 156 | } catch (Exception e) { 157 | log.error(jsonString, e); 158 | } 159 | return null; 160 | } 161 | 162 | public static NettyMessage fromJson(byte[] array, Class nettyMessageClass) { 163 | try { 164 | return mapper.readValue(array, NettyMessage.class); 165 | } catch (IOException e) { 166 | e.printStackTrace(); 167 | log.error(e.getMessage(), e); 168 | } 169 | return null; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/NettyClient.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import com.lanux.io.NetConfig; 4 | import io.netty.bootstrap.Bootstrap; 5 | import io.netty.buffer.PooledByteBufAllocator; 6 | import io.netty.channel.*; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | import org.apache.commons.lang3.RandomStringUtils; 12 | import org.apache.commons.lang3.RandomUtils; 13 | import org.apache.commons.lang3.StringUtils; 14 | 15 | import java.util.concurrent.ThreadFactory; 16 | import java.util.concurrent.TimeUnit; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | 19 | /** 20 | * Created by lanux on 2017/9/16. 21 | */ 22 | public class NettyClient { 23 | //nThreads=0: Netty 会首先从系统属性中获取 "io.netty.eventLoopThreads" 的值, 如果我们没有设置它的话, 那么就返回默认值: 处理器核心数 * 2. 24 | EventLoopGroup workerGroup = new NioEventLoopGroup(0, 25 | new ThreadFactory() { 26 | AtomicInteger count = new AtomicInteger(1); 27 | 28 | @Override 29 | public Thread newThread(Runnable r) { 30 | return new Thread(r, "NioEventLoop-" + count.getAndIncrement()); 31 | } 32 | }); 33 | 34 | final Bootstrap b = new Bootstrap(); 35 | 36 | public NettyClient() throws Exception { 37 | b.group(workerGroup) 38 | .channel(NioSocketChannel.class) 39 | .option(ChannelOption.SO_KEEPALIVE, true) 40 | .option(ChannelOption.TCP_NODELAY, true) 41 | .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 42 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, NetConfig.SO_TIMEOUT) 43 | //Each channel has its own pipeline and it is created automatically when a new channel is created. 44 | .handler(new ChildChannelHandler()); 45 | } 46 | 47 | public static class ChildChannelHandler extends 48 | ChannelInitializer { 49 | 50 | @Override 51 | protected void initChannel(SocketChannel ch) throws Exception { 52 | ch.pipeline().addLast(new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS)) 53 | .addLast(new NettyMessageDecoder()) 54 | .addLast(new NettyMessageEncoder()) 55 | .addLast(new NettyMessageHandler()); 56 | } 57 | 58 | } 59 | 60 | public Channel connect(String host, int port) { 61 | Channel channel; 62 | ChannelFuture connectFuture = b.connect(host, port); 63 | System.out.println("Netty time Client connected at port " + port); 64 | try { 65 | channel = connectFuture.sync().channel(); 66 | } catch (Exception e) { 67 | // TODO 需要进一步验证这种机制,是否可以确保关闭连接。 68 | connectFuture.cancel(true); 69 | channel = connectFuture.channel(); 70 | if (channel != null) { 71 | channel.close(); 72 | } 73 | System.out.println("connect server fail " + host + ":" + port); 74 | e.printStackTrace(); 75 | } 76 | return channel; 77 | } 78 | 79 | public static void main(String[] args) { 80 | try { 81 | Channel connect = new NettyClient().connect(NetConfig.SERVER_IP, NetConfig.SERVER_PORT); 82 | NettyMessage msg = new NettyMessage(); 83 | msg.setHeader(new Header()); 84 | for (int i = 0; i < 100; i++) { 85 | msg.getHeader().setSessionID(1000001+i); 86 | msg.setBody(i + "=" + RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(1000, 20000))); 87 | connect.writeAndFlush(msg); 88 | TimeUnit.SECONDS.sleep(1); 89 | } 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/NettyMessage.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | /** 4 | * Created by lanux on 2017/9/16. 5 | */ 6 | public class NettyMessage { 7 | private Header header; 8 | private Object body; 9 | public Header getHeader() { 10 | return header; 11 | } 12 | public void setHeader(Header header) { 13 | this.header = header; 14 | } 15 | public Object getBody() { 16 | return body; 17 | } 18 | public void setBody(Object body) { 19 | this.body = body; 20 | } 21 | 22 | public String toString(){ 23 | return "NettyMessage [header=" + header + "]"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/NettyMessageDecoder.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * https://github.com/linweiliang451791119/NIO/blob/master/nio/src/chapter13/NettyMarshallingEncoder.java 11 | * Created by lanux on 2017/9/16. 12 | */ 13 | public class NettyMessageDecoder extends ByteToMessageDecoder { 14 | 15 | public NettyMessageDecoder() { 16 | super(); 17 | } 18 | 19 | @Override 20 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List out) 21 | throws Exception { 22 | Object decoded = this.decode(channelHandlerContext, byteBuf); 23 | if (decoded != null) { 24 | out.add(decoded); 25 | } 26 | } 27 | 28 | public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 29 | if (in.readableBytes() < 5) { 30 | return null; 31 | } 32 | int begin = in.readerIndex(); 33 | byte crcCode = in.readByte(); 34 | if (0x02 != crcCode) { 35 | in.readerIndex(begin); 36 | return null; 37 | } 38 | int length = in.readInt(); 39 | if (in.readableBytes() < length) { // STX - ETX 40 | in.readerIndex(begin); 41 | return null; 42 | } 43 | byte[] array = new byte[length]; 44 | in.readBytes(array); 45 | NettyMessage nettyMessage = JsonUtil.fromJson(new String(array), NettyMessage.class); 46 | return nettyMessage; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/NettyMessageEncoder.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToMessageEncoder; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * https://github.com/linweiliang451791119/NIO/blob/master/nio/src/chapter13/NettyMarshallingEncoder.java 13 | * Created by lanux on 2017/9/16. 14 | */ 15 | public class NettyMessageEncoder extends MessageToMessageEncoder { 16 | 17 | 18 | public NettyMessageEncoder() { 19 | } 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext ctx, NettyMessage msg, 23 | List out) throws Exception { 24 | if (msg == null) { 25 | throw new Exception("The encode message is null"); 26 | } 27 | 28 | ByteBuf sendBuf = Unpooled.buffer(); 29 | sendBuf.writeByte(0x02); 30 | sendBuf.writeInt(0); 31 | sendBuf.writeBytes(JsonUtil.toJson(msg).getBytes()); 32 | // 在第4个字节出写入Buffer的长度 33 | int readableBytes = sendBuf.readableBytes(); 34 | sendBuf.setInt(1, readableBytes-5); 35 | 36 | // 把Message添加到List传递到下一个Handler 37 | out.add(sendBuf); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/NettyMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import io.netty.channel.ChannelHandlerAdapter; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ThreadFactory; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | public class NettyMessageHandler extends ChannelInboundHandlerAdapter { 13 | 14 | private static ExecutorService executor = Executors 15 | .newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { 16 | final AtomicInteger threadNumber = new AtomicInteger(1); 17 | 18 | @Override 19 | public Thread newThread(Runnable r) { 20 | return new Thread(r, "netty-worker-thread-" + threadNumber.getAndIncrement()); 21 | } 22 | }); 23 | 24 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 25 | NettyMessage message = (NettyMessage) msg; 26 | if (message.getHeader().getType()==2){ 27 | System.out.println("JsonUtil.toJson(message) = " + JsonUtil.toJson(message)); 28 | return; 29 | } 30 | executor.execute(() -> { 31 | String body = (String) message.getBody(); 32 | System.out.println("Recevied message body from client is " + body); 33 | ctx.writeAndFlush(buildLoginResponse("success")); 34 | }); 35 | } 36 | 37 | private NettyMessage buildLoginResponse(String result) { 38 | NettyMessage message = new NettyMessage(); 39 | Header header = new Header(); 40 | header.setType((byte) 2); 41 | message.setHeader(header); 42 | message.setBody(result); 43 | return message; 44 | } 45 | 46 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 47 | ctx.flush(); 48 | } 49 | 50 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 51 | ctx.close(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.netty; 2 | 3 | import com.lanux.io.NetConfig; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.EventLoopGroup; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.channel.socket.nio.NioServerSocketChannel; 12 | 13 | /** 14 | * Created by lanux on 2017/9/16. 15 | */ 16 | public class NettyServer { 17 | public void bind(String inetHost, int inetPort) throws Exception { 18 | 19 | /** 20 | * Netty 的服务器端的 acceptor 阶段, 没有使用到多线程,主从多线程模型 在 Netty 的服务器端是不存在的. 21 | * 服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 22 | * 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 23 | * 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费. 24 | * the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup 25 | * between different server bootstraps, but I don't see the reason for it. 26 | */ 27 | EventLoopGroup bossGroup = new NioEventLoopGroup(1);// 指定 Acceptor 线程池大小 28 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 29 | try { 30 | ServerBootstrap b = new ServerBootstrap(); 31 | b.group(bossGroup, workerGroup) 32 | .channel(NioServerSocketChannel.class) 33 | .option(ChannelOption.SO_BACKLOG, 1024)// BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。 34 | .childOption(ChannelOption.SO_KEEPALIVE, true)// 是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。 35 | .childHandler(new ChildChannelHandler()); 36 | ChannelFuture f = b.bind(inetHost, inetPort).sync(); 37 | System.out.println("Netty time Server started at port " + inetPort); 38 | f.channel().closeFuture().sync(); 39 | } finally { 40 | bossGroup.shutdownGracefully(); 41 | workerGroup.shutdownGracefully(); 42 | } 43 | } 44 | 45 | public static class ChildChannelHandler extends 46 | ChannelInitializer { 47 | 48 | @Override 49 | protected void initChannel(SocketChannel ch) throws Exception { 50 | ch.pipeline().addLast(new NettyMessageDecoder()) 51 | .addLast(new NettyMessageEncoder()) 52 | .addLast(new NettyMessageHandler()); 53 | } 54 | 55 | } 56 | 57 | public static void main(String[] args) { 58 | try { 59 | new NettyServer().bind(NetConfig.SERVER_IP, NetConfig.SERVER_PORT); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/README.md: -------------------------------------------------------------------------------- 1 | # Netty 2 | 3 | Reactor 的线程模型有三种: 4 | 5 | - 单线程模型 6 | - 多线程模型 7 | - 主从多线程模型 8 | 9 | Reactor 多线程模型特点: 10 | > 1. 有专门一个线程, 即 Acceptor 线程用于监听客户端的TCP连接请求. 11 | > 1. IO线程(不要阻塞):客户端连接的 IO 操作都是由一个特定的 NIO 线程池负责. 每个客户端连接都与一个特定的 NIO 线程绑定, 因此在这个客户端连接中的所有IO操作都是在同一个线程中完成的. 12 | > 1. 客户端连接有很多, 但是 NIO 线程数是比较少的, 因此一个 NIO 线程可以同时绑定到多个客户端连接中. 13 | 14 | Reactor 主从多线程模型特点: 15 | > 1. 服务器端接收客户端的连接请求不再是一个线程, 而是由一个独立的线程池组成 16 | 17 | ## Server端 18 | 启动代码示例 19 | ```java 20 | EventLoopGroup bossGroup = new NioEventLoopGroup(1);// 指定 Acceptor 线程池大小 21 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 22 | try { 23 | ServerBootstrap b = new ServerBootstrap(); 24 | b.group(bossGroup, workerGroup) 25 | .channel(NioServerSocketChannel.class) 26 | .option(ChannelOption.SO_BACKLOG, 1024)// BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。 27 | .childOption(ChannelOption.SO_KEEPALIVE, true)// 是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。 28 | //这里的childHandler是服务于workerGroup的,如果直接使用 29 | //handler方法添加处理器,则是服务于bossGroup的 30 | .childHandler(new ChildChannelHandler()); 31 | ChannelFuture f = b.bind(inetHost, inetPort).sync(); 32 | System.out.println("Netty time Server started at port " + inetPort); 33 | f.channel().closeFuture().sync(); 34 | } finally { 35 | bossGroup.shutdownGracefully(); 36 | workerGroup.shutdownGracefully(); 37 | } 38 | ``` 39 | 40 | ### EventLoopGroup 41 | [参考](https://segmentfault.com/a/1190000007403873) 42 | EventLoopGroup管理的线程数可以通过构造函数设置, 43 | 如果没有设置,默认取java系统变量`-Dio.netty.eventLoopThreads`, 44 | 如果该系统参数也没有指定,则为可用的CPU内核数 × 2。 45 | channel和EventLoop是绑定的,即channel从EventLoopGroup获得一个EventLoop,并注册到该EventLoop,channel生命周期内都和该EventLoop在一起,其相关的I/O、编解码、超时处理都在同一个EventLoop中,这样可以确保这些操作都是线程安全的。 46 | 47 | **bossGroup** 实际就是Acceptor线程池,负责处理客户端的请求接入(握手/认证),如果系统只有一个服务端端口需要监听,则建议bossGroup线程组线程数设置为1。 48 | 49 | **workerGroup** 是真正负责I/O读写操作的线程组,通过ServerBootstrap的group方法进行设置,用于后续的Channel绑定。 50 | 51 | Netty服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费. 52 | 53 | 经 Google, Netty 中的 bossGroup 为什么使用线程池的原因大家众所纷纭, 不过我在 stackoverflow 上找到一个比较靠谱的答案: 54 | 55 | > the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup between different server bootstraps, but I don't see the reason for it. 56 | 57 | 因此上面的 主从多线程模型 分析是有问题, 抱歉. 58 | 59 | > Yeah the boss group is only used for accept etc and for each ServerChannel 60 | only one Thread is used. So if you only call bind(...) one time only one of 61 | the Threads out of it will be used. [see](http://netty.narkive.com/ZOGrVgar/about-netty-4-boss-thread-do) 62 | 63 | ### ChannelPipeline 64 | 一个 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表. 65 | 这个链表的头是 HeadContext, 链表的尾是 TailContext, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler(`head 和 tail 并没有包含 ChannelHandler·`) 66 | [参考](https://segmentfault.com/a/1190000007308934) 67 | 68 | ## client 端 69 | 代码示例 70 | ``` 71 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 72 | try { 73 | Bootstrap b = new Bootstrap(); 74 | b.group(workerGroup); 75 | b.channel(NioSocketChannel.class); 76 | b.option(ChannelOption.SO_KEEPALIVE, true); 77 | b.handler(new ChannelInitializer() { 78 | @Override 79 | public void initChannel(SocketChannel ch) throws Exception { 80 | ch.pipeline().addLast(new HelloClientIntHandler()); 81 | } 82 | }); 83 | 84 | // Start the client. 85 | ChannelFuture f = b.connect(host, port).sync(); 86 | 87 | // Wait until the connection is closed. 88 | f.channel().closeFuture().sync(); 89 | } finally { 90 | workerGroup.shutdownGracefully(); 91 | } 92 | 93 | ``` 94 | 95 | 客户端只需要创建一个EventLoopGroup,因为它不需要独立的线程去监听客户端连接,也没必要通过一个单独的客户端线程去连接服务端。 96 | Netty是异步事件驱动的NIO框架,它的连接和所有IO操作都是异步的,因此不需要创建单独的连接线程。 97 | 98 | 99 | #### 参考资料 100 | 101 | [Netty学习三:线程模型](http://www.cnblogs.com/TomSnail/p/6158249.html) 102 | [深入浅出Netty - EventLoop, EventLoopGroup](https://caorong.github.io/2016/12/24/head-first-netty-1/) 103 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/netty/粘包拆包.md: -------------------------------------------------------------------------------- 1 | LengthFieldBasedFrameDecoder是netty解决拆包粘包问题的一个重要的类,主要结构就是header+body结构。我们只需要传入正确的参数就可以发送和接收正确的数据,那吗重点就在于这几个参数的意义。下面我们就具体了解一下这几个参数的意义。先来看一下LengthFieldBasedFrameDecoder主要的构造方法: 2 | ``` 3 | public LengthFieldBasedFrameDecoder( 4 | int maxFrameLength, 5 | int lengthFieldOffset, int lengthFieldLength, 6 | int lengthAdjustment, int initialBytesToStrip) 7 | ``` 8 | 那么这几个重要的参数如下: 9 | 10 | maxFrameLength:最大帧长度。也就是可以接收的数据的最大长度。如果超过,此次数据会被丢弃。 11 | lengthFieldOffset:长度域偏移。就是说数据开始的几个字节可能不是表示数据长度,需要后移几个字节才是长度域。 12 | lengthFieldLength:长度域字节数。用几个字节来表示数据长度。 13 | lengthAdjustment:数据长度修正。因为长度域指定的长度可以使header+body的整个长度,也可以只是body的长度。如果表示header+body的整个长度,那么我们需要修正数据长度。 14 | initialBytesToStrip:跳过的字节数。如果你需要接收header+body的所有数据,此值就是0,如果你只想接收body数据,那么需要跳过header所占用的字节数。 15 | 下面我们根据几个例子的使用来具体说明这几个参数的使用。 16 | 17 | 需求1 18 | 长度域为2个字节,我们要求发送和接收的数据如下所示: 19 |
 20 | 
 21 |  *      发送的数据 (14 bytes)                接收到数据 (14 bytes)
 22 |  * +--------+----------------+ +--------+----------------+
 23 |  * | Length | Actual Content |----->| Length | Actual Content |
 24 |  * |  12    | "HELLO, WORLD" |      |   12   | "HELLO, WORLD" |
 25 |  * +--------+----------------+ +--------+----------------+
 26 | 
27 | 留心的你肯定发现了,长度域只是实际内容的长度,不包括长度域的长度。下面是参数的值: 28 | 29 | lengthFieldOffset=0:开始的2个字节就是长度域,所以不需要长度域偏移。 30 | lengthFieldLength=2:长度域2个字节。 31 | lengthAdjustment=0:数据长度修正为0,因为长度域只包含数据的长度,所以不需要修正。 32 | initialBytesToStrip=0:发送和接收的数据完全一致,所以不需要跳过任何字节。 33 | 需求2 34 | 长度域为2个字节,我们要求发送和接收的数据如下所示: 35 | 36 | * 发送的数据 (14 bytes) 接收到数据 (12 bytes) 37 | * +--------+----------------+ +----------------+ 38 | * | Length | Actual Content |----->| Actual Content | 39 | * | 12 | "HELLO, WORLD" | | "HELLO, WORLD" | 40 | * +--------+----------------+ +----------------+ 41 | 参数值如下: 42 | 43 | lengthFieldOffset=0:开始的2个字节就是长度域,所以不需要长度域偏移。 44 | lengthFieldLength=2:长度域2个字节。 45 | lengthAdjustment=0:数据长度修正为0,因为长度域只包含数据的长度,所以不需要修正。 46 | initialBytesToStrip=2:我们发现接收的数据没有长度域的数据,所以要跳过长度域的2个字节。 47 | 需求3 48 | 长度域为2个字节,我们要求发送和接收的数据如下所示: 49 | 50 | * BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) 51 | * +--------+----------------+ +--------+----------------+ 52 | * | Length | Actual Content |----->| Length | Actual Content | 53 | * | 14 | "HELLO, WORLD" | | 14 | "HELLO, WORLD" | 54 | * +--------+----------------+ +--------+----------------+ 55 | 留心的你肯定又发现了,长度域表示的长度是总长度 也就是header+body的总长度。参数如下: 56 | 57 | lengthFieldOffset=0:开始的2个字节就是长度域,所以不需要长度域偏移。 58 | lengthFieldLength=2:长度域2个字节。 59 | lengthAdjustment=-2:因为长度域为总长度,所以我们需要修正数据长度,也就是减去2。 60 | initialBytesToStrip=0:我们发现接收的数据没有长度域的数据,所以要跳过长度域的2个字节。 61 | 需求4 62 | 长度域为2个字节,我们要求发送和接收的数据如下所示: 63 | 64 | * BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) 65 | * +----------+----------+----------------+ +----------+----------+----------------+ 66 | * | meta | Length | Actual Content |----->| meta | Length | Actual Content | 67 | * | 0xCAFE | 12 | "HELLO, WORLD" | | 0xCAFE | 12 | "HELLO, WORLD" | 68 | * +----------+----------+----------------+ +----------+----------+----------------+ 69 | 我们发现,数据的结构有点变化,变成了 meta+header+body的结构。meta一般表示元数据,魔数等。我们定义这里meta有三个字节。参数如下: 70 | 71 | lengthFieldOffset=3:开始的3个字节是meta,然后才是长度域,所以长度域偏移为3。 72 | lengthFieldLength=2:长度域2个字节。 73 | lengthAdjustment=0:长度域指定的长度位数据长度,所以数据长度不需要修正。 74 | initialBytesToStrip=0:发送和接收数据相同,不需要跳过数据。 75 | 需求5 76 | 长度域为2个字节,我们要求发送和接收的数据如下所示: 77 | 78 | * BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) 79 | * +----------+----------+----------------+ +----------+----------+----------------+ 80 | * | Length | meta | Actual Content |----->| Length | meta | Actual Content | 81 | * | 12 | 0xCAFE | "HELLO, WORLD" | | 12 | 0xCAFE | "HELLO, WORLD" | 82 | * +----------+----------+----------------+ +----------+----------+----------------+ 83 | 我们发现,数据的结构有点变化,变成了 header+meta+body的结构。meta一般表示元数据,魔数等。我们定义这里meta有三个字节。参数如下: 84 | 85 | lengthFieldOffset=0:开始的2个字节就是长度域,所以不需要长度域偏移。 86 | lengthFieldLength=2:长度域2个字节。 87 | lengthAdjustment=3:我们需要把meta+body当做body处理,所以数据长度需要加3。 88 | initialBytesToStrip=0:发送和接收数据相同,不需要跳过数据。 89 | 需求6 90 | 长度域为2个字节,我们要求发送和接收的数据如下所示: 91 | 92 | * BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) 93 | * +------+--------+------+----------------+ +------+----------------+ 94 | * | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | 95 | * | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | 96 | * +------+--------+------+----------------+ +------+----------------+ 97 | 我们发现,数据的结构有点变化,变成了 hdr1+header+hdr2+body的结构。我们定义这里hdr1和hdr2都只有1个字节。参数如下: 98 | 99 | lengthFieldOffset=1:开始的1个字节是长度域,所以需要设置长度域偏移为1。 100 | lengthFieldLength=2:长度域2个字节。 101 | lengthAdjustment=1:我们需要把hdr2+body当做body处理,所以数据长度需要加1。 102 | initialBytesToStrip=3:接收数据不包括hdr1和长度域相同,所以需要跳过3个字节。 103 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/nio/NioBasic.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.nio; 2 | 3 | import com.lanux.tool.ByteUtil; 4 | import com.lanux.tool.StringTool; 5 | 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.SelectionKey; 9 | import java.nio.channels.SocketChannel; 10 | import java.util.Date; 11 | 12 | /** 13 | * Created by lanux on 2017/9/18. 14 | */ 15 | public class NioBasic { 16 | 17 | /** 18 | * 非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。 19 | * 20 | * @param sc 21 | * @throws IOException 22 | */ 23 | public String handleRead(SocketChannel sc) throws IOException { 24 | ByteBuffer header = ByteBuffer.allocate(4); 25 | while (header.hasRemaining()) { 26 | int read = sc.read(header); // read()返回 -1, 那么表示连接中断了. 27 | } 28 | header.flip();//写=>读 29 | int length = ByteUtil.byteArrayToInt(header.array()); 30 | header.clear(); 31 | ByteBuffer input = ByteBuffer.allocate(length); 32 | while (input.hasRemaining()) { 33 | sc.read(input);// read()返回 -1, 那么表示连接中断了. 34 | } 35 | input.flip();//写=>读 36 | String value = new String(input.array()); 37 | System.out.println( 38 | Thread.currentThread().getName() + " received " + input.limit() + " : " + StringTool.maxString( 39 | value, 50)); 40 | input.clear(); 41 | return value; 42 | } 43 | 44 | /** 45 | * 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。 46 | * 47 | * @param key 48 | * @throws IOException 49 | */ 50 | public void handleWrite(SelectionKey key) throws IOException { 51 | writeMsg((SocketChannel) key.channel(), new Date().toString()); 52 | } 53 | 54 | public void writeMsg(SocketChannel sc, String value) throws IOException { 55 | byte[] bytes = value.getBytes(); 56 | // System.out.println( 57 | // Thread.currentThread().getName() + " write " + bytes.length + " : " + StringTool.maxString(value, 50)); 58 | ByteBuffer buf = ByteBuffer.allocate(bytes.length + 4); 59 | buf.put(ByteUtil.intToByteArray(bytes.length)); 60 | buf.put(bytes); 61 | buf.flip();//写=>读 62 | while (buf.hasRemaining()) { 63 | sc.write(buf); 64 | } 65 | buf.clear();//清空所有 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/nio/NioClient.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.nio; 2 | 3 | import com.lanux.io.NetConfig; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | import java.net.InetSocketAddress; 8 | import java.nio.channels.SelectionKey; 9 | import java.nio.channels.Selector; 10 | import java.nio.channels.SocketChannel; 11 | import java.util.Iterator; 12 | 13 | /** 14 | * Created by lanux on 2017/8/6. 15 | */ 16 | public class NioClient extends NioBasic implements Closeable { 17 | 18 | private Selector selector; 19 | public SocketChannel channel; 20 | 21 | public volatile boolean connected; 22 | 23 | public NioClient() { 24 | try { 25 | channel = SocketChannel.open(); 26 | channel.configureBlocking(false);//设置 SocketChannel 为异步模式, 这样connect, read, write 都是异步的 27 | channel.connect(new InetSocketAddress(NetConfig.SERVER_IP, NetConfig.SERVER_PORT)); 28 | /** 29 | * 非阻塞模式 connect立即返回,可能还未连接成功,所以一般用while来等待 30 | * while(! channel.finishConnect() ){ 31 | * //wait, or do something else... 32 | * } 33 | */ 34 | selector = Selector.open(); 35 | channel.register(selector, SelectionKey.OP_CONNECT); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | public void listen() { 42 | while (true) { 43 | try { 44 | if (selector.select(NetConfig.SO_TIMEOUT) == 0) { 45 | continue; 46 | } 47 | Iterator ite = selector.selectedKeys().iterator(); 48 | while (ite.hasNext()) { 49 | SelectionKey key = ite.next(); 50 | //删除已选的key,防止重复处理 51 | ite.remove(); 52 | if (key.isValid() && key.isConnectable()) { 53 | SocketChannel channel = (SocketChannel) key.channel(); 54 | if (channel.isConnectionPending()) { 55 | channel.finishConnect(); 56 | } 57 | channel.register(selector, SelectionKey.OP_READ); 58 | connected = true; 59 | System.out.println("nio client connected"); 60 | } else if (key.isReadable()) { 61 | SocketChannel channel = (SocketChannel) key.channel(); 62 | handleRead(channel); 63 | } else if (key.isWritable()) { 64 | handleWrite(key); 65 | // key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); //取消注册写监听 66 | } 67 | } 68 | 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | } 74 | 75 | public void write(String value) throws IOException { 76 | writeMsg(channel, value); 77 | } 78 | 79 | @Override 80 | public void close() throws IOException { 81 | try { 82 | connected = false; 83 | if (channel != null) { 84 | channel.close(); 85 | } 86 | } catch (IOException e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/nio/NioServer.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.nio; 2 | 3 | import com.lanux.io.NetConfig; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.nio.channels.SelectionKey; 8 | import java.nio.channels.Selector; 9 | import java.nio.channels.ServerSocketChannel; 10 | import java.nio.channels.SocketChannel; 11 | import java.util.Iterator; 12 | 13 | /** 14 | * Created by lanux on 2017/9/16. 15 | */ 16 | public class NioServer extends NioBasic { 17 | Selector selector = null; 18 | ServerSocketChannel ssc = null; 19 | 20 | public NioServer() { 21 | try { 22 | selector = Selector.open(); 23 | ssc = ServerSocketChannel.open(); 24 | ssc.socket().bind(new InetSocketAddress(NetConfig.SERVER_IP, NetConfig.SERVER_PORT)); 25 | ssc.configureBlocking(false);// Channel 要注册到 Selector 中, 那么这个 Channel 必须是非阻塞的 26 | /** 27 | * 28 | * Connect, 即连接事件(TCP 连接), 对应于SelectionKey.OP_CONNECT 29 | * Accept, 即确认事件, 对应于SelectionKey.OP_ACCEPT 30 | * Read, 即读事件, 对应于SelectionKey.OP_READ, 表示 buffer 可读. 31 | * Write, 即写事件, 对应于SelectionKey.OP_WRITE, 表示 buffer 可写. 32 | * 33 | * 我们可以使用或运算|来组合多个事件, 例如:int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 34 | */ 35 | SelectionKey register = ssc.register(selector, SelectionKey.OP_ACCEPT); 36 | 37 | while (true) { 38 | if (selector.select(NetConfig.SO_TIMEOUT) == 0) { 39 | continue; 40 | } 41 | Iterator it = selector.selectedKeys().iterator(); 42 | while (it.hasNext()) { 43 | SelectionKey key = it.next(); 44 | it.remove(); 45 | if (!key.isValid()) { 46 | // 选择键无效 47 | continue; 48 | } 49 | handleKey(key); 50 | // 这里不能用异步 51 | // executor.submit(()-> handleKey(key)); 52 | } 53 | } 54 | 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } finally { 58 | try { 59 | if (selector != null) { 60 | selector.close(); 61 | } 62 | if (ssc != null) { 63 | ssc.close(); 64 | } 65 | } catch (IOException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | } 70 | 71 | private void handleKey(SelectionKey key) { 72 | try { 73 | if (key.isAcceptable()) { 74 | ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); 75 | serverSocketChannel 76 | .accept() 77 | .configureBlocking(false) 78 | .register(selector, SelectionKey.OP_READ); 79 | // .register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE); 80 | //一般来说,你不应该注册写事件。 81 | // 写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个CPU资源。 82 | // 所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。 83 | // key.interestOps(SelectionKey.OP_WRITE); //注册写监听 84 | // key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); //取消注册写监听 85 | } 86 | if (key.isReadable()) { 87 | SocketChannel sc = (SocketChannel) key.channel(); 88 | String value = handleRead(sc); 89 | writeMsg(sc, value); 90 | } 91 | if (key.isWritable()) { 92 | handleWrite(key); 93 | key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);//取消注册写监听 94 | } 95 | if (key.isConnectable()) { 96 | System.out.println("is connect able"); 97 | } 98 | } catch (IOException e) { 99 | e.printStackTrace(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/nio/NioServer2.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.nio; 2 | 3 | import com.lanux.io.NetConfig; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.io.Closeable; 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.nio.channels.SelectionKey; 10 | import java.nio.channels.Selector; 11 | import java.nio.channels.ServerSocketChannel; 12 | import java.nio.channels.SocketChannel; 13 | import java.util.Date; 14 | import java.util.Iterator; 15 | import java.util.concurrent.ExecutorService; 16 | import java.util.concurrent.Executors; 17 | import java.util.concurrent.ThreadFactory; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | /** 21 | * Created by lanux on 2017/9/16. 22 | */ 23 | public class NioServer2 extends NioBasic implements Closeable { 24 | private Selector acceptSelector; 25 | private ServerSocketChannel ssc; 26 | private Selector ioSelector; 27 | private volatile boolean ioThreadStarted; 28 | 29 | private static ExecutorService executor = Executors 30 | .newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { 31 | final AtomicInteger threadNumber = new AtomicInteger(1); 32 | 33 | @Override 34 | public Thread newThread(Runnable r) { 35 | return new Thread(r, "nio-worker-thread-" + threadNumber.getAndIncrement()); 36 | } 37 | }); 38 | 39 | public NioServer2() { 40 | try { 41 | acceptSelector = Selector.open(); 42 | ioSelector = Selector.open(); 43 | ssc = ServerSocketChannel.open(); 44 | ssc.socket().bind(new InetSocketAddress(NetConfig.SERVER_IP, NetConfig.SERVER_PORT)); 45 | ssc.configureBlocking(false); 46 | ssc.register(acceptSelector, SelectionKey.OP_ACCEPT); 47 | new Thread(() -> listen(acceptSelector), "nio-accept-thread").start(); 48 | System.out.println("nio server started"); 49 | } catch (IOException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | private void listen(Selector selector) { 55 | while (true) { 56 | try { 57 | // selector.select() 阻塞到至少有一个通道在你注册的事件上就绪 58 | // selector.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut 59 | // selector.selectNow() 立即返回。如果没有就绪的通道则返回0,与wakeup没有太大关系。 60 | // select方法的返回值表示就绪通道的个数。 61 | if (selector.select() == 0) { 62 | continue; 63 | } 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | Iterator it = selector.selectedKeys().iterator(); 68 | while (it.hasNext()) { 69 | SelectionKey key = it.next(); 70 | it.remove(); 71 | if (!key.isValid()) { 72 | // 选择键无效 73 | continue; 74 | } 75 | // 这里不能用异步 76 | handleKey(key); 77 | } 78 | } 79 | } 80 | 81 | private void handleKey(SelectionKey key) { 82 | try { 83 | if (key.isAcceptable()) { 84 | ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); 85 | serverSocketChannel 86 | .accept() 87 | .configureBlocking(false) 88 | .register(ioSelector, SelectionKey.OP_READ); 89 | 90 | // .register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE); 91 | // 一般来说,你不应该注册写事件。 92 | // 写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个CPU资源。 93 | // 所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。 94 | // key.interestOps(SelectionKey.OP_WRITE); //注册写监听 95 | // key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); //取消注册写监听 96 | 97 | if (!ioThreadStarted) { 98 | new Thread(() -> listen(ioSelector), "nio-io-thread").start(); 99 | ioThreadStarted = true; 100 | } 101 | // 在其他线程中在那个selector上调用wakeUp方法,使阻塞在select上的线程立即返回。 102 | // 如果调用wakeUp时并没有select线程阻塞,则下次调用select时会立即返回。 103 | // ioSelector.wakeup(); 104 | } 105 | if (key.isReadable()) { 106 | SocketChannel sc = (SocketChannel) key.channel(); 107 | String value = handleRead(sc); 108 | if (StringUtils.isBlank(value)) { 109 | return; 110 | } 111 | executor.submit(() -> { 112 | //里面可以写一些负责的处理逻辑 113 | try { 114 | writeMsg(sc, value); 115 | } catch (IOException e) { 116 | e.printStackTrace(); 117 | } 118 | }); 119 | } 120 | if (key.isWritable()) { 121 | writeMsg((SocketChannel) key.channel(), new Date().toString()); 122 | //key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);//取消注册写监听 123 | } 124 | if (key.isConnectable()) { 125 | System.out.println("is connect able"); 126 | } 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | @Override 133 | public void close() throws IOException { 134 | try { 135 | if (acceptSelector != null) { 136 | acceptSelector.close(); 137 | } 138 | if (ioSelector != null) { 139 | ioSelector.close(); 140 | } 141 | if (ssc != null) { 142 | ssc.close(); 143 | } 144 | } catch (IOException e) { 145 | e.printStackTrace(); 146 | } 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/nio/README.md: -------------------------------------------------------------------------------- 1 | # java NIO 核心概念 2 | 3 | - [Selector](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#selector选择器) 4 | - [Channel](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#channel) 5 | - [Buffer](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#buffer) 6 | 7 | ## Selector(选择器) 8 | - [获取就绪事件](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#一获取就绪事件) 9 | - [SelectionKey](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#二selectionkey) 10 | - [Selector 的基本使用流程](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#三selector-的基本使用流程) 11 | - [close and wakeup](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#四close-or-wakeup-selector) 12 | - [demo](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#五完整的-selector-例子) 13 | 14 | Selector(选择器)是Java NIO中能够检测一到多个NIO channel,并能够知晓通道是否为诸如读/写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。 15 | 16 | **例1** 17 | ``` 18 | // 创建一个选择器: 19 | Selector selector = Selector.open(); 20 | 21 | channel.configureBlocking(false); 22 | // 将 Channel 注册到选择器中为了使用选择器管理 Channel: 23 | SelectionKey key = channel.register(selector, SelectionKey.OP_READ); 24 | 25 | ``` 26 | 27 | `isOpen()` —— 判断Selector是否处于打开状态。Selector对象创建后就处于打开状态了
28 | `close()` —— 当调用了Selector对象的close()方法,就进入关闭状态。 29 | 用完Selector后调用其close()方法会关闭该Selector, 30 | 且使注册到该Selector上的所有SelectionKey实例无效。**通道本身并不会关闭** 31 | 32 | 33 | > 注意, 如果一个 Channel 要注册到 Selector 中, 那么这个 Channel 必须是非阻塞的, 即`channel.configureBlocking(false)`;
34 | > 因为 Channel 必须要是非阻塞的, 因此 FileChannel 是不能够使用选择器的, 因为 FileChannel 都是阻塞的. 35 | 36 | `channel.register()`方法的第二个参数,这是一个"interest set",意思是在通过Selector监听Channel时对什么事件感兴趣。channel触发了一个事件则说明该事件已经就绪。 37 | 38 | - **Connect**, 即连接事件(TCP 连接), 对应于`SelectionKey.OP_CONNECT`
39 | - **Accept**, 即确认事件, 对应于`SelectionKey.OP_ACCEPT`
40 | - **Read**, 即读事件, 对应于`SelectionKey.OP_READ`, 表示 buffer 可读.
41 | - **Write**, 即写事件, 对应于`SelectionKey.OP_WRITE`, 表示 buffer 可写.
42 | 43 | > 一般来说,你不应该注册写事件。写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个CPU资源。 44 | 所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。 45 | 46 | 我们可以使用或运算 ` | ` 来组合多个事件, 例如: 47 | ``` 48 | int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 49 | ``` 50 | 注意, 一个 Channel 仅仅可以被注册到一个 Selector 一次, 如果将 Channel 注册到 Selector 多次, 那么其实就是相当于更新 SelectionKey 的 interest set. 例如: 51 | ``` 52 | channel.register(selector, SelectionKey.OP_READ); 53 | channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); 54 | ``` 55 | 56 | 注意, 我们可以动态更改 SelectedKey 中的 interest set. 例如在 OP_ACCEPT 中, 我们可以将 interest set 更新为 OP_READ, 这样 Selector 就会将这个 Channel 的 读 IO 就绪事件包含进来了. 57 | ``` 58 | key.interestOps(SelectionKey.OP_WRITE); //注册写监听 59 | key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); //取消注册写监听 60 | ``` 61 | 62 | #### 一、获取就绪事件 63 | channel注册到selector后,我们可以通过`Selector.select()`方法阻塞查询有多少个事件(注册过的事件)准备就绪. 64 | 如果`select()`方法返回值大于1, 那么我们可以通过`selector.selectedKeys()`读取就绪事件集合,然后进行处理: 65 | 66 | **例2** 67 | ``` 68 | while(true){ 69 | if (selector.select(TIMEOUT) == 0) { 70 | System.out.print("."); 71 | continue; 72 | } 73 | Set selectedKeys = selector.selectedKeys(); 74 | 75 | Iterator keyIterator = selectedKeys.iterator(); 76 | 77 | while(keyIterator.hasNext()) { 78 | 79 | SelectionKey key = keyIterator.next(); 80 | 81 | if(key.isAcceptable()) { 82 | // a connection was accepted by a ServerSocketChannel. 83 | 84 | } else if (key.isConnectable()) { 85 | // a connection was established with a remote server. 86 | 87 | } else if (key.isReadable()) { 88 | // a channel is ready for reading 89 | 90 | } else if (key.isWritable()) { 91 | // a channel is ready for writing 92 | } 93 | 94 | keyIterator.remove(); 95 | } 96 | } 97 | ``` 98 | 99 | > ***注意***, 在每次迭代时, 我们都调用 "keyIterator.remove()" 将这个 key 从迭代器中删除, 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中, 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中. 100 | 101 | 一个Selector对象会包含3种类型的SelectionKey集合:
102 | **all-keys集合** —— 当前所有向Selector注册的SelectionKey的集合,Selector的keys()方法返回该集合
103 | **\* selected-keys集合(就绪事件集合)** —— 相关事件已经被Selector捕获的SelectionKey的集合,Selector的selectedKeys()方法返回该集合
104 | **cancelled-keys集合** —— 已经被取消的SelectionKey的集合,Selector**没有**提供访问这种集合的方法
105 | 106 | 当register()方法执行时,新建一个SelectioKey,并把它加入Selector的all-keys集合中。
107 | 如果关闭了与SelectionKey对象关联的Channel对象,或者调用了SelectionKey对象的cancel方法,这个SelectionKey对象就会被加入到cancelled-keys集合中,表示这个SelectionKey对象已经被取消。
108 | 在执行Selector的select()方法时,如果与SelectionKey相关的事件发生了,这个SelectionKey就被加入到selected-keys集合中,程序直接调用selected-keys集合的remove()方法,或者调用它的iterator的remove()方法,都可以从selected-keys集合中删除一个SelectionKey对象。 109 | 110 | #### 二、SelectionKey 111 | 112 | 如**例1**所示, 当我们使用 register 注册一个 Channel 时, 会返回一个 SelectionKey 对象,; 113 | 如**例2**所示`selector.selectedKeys()`返回SelectionKey集合,这个SelectionKey对象包含了如下内容: 114 | 115 | - **interest set**, 即我们感兴趣的事件集, 即在调用 register 注册 channel 时所设置的 interest set. 116 | - **ready set** 117 | - **channel** 118 | - **selector** 119 | - **attached object**, 可选的附加对象 120 | 121 | ##### interest set 122 | ``` 123 | int interestSet = selectionKey.interestOps(); 124 | boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT; 125 | boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; 126 | boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; 127 | boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; 128 | ``` 129 | ##### ready set 130 | 131 | 代表了 Channel 所准备好了的操作. 132 | 我们可以像判断 interest set 一样操作 Ready set, 但是我们还可以使用如下方法进行判断: 133 | ``` 134 | int readySet = selectionKey.readyOps(); 135 | selectionKey.isAcceptable(); 136 | selectionKey.isConnectable(); 137 | selectionKey.isReadable(); 138 | selectionKey.isWritable(); 139 | ``` 140 | 141 | ##### Channel 和 Selector 142 | 我们可以通过 SelectionKey 获取相对应的 Channel 和 Selector: 143 | 144 | Channel channel = selectionKey.channel(); 145 | Selector selector = selectionKey.selector(); 146 | 147 | ##### Attaching Object 148 | 我们可以在selectionKey中附加一个对象: 149 | ``` 150 | selectionKey.attach(theObject); 151 | Object attachedObj = selectionKey.attachment(); 152 | ``` 153 | 或者在注册时直接附加: 154 | ``` 155 | SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject); 156 | ``` 157 | 158 | 159 | #### 三、Selector 的基本使用流程 160 | 1. 通过 Selector.open() 打开一个 Selector.
161 | 2. 将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set)
162 | 3. 不断重复:
163 | - 调用 select() 方法
164 | - 调用 selector.selectedKeys() 获取 selected keys
165 | - 迭代每个 selected key:
166 | - *从 selected key 中获取 对应的 Channel 和附加信息(如果有的话)
167 | - *判断是哪些 IO 事件已经就绪了, 然后处理它们. 如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.
168 | - *根据需要更改 selected key 的监听事件.
169 | - *将已经处理过的 key 从 selected keys 集合中删除.
170 | 171 | #### 四、close or wakeUp Selector 172 | 当调用了 `Selector.close()` 方法时, 我们其实是关闭了 Selector 本身并且将所有的 SelectionKey 失效, 但是并不会关闭 Channel. 173 | 174 | 某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其他线程在第一个线程调用select()方法的那个对象上调用`Selector.wakeup()`方法即可。阻塞在select()方法上的线程会立马返回。 175 | #### 五、完整的 Selector 例子 176 | ```java 177 | public class NioEchoServer { 178 | private static final int BUF_SIZE = 256; 179 | private static final int TIMEOUT = 3000; 180 | 181 | public static void main(String args[]) throws Exception { 182 | // 打开服务端 Socket 183 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 184 | 185 | // 打开 Selector 186 | Selector selector = Selector.open(); 187 | 188 | // 服务端 Socket 监听8080端口, 并配置为非阻塞模式 189 | serverSocketChannel.socket().bind(new InetSocketAddress(8080)); 190 | serverSocketChannel.configureBlocking(false); 191 | 192 | // 将 channel 注册到 selector 中. 193 | // 通常我们都是先注册一个 OP_ACCEPT 事件, 然后在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 194 | // 注册到 Selector 中. 195 | serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 196 | 197 | while (true) { 198 | // 通过调用 select 方法, 阻塞地等待 channel I/O 可操作 199 | if (selector.select(TIMEOUT) == 0) { 200 | System.out.print("."); 201 | continue; 202 | } 203 | 204 | // 获取 I/O 操作就绪的 SelectionKey, 通过 SelectionKey 可以知道哪些 Channel 的哪类 I/O 操作已经就绪. 205 | Iterator keyIterator = selector.selectedKeys().iterator(); 206 | 207 | while (keyIterator.hasNext()) { 208 | 209 | SelectionKey key = keyIterator.next(); 210 | 211 | // 当获取一个 SelectionKey 后, 就要将它删除, 表示我们已经对这个 IO 事件进行了处理. 212 | keyIterator.remove(); 213 | 214 | if (key.isAcceptable()) { 215 | // 当 OP_ACCEPT 事件到来时, 我们就有从 ServerSocketChannel 中获取一个 SocketChannel, 216 | // 代表客户端的连接 217 | // 注意, 在 OP_ACCEPT 事件中, 从 key.channel() 返回的 Channel 是 ServerSocketChannel. 218 | // 而在 OP_WRITE 和 OP_READ 中, 从 key.channel() 返回的是 SocketChannel. 219 | SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); 220 | clientChannel.configureBlocking(false); 221 | //在 OP_ACCEPT 到来时, 再将这个 Channel 的 OP_READ 注册到 Selector 中. 222 | // 注意, 这里我们如果没有设置 OP_READ 的话, 即 interest set 仍然是 OP_CONNECT 的话, 那么 select 方法会一直直接返回. 223 | clientChannel.register(key.selector(), OP_READ, ByteBuffer.allocate(BUF_SIZE)); 224 | } 225 | 226 | if (key.isReadable()) { 227 | SocketChannel clientChannel = (SocketChannel) key.channel(); 228 | ByteBuffer buf = (ByteBuffer) key.attachment(); 229 | long bytesRead = clientChannel.read(buf); 230 | if (bytesRead == -1) { 231 | clientChannel.close(); 232 | } else if (bytesRead > 0) { 233 | key.interestOps(OP_READ | SelectionKey.OP_WRITE); 234 | System.out.println("Get data length: " + bytesRead); 235 | } 236 | } 237 | 238 | if (key.isValid() && key.isWritable()) { 239 | ByteBuffer buf = (ByteBuffer) key.attachment(); 240 | buf.flip(); 241 | SocketChannel clientChannel = (SocketChannel) key.channel(); 242 | 243 | clientChannel.write(buf); 244 | 245 | if (!buf.hasRemaining()) { 246 | key.interestOps(OP_READ);//动态注册读事件 247 | } 248 | buf.compact(); 249 | } 250 | } 251 | } 252 | } 253 | } 254 | ``` 255 | 256 | ## Channel 257 | 通常来说, 所有的 NIO 的 I/O 操作都是从 Channel 开始的. 一个 channel 类似于一个 stream. 258 | java Stream 和 NIO Channel 对比 259 | 260 | 1. channel 既可以从通道中读取数据,又可以写数据到通道。但Stream的读写通常是单向的。 261 | 1. Channel 可以非阻塞读写, 而 Stream 是阻塞的同步读写. 262 | 1. Channel 总是从 Buffer 中读取数据, 或将数据写入到 Buffer 中. 263 | 264 | Channel 类型有: 265 | - FileChannel, 文件操作 266 | - [SocketChannel](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#socketchannel), TCP 操作 267 | - [ServerSocketChannel](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#serversocketchannel), TCP 操作, 使用在服务器端. 268 | - [DatagramChannel](https://github.com/lanux/java-demo/tree/master/src/main/java/com/lanux/io/nio#datagramchannel), UDP 操作 269 | 270 | ``` 271 | public static void main( String[] args ) throws Exception 272 | { 273 | RandomAccessFile aFile = new RandomAccessFile("/Users/xiongyongshun/settings.xml", "rw"); 274 | FileChannel inChannel = aFile.getChannel(); 275 | 276 | ByteBuffer buf = ByteBuffer.allocate(48); 277 | 278 | int bytesRead = inChannel.read(buf); 279 | while (bytesRead != -1) { 280 | buf.flip(); 281 | 282 | while(buf.hasRemaining()){ 283 | System.out.print((char) buf.get()); 284 | } 285 | 286 | buf.clear(); 287 | bytesRead = inChannel.read(buf); 288 | } 289 | aFile.close(); 290 | } 291 | ``` 292 | 293 | > 注意, FileChannel 不能设置为非阻塞模式. 294 | 295 | 296 | #### SocketChannel 297 | 298 | SocketChannel 是一个客户端用来进行 TCP 连接的 Channel. 299 | 创建一个 SocketChannel 的方法有两种: 300 | - 打开一个 SocketChannel, 然后将其连接到某个服务器中 301 | - 当一个 ServerSocketChannel 接受到连接请求时, 会返回一个 SocketChannel 对象. 302 | ##### 打开 SocketChannel 303 | ``` 304 | SocketChannel socketChannel = SocketChannel.open(); 305 | socketChannel.connect(new InetSocketAddress("http://example.com", 80)); 306 | ``` 307 | ##### 关闭 308 | ``` 309 | socketChannel.close(); 310 | ``` 311 | ##### 读取数据 312 | ``` 313 | ByteBuffer buf = ByteBuffer.allocate(48); 314 | int bytesRead = socketChannel.read(buf); 315 | ``` 316 | 317 | > read()返回 -1, 那么表示连接中断了. 318 | 319 | ##### 写入数据 320 | ``` 321 | String newData = "New String to write to file..." + System.currentTimeMillis(); 322 | 323 | ByteBuffer buf = ByteBuffer.allocate(48); 324 | buf.clear(); 325 | buf.put(newData.getBytes()); 326 | 327 | buf.flip(); 328 | 329 | while(buf.hasRemaining()) { 330 | channel.write(buf); 331 | } 332 | ``` 333 | 334 | ##### 非阻塞模式 335 | 336 | 我们可以设置 SocketChannel 为异步模式, 这样我们的 connect, read, write 都是异步的了. 337 | 338 | ``` 339 | socketChannel.configureBlocking(false); 340 | socketChannel.connect(new InetSocketAddress("http://example.com", 80)); 341 | 342 | while(! socketChannel.finishConnect() ){ 343 | //wait, or do something else... 344 | } 345 | ``` 346 | 在非阻塞模式中, 或许连接还没有建立, connect 方法就返回了, 因此我们需要检查当前是否是连接到了主机, 因此通过一个 while 循环来判断. 347 | 348 | 349 | #### ServerSocketChannel 350 | 351 | ServerSocketChannel 顾名思义, 是用在服务器为端的, 可以监听客户端的 TCP 连接, 例如: 352 | ``` 353 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 354 | serverSocketChannel.socket().bind(new InetSocketAddress(9999)); 355 | while(true){ 356 | SocketChannel socketChannel = 357 | serverSocketChannel.accept(); 358 | 359 | //do something with socketChannel... 360 | } 361 | ``` 362 | 363 | ##### 打开 关闭 364 | ``` 365 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 366 | serverSocketChannel.close(); 367 | ``` 368 | ##### 监听连接 369 | 370 | 我们可以使用ServerSocketChannel.accept()方法来监听客户端的 TCP 连接请求, accept()方法会阻塞, 直到有连接到来, 当有连接时, 这个方法会返回一个 SocketChannel 对象: 371 | ``` 372 | while(true){ 373 | SocketChannel socketChannel = 374 | serverSocketChannel.accept(); 375 | 376 | //do something with socketChannel... 377 | } 378 | ``` 379 | ##### 非阻塞模式 380 | 381 | 在非阻塞模式下, accept()是非阻塞的, 因此如果此时没有连接到来, 那么 accept()方法会返回null: 382 | ``` 383 | ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 384 | 385 | serverSocketChannel.socket().bind(new InetSocketAddress(9999)); 386 | serverSocketChannel.configureBlocking(false); 387 | 388 | while(true){ 389 | SocketChannel socketChannel = 390 | serverSocketChannel.accept(); 391 | 392 | if(socketChannel != null){ 393 | //do something with socketChannel... 394 | } 395 | } 396 | ``` 397 | 398 | #### DatagramChannel 399 | DatagramChannel 是用来处理 UDP 连接的. 400 | 401 | ##### 打开 402 | ``` 403 | DatagramChannel channel = DatagramChannel.open(); 404 | channel.socket().bind(new InetSocketAddress(9999)); 405 | ``` 406 | ##### 读取数据 407 | ``` 408 | ByteBuffer buf = ByteBuffer.allocate(48); 409 | buf.clear(); 410 | 411 | channel.receive(buf); 412 | ``` 413 | ##### 发送数据 414 | ``` 415 | String newData = "New String to write to file..." 416 | + System.currentTimeMillis(); 417 | 418 | ByteBuffer buf = ByteBuffer.allocate(48); 419 | buf.clear(); 420 | buf.put(newData.getBytes()); 421 | buf.flip(); 422 | 423 | int bytesSent = channel.send(buf, new InetSocketAddress("example.com", 80)); 424 | ``` 425 | ##### 连接到指定地址 426 | 427 | 因为 UDP 是非连接的, 因此这个的 connect 并不是像 TCP 一样真正意义上的连接, 而是它会将 DatagramChannel 锁住, 因此我们仅仅可以从指定的地址中读取或写入数据. 428 | ``` 429 | channel.connect(new InetSocketAddress("example.com", 80)); 430 | ``` 431 | 432 | ## Buffer 433 | 当我们需要与 NIO Channel 进行交互时, 我们就需要使用到 NIO Buffer, 即数据从 Buffer读取到 Channel 中, 并且从 Channel 中写入到 Buffer 中. 434 | 实际上, 一个 Buffer 其实就是一块内存区域, 我们可以在这个内存区域中进行数据的读写. NIO Buffer 其实是这样的内存块的一个封装, 并提供了一些操作方法让我们能够方便地进行数据的读写. 435 | Buffer 类型有: 436 | - ByteBuffer 437 | - CharBuffer 438 | - DoubleBuffer 439 | - FloatBuffer 440 | - IntBuffer 441 | - LongBuffer 442 | - ShortBuffer 443 | 这些 Buffer 覆盖了能从 IO 中传输的所有的 Java 基本数据类型. 444 | 445 | 为了理解Buffer的工作原理,需要熟悉它的三个属性: 446 | 447 | - capacity代表这块Buffer的容量(DoubleBuffer, 其Capacity是100, 那么我们最多可以写入100个double值) 448 | - position代表当前可读(或写)的位置,初始的position值为0,每读/写一单位数据position的值递增1,直到limit结束,最大可为capacity – 1。 449 | - limit代表本次读(或写)的右边界位置,表示你最多能对Buffer读(或写)多少数据,初始limit的值等于Buffer的capacity 450 | 451 | > 其中 position 和 limit 的含义与 Buffer 处于读模式或写模式有关, 而 capacity 的含义与 Buffer 所处的模式无关. 452 | capacity 453 | 454 | #### 模式切换: 455 | 456 | 1. 将 NIO Buffer 转换为读模式 457 | - 调用 Buffer.flip()方法 458 | 2. 将 Buffer 转换为写模式 459 | - Buffer.clear() 清空整个buffer 460 | - Buffer.compact() 清空已读部分 461 | 462 | #### 关于 Direct Buffer 和 Non-Direct Buffer 的区别 463 | ##### Direct Buffer: 464 | 所分配的内存不在 JVM 堆上, 不受 GC 的管理.(但是 Direct Buffer 的 Java 对象是由 GC 管理的, 因此当发生 GC, 对象被回收时, Direct Buffer 也会被释放) 465 | 因为 Direct Buffer 不在 JVM 堆上分配, 因此 Direct Buffer 对应用程序的内存占用的影响就不那么明显(实际上还是占用了这么多内存, 但是 JVM 不好统计到非 JVM 管理的内存.) 466 | 申请和释放 Direct Buffer 的开销比较大. 因此正确的使用 Direct Buffer 的方式是在初始化时申请一个 Buffer, 然后不断复用此 buffer, 在程序结束后才释放此 buffer. 467 | 使用 Direct Buffer 时, 当进行一些底层的系统 IO 操作时, 效率会比较高, 因为此时 JVM 不需要拷贝 buffer 中的内存到中间临时缓冲区中. 468 | ##### Non-Direct Buffer: 469 | 直接在 JVM 堆上进行内存的分配, 本质上是 byte[] 数组的封装. 470 | 因为 Non-Direct Buffer 在 JVM 堆中, 因此当进行操作系统底层 IO 操作中时, 会将此 buffer 的内存复制到中间临时缓冲区中. 因此 Non-Direct Buffer 的效率就较低. 471 | 472 | #### 写入数据到 Buffer 473 | ``` 474 | int bytesRead = inChannel.read(buf); //read into buffer. 475 | buf.put(127); 476 | 从 Buffer 中读取数据 477 | //read from buffer into channel. 478 | int bytesWritten = inChannel.write(buf); 479 | byte aByte = buf.get(); 480 | ``` 481 | #### 重置 position 482 | Buffer.rewind()方法可以重置 position 的值为0, 因此我们可以重新读取/写入 Buffer 了. 483 | 如果是读模式, 则重置的是读模式的 position, 如果是写模式, 则重置的是写模式的 position. 484 | 例如: 485 | 486 | > rewind() 主要针对于读模式. 在读模式时, 读取到 limit 后, 可以调用 rewind() 方法, 将读 position 置为0. 487 | 488 | #### mark()和 reset() 489 | 我们可以通过调用 Buffer.mark()将当前的 position 的值保存起来, 随后可以通过调用 Buffer.reset()方法将 position 的值回复回来. 490 | 491 | #### flip, rewind, clear, compact 的区别 492 | 493 | flip 方法源码 494 | ``` 495 | public final Buffer flip() { 496 | limit = position; 497 | position = 0; 498 | mark = -1; 499 | return this; 500 | } 501 | ``` 502 | Buffer 的读/写模式共用一个 position 和 limit 变量. 503 | 当从写模式变为读模式时, 原先的 写 position 就变成了读模式的 limit. 504 | 505 | rewind 方法源码 506 | ``` 507 | public final Buffer rewind() { 508 | position = 0; 509 | mark = -1; 510 | return this; 511 | } 512 | ``` 513 | rewind, 即倒带, 这个方法仅仅是将 position 置为0. 514 | 515 | clear 方法源码: 516 | ``` 517 | public final Buffer clear() { 518 | position = 0; 519 | limit = capacity; 520 | mark = -1; 521 | return this; 522 | } 523 | ``` 524 | 根据源码我们可以知道, clear 将 positin 设置为0, 将 limit 设置为 capacity. 525 | 526 | compact 方法源码: 527 | ``` 528 | public ByteBuffer compact() { 529 | System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); 530 | position(remaining()); 531 | limit(capacity()); 532 | discardMark(); 533 | return this; 534 | } 535 | ``` 536 | #### Buffer 的比较 537 | 我们可以通过 equals() 或 compareTo() 方法比较两个 Buffer, 当且仅当如下条件满足时, 两个 Buffer 是相等的: 538 | 539 | - 两个 Buffer 是相同类型的 540 | - 两个 Buffer 的剩余的数据个数是相同的 541 | - 两个 Buffer 的剩余的数据都是相同的. 542 | 543 | 通过上述条件我们可以发现, 比较两个 Buffer 时, 并不是 Buffer 中的每个元素都进行比较, 而是比较 Buffer 中剩余的元素. -------------------------------------------------------------------------------- /src/main/java/com/lanux/io/stream/README.md: -------------------------------------------------------------------------------- 1 | # java 输入输出流 2 | 3 | ## 概念 4 | 5 | #### 1.存储设备 6 | 电脑上的数据有三种存储方式: 7 | 8 | - 外存,比如电脑上的硬盘,磁盘,U盘等 9 | - 内存,内存条 10 | - 缓存,缓存是在CPU里面的 11 | 12 | #### 2.输入流(Input Stream) 13 | 程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道 14 | 15 | #### 3.输出流(Output Stream) 16 | 程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。 17 | 18 | #### 4.数据流分类 19 | Java中的流分为两种: 20 | > 1) 字节流:数据流中最小的数据单元是字节,主要用于读写图片、声音、视频等二进制数据。
21 | > 2) 字符流:数据流中最小的数据单元是字符,在处理字符流时涉及了字符编码的转换问题。Java中的字符是Unicode编码,一个字符占用两个字节,主要用于文本文件等Unicode数据。 22 | 23 | # java.io包体系结构 24 | 在整个Java.io包中最重要的就是5个类和一个接口。 25 | 5个类指的是`File`、`OutputStream`、`InputStream`、`Writer`、`Reader`, 26 | 一个接口指的是`Serializable`。 27 | 28 | Java I/O包含三个部分: 29 | 30 | 1. **流式部分**
31 | IO的主体部分,以下内容主要讲这一部分; 32 | 2. **非流式部分**
33 | 主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类,专门用来管理磁盘文件与目录; 34 | 3. **其他类**
35 | 文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。 36 | 37 | 38 | # java io流式部分 39 | 40 | 在java.io包中有四个基本类:`InputStream`、`OutputStream`及`Reader`、`Writer`类,它们分别处理字节流和字符流: 41 | 42 | 输入/输出 | 字节流 | 字符流 43 | ---|---|--- 44 | 输入流 | InputStream | Reader 45 | 输出流 | OutputStream | Writer 46 | 47 | 48 | ![image](https://raw.githubusercontent.com/lanux/java-demo/master/public/img/java-IO-2.png) 49 | 50 | ![image](http://p9.pstatp.com/large/3ecb0003e4983bebb8b7) 51 | 52 | 分类 | 输入字节流 | 输出字节流 | 输入字符流 | 输出字符流 53 | ---|---|---|---|--- 54 | 抽象基类 | InputStream | OutputStream | Reader | Writer 55 | 文件 | FileInputStream | FileOutputStream | FileReader | FileWriter 56 | 数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter 57 | 管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter 58 | 缓冲 | BufferInputStream | BufferOutputStream | BufferReader | BufferWriter 59 | 转换 | -- | -- | InputStreamReader | OutputStreamWriter 60 | 对象 | ObjectInputStream | ObjectOutputStream | -- | -- 61 | 过滤基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter 62 | 打印 | -- | PrintStream | -- | PrintWriter 63 | 特殊 | DataInputStream | DataOutputStream | -- | -- 64 | 声音 | AudioInputStream | | | 65 | 工具 | SequenceInputStream | | | 66 | 什么是 Java 序列化,如何实现 Java 序列化? 67 | 68 | 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。 69 | 70 | 序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法, implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。 -------------------------------------------------------------------------------- /src/main/java/com/lanux/juc/SynchronizedCase.java: -------------------------------------------------------------------------------- 1 | package com.lanux.juc; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.Callable; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ThreadFactory; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | /** 13 | * static的方法属于类方法,它属于这个Class(注意:这里的Class不是指Class的某个具体对象), 14 | * 那么static获取到的锁,是属于类的锁。而非static方法获取到的锁,是属于当前对象的锁。 15 | * 所以,他们之间不会产生互斥。 16 | * 网上说 "synchronized修饰静态方法作用的象是这个类的所有对象" 这句话是错误的 17 | */ 18 | public class SynchronizedCase { 19 | static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2, 20 | new ThreadFactory() { 21 | AtomicInteger counter = new AtomicInteger(1); 22 | @Override 23 | public Thread newThread(Runnable r) { 24 | return new Thread(r,"thread-" + counter.getAndIncrement()); 25 | } 26 | }); 27 | 28 | public static void main(String[] args) throws Exception { 29 | test(); 30 | executor.shutdown(); 31 | } 32 | 33 | private static void test() throws InterruptedException { 34 | final SynchronizedCase demo = new SynchronizedCase(); 35 | List> list = Lists.newArrayList(); 36 | for (int i = 0; i < 5; i++) { 37 | list.add(() -> { 38 | staticFunction(); 39 | return null; 40 | }); 41 | } 42 | for (int i = 0; i < 5; i++) { 43 | list.add(() -> { 44 | demo.function(); 45 | return null; 46 | }); 47 | } 48 | executor.invokeAll(list); 49 | } 50 | 51 | public static synchronized void staticFunction() { 52 | for (int i = 0; i < 3; i++) { 53 | sleep(); 54 | System.out.println(Thread.currentThread().getName() + ": Static function running"); 55 | } 56 | } 57 | 58 | public synchronized void function() { 59 | for (int i = 0; i < 3; i++) { 60 | sleep(); 61 | System.out.println(Thread.currentThread().getName() + ": not static function running"); 62 | } 63 | } 64 | 65 | private static void sleep() { 66 | try { 67 | Thread.sleep(1000); 68 | } catch (InterruptedException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/juc/ThreadInterruptCase.java: -------------------------------------------------------------------------------- 1 | package com.lanux.juc; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | public class ThreadInterruptCase { 9 | 10 | class ThreadCase extends Thread implements Runnable{ 11 | 12 | @Override 13 | public void run() { 14 | super.run(); 15 | } 16 | } 17 | 18 | static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), 19 | new ThreadFactory() { 20 | AtomicInteger counter = new AtomicInteger(1); 21 | 22 | @Override 23 | public Thread newThread(Runnable r) { 24 | Thread thread = new Thread(r, "thread-" + counter.getAndIncrement()); 25 | return thread; 26 | } 27 | }); 28 | 29 | public static void main(String[] args) { 30 | for (int i = 0; i < 100000; i++) { 31 | executor.submit(new Runnable() { 32 | private int count; 33 | 34 | @Override 35 | public void run() { 36 | System.out.println(count + " " + Thread.currentThread().getName()); 37 | Thread.currentThread().stop(); 38 | long time = System.currentTimeMillis(); 39 | /* 40 | * 使用while循环模拟 sleep 方法,这里不要使用sleep,否则在阻塞时会 抛 41 | * InterruptedException异常 42 | */ 43 | while ((System.currentTimeMillis() - time < 100)) {} 44 | } 45 | 46 | public Runnable setCount(int count) { 47 | this.count = count; 48 | return this; 49 | } 50 | }.setCount(i)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/juc/WaitNotifySemaphore.java: -------------------------------------------------------------------------------- 1 | package com.lanux.juc; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | public class WaitNotifySemaphore { 7 | private final static int QUERY_LENGTH = 5; 8 | private final static int THREAD_COUNT = 20; 9 | private final static AtomicInteger COUNTER = new AtomicInteger(0); 10 | private final static Object LOCK = new Object(); 11 | 12 | public static void main(String[] args) { 13 | Random random = new Random(); 14 | for (int i = 0; i < THREAD_COUNT; i++) { 15 | new Thread(String.valueOf(i)) { 16 | @Override 17 | public void run() { 18 | tryLock(); 19 | System.out.println("start " + this.getName()); 20 | try { 21 | int i1 = random.nextInt() & 3000; 22 | Thread.sleep(100 + i1); 23 | } catch (InterruptedException e) { 24 | e.printStackTrace(); 25 | } 26 | System.out.println("unlock " + this.getName()); 27 | unLock(); 28 | } 29 | }.start(); 30 | } 31 | } 32 | 33 | private static void unLock() { 34 | COUNTER.getAndDecrement(); 35 | synchronized (LOCK) { 36 | LOCK.notify(); 37 | } 38 | } 39 | 40 | private static void tryLock() { 41 | int tryTimes = 0; 42 | int nowValue = COUNTER.get(); 43 | while (true) { 44 | if (nowValue < QUERY_LENGTH && COUNTER.compareAndSet(nowValue, nowValue + 1)) { 45 | break; 46 | } 47 | if (tryTimes % 3 == 0) { 48 | waitForNotify(); 49 | } 50 | nowValue = COUNTER.get(); 51 | tryTimes++; 52 | } 53 | } 54 | 55 | private static void waitForNotify() { 56 | synchronized (LOCK) { 57 | try { 58 | LOCK.wait(5); 59 | } catch (InterruptedException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/pattern/ProxyCase.java: -------------------------------------------------------------------------------- 1 | package com.lanux.pattern; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Proxy; 6 | 7 | public class ProxyCase { 8 | 9 | interface UserService { 10 | void addUser(String userId, String userName); 11 | 12 | void delUser(String userId); 13 | } 14 | 15 | class UserServiceImpl implements UserService { 16 | 17 | @Override 18 | public void addUser(String userId, String userName) { 19 | System.out.println("UserServiceImpl.addUser"); 20 | } 21 | 22 | @Override 23 | public void delUser(String userId) { 24 | System.out.println("UserServiceImpl.delUser"); 25 | } 26 | } 27 | 28 | class UserServiceImplProxy implements UserService { 29 | 30 | // 目标对象 31 | private UserService userService; 32 | 33 | // 通过构造方法传入目标对象 34 | public UserServiceImplProxy(UserService userService) { 35 | this.userService = userService; 36 | } 37 | 38 | @Override 39 | public void addUser(String userId, String userName) { 40 | System.out.println("start-->addUser()"); 41 | userService.addUser(userId, userName); 42 | System.out.println("success-->addUser()"); 43 | } 44 | 45 | @Override 46 | public void delUser(String userId) { 47 | userService.delUser(userId); 48 | } 49 | } 50 | 51 | class LogHandler implements InvocationHandler { 52 | 53 | // 目标对象 54 | private Object targetObject; 55 | 56 | /** 57 | * 根据传入的目标返回一个代理对象 58 | * @param targetObject 59 | * @return 60 | */ 61 | public Object newProxyInstance(Object targetObject) { 62 | this.targetObject = targetObject; 63 | //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 64 | //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器 65 | //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口 66 | //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法 67 | return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), 68 | targetObject.getClass().getInterfaces(), this); 69 | } 70 | 71 | /** 72 | * InvocationHandler接口的方法, 73 | * @param proxy 表示代理, 74 | * @param method 表示原对象被调用的方法, 75 | * @param args 表示方法的参数 76 | */ 77 | @Override 78 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 79 | System.out.println("start-->>" + method.getName()); 80 | Object ret = method.invoke(targetObject, args); 81 | System.out.println("success-->>" + method.getName()); 82 | return ret; 83 | } 84 | 85 | } 86 | 87 | public static void main(String[] args) { 88 | ProxyCase pc = new ProxyCase(); 89 | // UserService userService = pc.new UserServiceImplProxy(pc.new UserServiceImpl()); 90 | // userService.addUser("001", "张三"); 91 | 92 | LogHandler logHandler = pc.new LogHandler(); 93 | UserService userService = (UserService) logHandler.newProxyInstance(pc.new UserServiceImpl()); 94 | //UserManager userManager=new UserServiceImpl(); 95 | userService.addUser("0001", "张三"); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/pattern/ProxyDemo.java: -------------------------------------------------------------------------------- 1 | package com.lanux.pattern; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Proxy; 6 | 7 | public class ProxyDemo { 8 | 9 | public interface BasicService { 10 | String getServiceName(); 11 | } 12 | 13 | public interface MyService extends BasicService { 14 | void sayHello(String name); 15 | 16 | void sayGoodBye(String name); 17 | } 18 | 19 | public class MyServiceProxy implements InvocationHandler { 20 | 21 | @Override 22 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 23 | if (args != null) 24 | System.out.println(method.getName() + " " + args[0]); 25 | return this.getClass().getName(); 26 | } 27 | 28 | public MyService newProxyInstance() { 29 | return (MyService) Proxy.newProxyInstance(ProxyDemo.this.getClass().getClassLoader(), 30 | new Class[] { MyService.class }, 31 | this); 32 | } 33 | } 34 | 35 | public static void main(String[] args) { 36 | ProxyDemo demo = new ProxyDemo(); 37 | MyService proxy = demo.new MyServiceProxy().newProxyInstance(); 38 | String serviceName = proxy.getServiceName(); 39 | proxy.sayHello(serviceName); 40 | proxy.sayGoodBye(serviceName); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/rxjava/Test.java: -------------------------------------------------------------------------------- 1 | package com.lanux.rxjava; 2 | 3 | import com.google.common.collect.Lists; 4 | import io.reactivex.Observable; 5 | import io.reactivex.ObservableEmitter; 6 | import io.reactivex.ObservableOnSubscribe; 7 | import io.reactivex.schedulers.Schedulers; 8 | 9 | import java.util.ArrayList; 10 | 11 | public class Test { 12 | static class User{ 13 | private String name; 14 | } 15 | 16 | public static void main(String[] args) throws InterruptedException { 17 | Observable.just(new ArrayList(),new ArrayList()) 18 | .flatMap(userList->Observable.fromIterable(userList)) 19 | .map(user->user.name) 20 | .subscribe(name-> System.out.println(name)); 21 | Observable.create(new ObservableOnSubscribe() { 22 | @Override 23 | public void subscribe(ObservableEmitter observableEmitter) throws Exception { 24 | String s = "hello"; 25 | System.out.println("发送线程 " + Thread.currentThread().getName()); 26 | Thread.sleep(100); 27 | observableEmitter.onNext(s); 28 | observableEmitter.onComplete(); 29 | } 30 | }) 31 | .observeOn(Schedulers.computation()) 32 | .subscribeOn(Schedulers.newThread()) 33 | .subscribe(s -> { 34 | System.out.println("消费线程 " + Thread.currentThread().getName()); 35 | }); 36 | Thread.sleep(200); 37 | System.out.println("888 " + Thread.currentThread().getName()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/tool/ByteUtil.java: -------------------------------------------------------------------------------- 1 | package com.lanux.tool; 2 | 3 | /** 4 | * Created by lanux on 2017/9/18. 5 | */ 6 | public class ByteUtil { 7 | 8 | public static int byteArrayToInt(byte[] b, int offset) { 9 | int value = 0; 10 | for (int i = 0; i < 4; i++) { 11 | int shift = (4 - 1 - i) * 8; 12 | value += (b[i + offset] & 0x000000FF) << shift; 13 | } 14 | return value; 15 | } 16 | 17 | public static byte intToByte(int x) { 18 | return (byte) x; 19 | } 20 | 21 | public static int byteToInt(byte b) { 22 | return b & 0xFF; 23 | } 24 | 25 | public static int byteArrayToInt(byte[] b) { 26 | return byteToInt(b[3]) | 27 | byteToInt(b[2]) << 8 | 28 | byteToInt(b[1]) << 16 | 29 | byteToInt(b[0]) << 24; 30 | } 31 | 32 | public static byte[] intToByteArray(int a) { 33 | return new byte[] { 34 | (byte) ((a >> 24) & 0xFF), 35 | (byte) ((a >> 16) & 0xFF), 36 | (byte) ((a >> 8) & 0xFF), 37 | (byte) (a & 0xFF) 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/lanux/tool/StringTool.java: -------------------------------------------------------------------------------- 1 | package com.lanux.tool; 2 | 3 | /** 4 | * Created by lanux on 2017/9/18. 5 | */ 6 | public class StringTool { 7 | public static String maxString(String value, int length) { 8 | if (value == null || value.length() <= length) { 9 | return value; 10 | } 11 | StringBuilder sb = new StringBuilder(); 12 | sb.append(value.substring(0, length / 2)); 13 | sb.append("……"); 14 | sb.append(value.substring(value.length() - length / 2, value.length())); 15 | return sb.toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/addpoint.drl: -------------------------------------------------------------------------------- 1 | package com.drools.demo 2 | 3 | import com.lanux.drools.PointDomain; 4 | 5 | rule birthdayPoint 6 | // 过生日,则加10分,并且将当月交易比数翻倍后再计算积分 7 | salience 100 8 | lock-on-active true 9 | when 10 | $pointDomain : PointDomain(birthDay == true) 11 | then 12 | $pointDomain.setPoint($pointDomain.getPoint()+10); 13 | $pointDomain.setBuyNums($pointDomain.getBuyNums()*2); 14 | $pointDomain.setBuyMoney($pointDomain.getBuyMoney()*2); 15 | $pointDomain.setBillThisMonth($pointDomain.getBillThisMonth()*2); 16 | 17 | $pointDomain.recordPointLog($pointDomain.getUserName(),"birthdayPoint"); 18 | end 19 | 20 | rule billThisMonthPoint 21 | // 2011-01-08 - 2011-08-08每月信用卡还款3次以上,每满3笔赠送30分 22 | salience 99 23 | lock-on-active true 24 | date-effective "2011-01-08 23:59:59" 25 | date-expires "2011-08-08 23:59:59" 26 | when 27 | $pointDomain : PointDomain(billThisMonth >= 3) 28 | then 29 | $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBillThisMonth()/3*30); 30 | $pointDomain.recordPointLog($pointDomain.getUserName(),"billThisMonthPoint"); 31 | end 32 | 33 | rule buyMoneyPoint 34 | // 当月购物总金额100以上,每100元赠送10分 35 | salience 98 36 | lock-on-active true 37 | when 38 | $pointDomain : PointDomain(buyMoney >= 100) 39 | then 40 | $pointDomain.setPoint($pointDomain.getPoint()+ (int)$pointDomain.getBuyMoney()/100 * 10); 41 | $pointDomain.recordPointLog($pointDomain.getUserName(),"buyMoneyPoint"); 42 | end 43 | 44 | rule buyNumsPoint 45 | // 当月购物次数5次以上,每五次赠送50分 46 | salience 97 47 | lock-on-active true 48 | when 49 | $pointDomain : PointDomain(buyNums >= 5) 50 | then 51 | $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBuyNums()/5 * 50); 52 | $pointDomain.recordPointLog($pointDomain.getUserName(),"buyNumsPoint"); 53 | end 54 | 55 | rule allFitPoint 56 | // 特别的,如果全部满足了要求,则额外奖励100分 57 | salience 96 58 | lock-on-active true 59 | when 60 | $pointDomain:PointDomain(buyNums >= 5 && billThisMonth >= 3 && buyMoney >= 100) 61 | then 62 | $pointDomain.setPoint($pointDomain.getPoint()+ 100); 63 | $pointDomain.recordPointLog($pointDomain.getUserName(),"allFitPoint"); 64 | end -------------------------------------------------------------------------------- /src/test/java/com/lanux/io/bio/TestBioClient.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | import org.apache.commons.lang3.RandomUtils; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * Created by lanux on 2017/9/16. 10 | */ 11 | public class TestBioClient { 12 | 13 | public static void main(String[] args) throws Exception { 14 | BioClient client = new BioClient().init(); 15 | int count = 0; 16 | // for (int j = 0; j < 50; j++) { 17 | // client.write(count++ + "=" + RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(2000, 10000))); 18 | // } 19 | // for (int j = 0; j < 50; j++) { 20 | // client.read(); 21 | // } 22 | for (int j = 0; j < 100000; j++) { 23 | client.write(count++ + "=" + RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(8000, 10000))); 24 | client.read(); 25 | System.out.println(); 26 | } 27 | // client.close(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/lanux/io/bio/TestBioServer.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | /** 4 | * Created by lanux on 2017/9/19. 5 | */ 6 | public class TestBioServer { 7 | public static void main(String[] args) { 8 | new BioServer(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/lanux/io/bio/TestNioClient.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | import com.lanux.io.nio.NioClient; 4 | import org.apache.commons.lang3.RandomStringUtils; 5 | import org.apache.commons.lang3.RandomUtils; 6 | 7 | /** 8 | * Created by lanux on 2017/9/16. 9 | */ 10 | public class TestNioClient { 11 | 12 | public static void main(String[] args) throws Exception { 13 | NioClient client = new NioClient(); 14 | new Thread(() -> client.listen(), "nio client").start(); 15 | while (!client.connected) { 16 | Thread.sleep(100); 17 | } 18 | for (int i = 0; i < 100; i++) { 19 | client.write(i + "=" + RandomStringUtils.randomAlphabetic(RandomUtils.nextInt(1000, 20000))); 20 | } 21 | // client.close(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/lanux/io/bio/TestNioServer.java: -------------------------------------------------------------------------------- 1 | package com.lanux.io.bio; 2 | 3 | import com.lanux.io.nio.NioServer2; 4 | 5 | public class TestNioServer { 6 | public static void main(String[] args) { 7 | new NioServer2(); 8 | 9 | } 10 | } 11 | --------------------------------------------------------------------------------