diff --git a/README.md b/README.md index a201b86..cdb2a9b 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,14 @@ **为开源贡献自己的一份力量。** -## [JVM 篇](/jvm) +**欢迎关注个人公众号,每天更新原创技术好文**: + -## [Java8 篇](/java8) +## :blue_book: [JVM 篇](/jvm) -## [数据结构篇](/datastructure) -- [StringBuilder和StringBuffer](/datastructure/stringbuilderandstringbuffer.md) -- [BlockingQueue和BlockingDeque](/datastructure/blockingqueueanddeque.md) - * Queue - * Deque - * LinkedList - * ArrayBlockingQueue - * LinkedBlockingQueue - * LinkedBlockingDeque +## :coffee: [Java 基础知识篇](/javabase) -## [代码篇](/codeinterview) +## :scroll: [代码篇](/codeinterview) 主要介绍 LeetCode 上面的算法题目,以及面试过程中遇到的实际编码问题总结。 ### [排序相关](/sortpro) @@ -37,21 +30,19 @@ - [桶排序](/sortpro/9.bucketSort.md) - [基数排序](/sortpro/10.radixSort.md) -## [Lock 篇](/lock) +## :lock: [Lock 篇](/lock) -## [IO 篇](/io) +## :file_folder: [IO 篇](/io) -## [大数据篇](/bigdata) +## :computer: [大数据篇](/bigdata) -## [架构篇](/architecture) +## :earth_asia: [机器学习](/machinelearning) -## [关键字篇](/keywords) -- [transient](/keywords/transient.md) -- [volatile](/keywords/volatile.md) +## :floppy_disk: [架构篇](/architecture) -## [web 篇](/web) +## :earth_asia: [web 篇](/web) -## [书单篇](/books) +## :books: [书单篇](/books) 各种编程类书籍整理。大家可以直接下载阅读,增长自己编程技术。 @@ -64,4 +55,3 @@ 有什么问题也可以在这里进行[讨论](https://github.com/joyang1/JavaInterview/issues/1)。 - diff --git a/architecture/2PC-3PC.md b/architecture/2PC-3PC.md index e516343..b1671e9 100644 --- a/architecture/2PC-3PC.md +++ b/architecture/2PC-3PC.md @@ -65,9 +65,9 @@ 参与者在完成事务 Rollback 之后,向协调者发送 Ack 消息。 - 4. 中段事务 + 4. 中断事务 - 协调者在接收到所有参与者节点反馈的 Ack 消息后,完成事务中段。 + 协调者在接收到所有参与者节点反馈的 Ack 消息后,完成事务中断。 总结来说,二阶段提交将一个事务的处理过程分为了投票和执行两个阶段,其核心是对每个事务都采用了先尝试后提交的处理方式,因此也可以将二阶段提交看作是一个强一致性算法,下图 1 和图 2 分别展示了二阶段提交过程中"事务提交"和"事务中断"两种场景下的交互流程。 diff --git a/architecture/README.md b/architecture/README.md index af28dac..0133378 100644 --- a/architecture/README.md +++ b/architecture/README.md @@ -1,7 +1,12 @@ # 架构篇 +## UML图 +分享项目设计中需要的类图、对象图、时序图等。 +[UML图总结](uml.md) + ## 设计模式 -分享实际项目中的设计模式经验 +分享实际项目中的设计模式经验。 +[设计模式](design_patterns.md) ## 分布式 @@ -20,5 +25,15 @@ ## 大型网站架构 ## 流行数据库架构 -- [mysql](mysql.md) -- [redis](redis.md) +- [MySQL](mysql.md) +- [Redis](redis.md) +- [ElasticSearch](elasticSearch.md) +- MongoDB + +## 大数据 +- Hadoop +- Hive +- Spark + +## 推荐系统 +- [推荐系统](recommend_system.md) diff --git a/architecture/design_patterns.md b/architecture/design_patterns.md new file mode 100644 index 0000000..6a37917 --- /dev/null +++ b/architecture/design_patterns.md @@ -0,0 +1,177 @@ +# 设计模式 +该章节主要是在读《Head First 设计模式》的总结。 + +## 策略模式 +案例:鸭子应用(模拟鸭子游戏)。 +游戏中需要设计各种鸭子,有戏水的、呱呱叫的。用学习过的OO技术,张三(鸭子游戏"首席架构师")首先会设计一个鸭子超类,并让各种鸭子继承该超类。 + + +类图中,超类Duck实现了鸭子呱呱叫(quack)和游泳(swim),由于鸭子外观都不同,所以display()方法是抽象的,由子类去实现。 +具体代码在该项目的位置:JavaInterview/architecture/src/main/java/cn.tommyyang.designpatterns.duckgame.Duck。 + +以上完成第一版鸭子应用。 + +由于鸭子游戏鸭子种类一直就那么几种,导致游戏用户增长上遇到了较大的瓶颈,所以急需创新。通过大家的头脑风暴,首先我们得让鸭子飞起来。这时,负责鸭子应用的开发人员张三接到了这个需求,认为只需在Duck超类上加上fly方法即可。设计如下: + + +改完后,完成了游戏的第二次发版。改动点: +- 加入鸭子会飞的逻辑。 +- 有鸭子的叫声不是呱呱叫,而是吱吱叫。 + +发布完成后,在公司内部开始进行试运行。试运行期间,同事们说,为什么橡皮鸭也会飞啊。张三收到了反馈后,他发现出问题了,不能直接在超类进行fly()方法的实现,或者在橡皮鸭内部覆盖fly方法,然后什么都不做。 +如果后面有几万种鸭子,比如加入了诱饵鸭(是木头鸭),不会飞也不会叫,那怎么办,也去覆盖quack、fly方法?那这样继承这些方法有什么意义呢? +看到这里,不知道大家怎么想? + +首先总结下利用继承来提供Duck的行为会导致哪些缺点: +- 代码在多个子类中重复。 +- 运行时的行为不容易改变。 +- 不能让鸭子跳舞。 +- 很难知道所有鸭子的全部行为。 +- 鸭子不能同时又飞又叫。 +- 改变会牵一发动全身,造成其他鸭子不想要的改变。 + +通过在游戏的第二版发布过程中,张三认识到继承无法解决在当下游戏版本经常更新的情况。张三知道鸭子的规则会经常改变,每当游戏中新增新的鸭子时,就需要检查是否需要覆盖fly()/quack()......等方法,张三作为"首席架构师",显然觉得这样的架构太low了,无法容忍。 + +故张三认为需要定义一些更清晰的接口,让"某些"(非全部)鸭子类型可飞/可叫......以后还可能扩展更多方法。 +新的架构图如下: + + +以上是张三使用接口实现的,但是张三对着类图思考了一会,发现虽然Flyable和Quackable可以解决"一小部分"问题(不会出现会飞的橡皮鸭),但是却造成了大量重复代码(大量的重复飞,重复嘎嘎叫),但代码却无法复用,甚至,在会飞的鸭子中,飞行的动作还可能各种变化...... + +作为"首席架构师",张三还是觉得否定了上述方案,深思许久后,总结道: +- 继承无法解决问题。 +- Flyable、Quackable接口不错,但是需要让会飞的鸭子才继承Flybale。 +- Java接口不具有实现代码(java8 default接口可以,但是不鼓励使用),所以继承接口无法达到代码的复用。 + +这时,张三突然想到从《Head First 设计模式》中看到的第一个设计模式中提到的三个原则: +- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。 +- 针对接口编程,而不是针对实现编程。 +- 多用组合,少用继承。 + +通过上述三个原则,张三知道,Duck类中fly()和quack()会随着鸭子的不同而改变。那么需要把这两个行为从Duck类中取出来,建立一组新类来代表每个行为。及游戏中需要**鸭子类**和`鸭子行为类`。`鸭子行为类`专门用于提供某行为接口的实现,那么鸭子类就不需要了解行为的实现细节了。这就是`针对接口编程,而不是针对实现编程`。 + +了解到这些设计原则后,张三开始重新设计类图。 + + +通过以上设计,关键在于,鸭子将飞行和呱呱叫的动作"委托"(delegate)别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。 +这样的好处,就是上述的三个原则,并且在运行时,可以随意修改其呱呱叫和飞行的行为,通过setFlyBehavior和setQuackBehavior来实现。 + +MallardDuck 的具体实现如下: + +``` java + +/** + * 野鸭类 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:00 + * @Software: IntelliJ IDEA + * @File : MallardDuck.java + */ +public class MallardDuck extends Duck { + + public MallardDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("绿头"); + } +} + +``` + +在构造野鸭类的时候,完成 FlyBehavior 和 QuackBehavior 的定义。 + +最后,张三"首席架构师",通过`策略模式(Strategy Pattern)`实现了鸭子游戏。 + +`策略模式`定义了算法簇,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 + +恭喜你,完成了第一个设计模式`策略模式`的学习,后续希望该模式在你的编程生涯中可以持续被使用。 + +## 观察者模式 +张三由于通过使用`策略模式`完成鸭子游戏应用的重构后,在鸭子游戏的维护和更新上只需要投入少量的时间。 +于是张三接到了下一个需求,*互联网气象观测站*的架构工作。 + +*互联网气象观测站*的三个组成部分: +- 气象站(获得实际气象数据的物理装置) +- WeatherData 对象(追踪来自气象站的数据,并更新布告板) +- 布告板(显示当前天气状况)给用户看 + +张三总结出系统用例如下: + +张三认为,如果团队接受了这个项目,那工作就是建立一个应应用,利用 WeatherData 对象取得数据(由公司兄弟团队去和气象站对接),并更新三个布告板:当前状况、气象统计、天气预报。 + +张三团队开始了工作,第二天由公司兄弟团队提供的获取相关气象数据的 WeatherData 类,类图如下: + +下面看一下实现的反面案例:针对具体实现编程。 +具体代码在该项目的位置:[JavaInterview](https://github.com/joyang1/JavaInterview); +JavaInterview/architecture/src/main/java/cn.tommyyang.designpatterns.observable.WeatherData。 + +```java + +public class WeatherData { + /** + * 温度 + */ + private float temperature; + + /** + * 湿度 + */ + private float humidity; + + /** + * 气压 + */ + private float pressure; + + public float getTemperature() { + return this.temperature; + } + + public float getHumidity() { + return this.humidity; + } + + public float getPressure() { + return pressure; + } + + /** + * 一旦气象测量更新,此方法会被调用 + */ + public void measurementsChanged() { + float temp = getTemperature(); + float humidity = getHumidity(); + float pressure = getPressure(); + + // 布告板数据更新 + // 针对具体的实现编程,导致后续增删布告板时必须修改程序 + CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); + currentConditionsDisplay.update(temp, humidity, pressure); + } +} + +``` +回故下上一篇讲解的一些设计原则: +- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。 +- 针对接口编程,而不是针对实现编程。 +- 多用组合,少用继承。 + +不难发现上面的代码非常*垃圾*。 + +下面由首席架构师张三带大家使用观察者模式来实现该工程。 + +`观察者模式`:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。 +张三画了一张简易的观察者模式图如下: + +## 装饰模式 + +## 工厂模式 +### 简单工厂模式 + +### 工厂方法模式 \ No newline at end of file diff --git a/architecture/elasticSearch.md b/architecture/elasticSearch.md new file mode 100644 index 0000000..86a4bf4 --- /dev/null +++ b/architecture/elasticSearch.md @@ -0,0 +1,131 @@ +# ElasticSearch + +ElasticSearch 技术相关介绍。 + +`目录` +[too many dynamic script rejected](#Too Many Dynamic Script Rejected) + +## Too Many Dynamic Script Rejected + +最近工程中会使用到ElasticSearch(以下统称ES),就是将一些统计结果(点击量:click_count,曝光量:impr_count,点击曝光比:ctr=click_count/impr_count)写入到ES,会用到ES的dynamic script去实时修改ctr。然后就遇到了too many dynamic script rejected的问题。 + +### 问题解决过程 +#### 获取EsClient的源码 + +``` java +public static synchronized TransportClient getInstance() throws UnknownHostException { + if(transportClient == null){ + String clusterName = DataSourceUtils.getProperteValue("es.cluster", "es"); + String host = DataSourceUtils.getProperteValue("es.host","localhost"); + Integer port = Integer.parseInt(DataSourceUtils.getProperteValue("es.port","9300")); + Settings settings = Settings.builder() + .put("cluster.name", clusterName) + .put("client.transport.sniff", true) + .build(); + transportClient = new PreBuiltTransportClient(settings).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); + } + return transportClient; + } +``` + +#### 贴上有问题的源码(dynamic script update ctr) + +``` java +TransportClient esClient = EsClient.getInstance(); +UpdateRequest updateRequest = new UpdateRequest("notes2", "note", "2"); +String script = String.format("ctx._source.click_count=%d;ctx._source.impr_count=%d;ctx._source.ctr=(double)ctx._source.click_count/ctx._source.impr_count", 15, 120); +updateRequest.script(new Script(script)); +updateRequest.retryOnConflict(3); +esClient.update(updateRequest).get(); +``` + +用以上code去update ctr, 然后发现log中出现了too many dynamic script rejected,导致ctr更新失败。 + +#### 寻找原因 + +在ES官网上查找Script相关的文档说明,在Script Parameters栏目发现以下参数 + +`lang` + +Specifies the language the script is written in. Defaults to painless. +指定编写脚本的语言,默认为painless。 + +`source, id` + +Specifies the source of the script. An inline script is specified source. A stored script is specified id and is retrieved from the cluster state. +指定脚本的来源。inline类型的脚本是指定source。stored类型的脚本是指定source的id,通过id从ES集群上检索对应的source。 + +`params` + +Specifies any named parameters that are passed into the script as variables. +指定作为变量传递到脚本的任何参数。 + +然后在这个下面的一段话,很重要,就是解决我们问题的关键信息 + +The first time Elasticsearch sees a new script, it compiles it and stores the compiled version in a cache. Compilation can be a heavy process. + +If you need to pass variables into the script, you should pass them in as named params instead of hard-coding values into the script itself. For example, if you want to be able to multiply a field value by different multipliers, don’t hard-code the multiplier into the script: +> "source": "doc['my_field'] * 2" + +Instead, pass it in as a named parameter: + +> "source": "doc['my_field'] * multiplier", + "params": { + "multiplier": 2 + } + +通过以上code我们可以得知 对于dynamic script我们应该使用params参数来传递参数,而不是把参数拼接在script中,这样就不需要ES就会在第一次的时候把script编译后保存在缓存中,而不是每次都会去编译。 + +`方法一` 使用inline type script + +``` java +TransportClient esClient = EsClient.getInstance(); +UpdateRequest updateRequest = new UpdateRequest("notes2", "note", "2"); +Map params = new HashMap() { + { + put("click_count", 120); + put("impr_count", 5); + } +}; + +String code = "ctx._source.click_count=params.click_count;ctx._source.impr_count=params.impr_count;ctx._source.ctr=(double)ctx._source.click_count/ctx._source.impr_count*100"; +Script script = new Script(ScriptType.INLINE, "painless", code, params); +updateRequest.script(script); +esClient.update(updateRequest).get(); +``` + +`方法二` 使用stored type script + +``` java +TransportClient esClient = EsClient.getInstance(); +UpdateRequest updateRequest = new UpdateRequest("notes2", "note", "2"); +Map params = new HashMap() { + { + put("click_count", 120); + put("impr_count", 5); + } +}; + +//String code = "ctx._source.read_time=params.read_time;ctx._source.read_num=params.read_num;ctx._source.avg_read_time=(double)ctx._source.read_time/ctx._source.read_num"; +Script script = new Script(ScriptType.STORED, "painless", "ctr_calc", params); +updateRequest.script(script); +esClient.update(updateRequest).get(); +``` + +使用方法二stored script的时候 需要在kibana里面将script存储到ES集群里面 + +`方法如下` + +``` java +POST _scripts/ctr_calc +{ + "script": { + "lang": "painless", + "source": "ctx._source.click_count=params.click_count;ctx._source.impr_count=params.impr_count;ctx._source.ctr=(double)ctx._source.click_count/ctx._source.impr_count*100" + } +} +``` + +#### 结果 + +通过以上两种方法,都可以解决too many dynamic script rejected的问题。 diff --git a/architecture/img/aggregation.png b/architecture/img/aggregation.png new file mode 100644 index 0000000..3ab02c7 Binary files /dev/null and b/architecture/img/aggregation.png differ diff --git a/architecture/img/association.png b/architecture/img/association.png new file mode 100644 index 0000000..96068ad Binary files /dev/null and b/architecture/img/association.png differ diff --git a/architecture/img/combine.png b/architecture/img/combine.png new file mode 100644 index 0000000..b5f64f4 Binary files /dev/null and b/architecture/img/combine.png differ diff --git a/architecture/img/depend.png b/architecture/img/depend.png new file mode 100644 index 0000000..457ad2a Binary files /dev/null and b/architecture/img/depend.png differ diff --git a/architecture/img/extends.png b/architecture/img/extends.png new file mode 100644 index 0000000..e0fb4c1 Binary files /dev/null and b/architecture/img/extends.png differ diff --git a/architecture/img/implement.png b/architecture/img/implement.png new file mode 100644 index 0000000..9057c45 Binary files /dev/null and b/architecture/img/implement.png differ diff --git a/architecture/img/uml-class-animal.jpg b/architecture/img/uml-class-animal.jpg new file mode 100644 index 0000000..fd38c89 Binary files /dev/null and b/architecture/img/uml-class-animal.jpg differ diff --git a/architecture/img/uml-object.jpg b/architecture/img/uml-object.jpg new file mode 100644 index 0000000..3d165eb Binary files /dev/null and b/architecture/img/uml-object.jpg differ diff --git a/architecture/mysql.md b/architecture/mysql.md index 54e68a7..12b7375 100644 --- a/architecture/mysql.md +++ b/architecture/mysql.md @@ -1,11 +1,597 @@ # MySQL -MySQL 是我们日常开发中用到的最多的关系型数据库,我们需要多了解其底层以及 MySQL 数据库相关锁类型的实现。 +MySQL 是我们日常开发中用到的最多的关系型数据库,该篇总结 MySQL 的常用知识点。 -## 锁总结 +>**目录** +- [查询优化](#查询优化) + + - [优化之 EXPLAIN](#优化之EXPLAIN) + - [优化访问](#优化数据访问) + - [重构查询方式](#重构查询方式) + +- [MySQL事物隔离级别与锁总结](#MySQL事物隔离级别与锁总结) + +- [主从复制](#主从复制) + +- [读写分离](#读写分离) + +## 查询优化 +### 优化之EXPLAIN +使用 EXPLAIN 可以帮助分析自己写的 SQL 语句,看看我们是否用到了索引。 + +#### 按以下两个 SQL 新建两张表 +```sql + +CREATE TABLE `demo` ( + `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo name', + `author` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo author', + PRIMARY KEY (`ID`), + KEY `IX_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE `demo_details` ( + `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `demoId` int(11) unsigned NOT NULL COMMENT 'demo id', + `url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo author', + PRIMARY KEY (`ID`), + KEY `FIX_demoId_ID` (`demoId`), + CONSTRAINT `demo_details_ibfk_1` FOREIGN KEY (`demoId`) REFERENCES `demo` (`ID`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +``` + +#### 具体用法 + +``` +mysql> EXPLAIN SELECT * FROM demo WHERE ID = 1\G + +执行结果: + +id: 1 +select_type: SIMPLE +table: demo +partitions: NULL +type: const +possible_keys: PRIMARY +key: PRIMARY +key_len: 4 +ref: const +rows: 1 +filtered: 100.00 +Extra: NULL +1 row in set, 1 warning (0.00 sec) + +``` + +以上执行结果各行表示的含义: +- id: SELECT 查询的标识符,每个 SELECT 都会自动分配一个唯一的标识符 +- select_type: SELECT 查询的类型 +- table: 查询的是哪个表 +- partitions: 匹配的分区 +- type: join 类型 +- possible_keys: 此次查询中可能选用的索引 +- key: 此次查询中确切使用到的索引 +- ref: 哪个字段或常数与 key 一起被使用 +- rows: 显示此查询一共扫描了多少行,这个是一个估计值 +- filtered: 表示此查询条件所过滤的数据的百分比 +- extra: 额外的信息 + +#### select_type +`select_type` 表示了查询的类型, 它的常用取值有: +- SIMPLE: 表示此查询不包含 UNION 查询或子查询 +- PRIMARY: 表示此查询是最外层的查询 +- UNION: 表示此查询是 UNION 的第二或随后的查询 +- DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询 +- UNION RESULT: UNION 的结果 +- SUBQUERY: 子查询中的第一个 SELECT +- DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果 + +最常见的应该是 SIMPLE,当我们的查询 SQL 里面没有 UNION 查询或者子查询的时候,那么通常就是 SIMPLE 类型。 + +#### type +`type` 字段比较重要,它提供了判断查询是否高效的重要依据。通过 type 字段,我们可以判断此次查询是**全表扫描**,还是**索引扫描**等。 + +`type` 常用取值有: +- system + + 表中只有一条数据,这个类型是特殊的 const 类型。 + +- const + + 针对主键或唯一索引的等值查询扫描,最多只返回一行数据,const 查询速度非常快,因为它仅仅读取一次即可。 + +- eq_ref + + 此类型通常出现在多表的 join 查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果,并且查询的比较操作通常是 =,查询效率较高。 + demo 如下: + ``` + + mysql>EXPLAIN select * from demo,demo_details where demo.ID = demo_details.demoId\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo_details + partitions: NULL + type: ALL + possible_keys: FIX_demoId_ID + key: NULL + key_len: NULL + ref: NULL + rows: 1 + filtered: 100.00 + Extra: NULL + *************************** 2. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: eq_ref + possible_keys: PRIMARY + key: PRIMARY + key_len: 4 + ref: springdemo.demo_details.demoId + rows: 1 + filtered: 100.00 + Extra: NULL + 2 rows in set, 1 warning (0.00 sec) + + ``` + +- ref + + 此类型通常出现在多表的 join 查询,针对于非唯一或非主键索引,或者是使用了**最左前缀**规则索引的查询。 + + ``` + + mysql> EXPLAIN select * from demo where name='tommy'\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: ref + possible_keys: IX_name + key: IX_name + key_len: 258 + ref: const + rows: 1 + filtered: 100.00 + Extra: NULL + 1 row in set, 1 warning (0.00 sec) + + ``` + +- range + + 表示使用索引范围查询,通过索引字段范围获取表中部分数据记录,这个类型通常出现在 =、 、>、>=、 <、 <=、 IS NULL、 <=>、 BETWEEN、 IN 操作中。 + 当 type 是 range 时,那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo where id between 1 and 2\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: range + possible_keys: PRIMARY + key: PRIMARY + key_len: 4 + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Using where + 1 row in set, 1 warning (0.00 sec) + + ``` + +- index + + 表示全索引扫描(full index scan),和 ALL 类型类似,只不过 ALL 类型是全表扫描,而 index 类型则仅仅扫描所有的索引,而不扫描数据。 + index 类型通常出现在:所要查询的数据直接在索引树中就可以获取到,而不需要扫描数据。当是这种情况时,Extra 字段 会显示 Using index。 + demo 如下: + ``` + + mysql> EXPLAIN select name from demo\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: index + possible_keys: NULL + key: IX_name + key_len: 258 + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Using index + 1 row in set, 1 warning (0.00 sec) + + ``` + +- ALL + + 表示全表扫描,这个类型的查询是性能最差的查询之一。通常来说,我们的查询不应该出现 ALL 类型的查询,因为这样的查询在数据量大的情况下,对数据库的性能是巨大的灾难。 + 如一个查询是 ALL 类型查询,那么一般来说可以对相应的字段添加索引来避免。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: ALL + possible_keys: NULL + key: NULL + key_len: NULL + ref: NULL + rows: 2 + filtered: 100.00 + Extra: NULL + 1 row in set, 1 warning (0.00 sec) + + ``` + +#### type 类型的性能比较 +通常来说, 不同的 type 类型的性能关系如下: +`ALL < index < range < index_merge < ref < eq_ref < const < system` + +ALL 类型因为是全表扫描,因此在相同的查询条件下,它是速度最慢的。 +而 index 类型的查询虽然不是全表扫描,但是它扫描了所有的索引,因此比 ALL 类型的稍快。 +后面的几种类型都是利用了索引来查询数据,因此可以过滤部分或大部分数据,因此查询效率就比较高了。 + +#### possible_keys +`possible_key` 表示 MySQL 在查询时,可能使用到的索引。即使有些索引出现在 possible_key 中,但是并不表示此索引一定会被 MySQL 使用到。MySQL 在查询时具体使用到那些索引,与 key 和你写的 SQL 有关。 + +#### key +此字段表示 MySQL 在当前查询时所真正会使用到的索引。 + +#### key_len +表示查询优化器使用了索引的字节数。这个字段可以评估组合索引是否完全被使用,或只有最左部分字段被使用到。 +key_len 的计算规则如下: + +- 字符串 + - char(n):n 字节长度 + - varchar(n):如果是 utf8 编码,则是 3n + 2字节;如果是 utf8mb4 编码,则是 4n + 2 字节。 + +- 数值类型 + - TINYINT: 1字节 + - SMALLINT: 2字节 + - MEDIUMINT: 3字节 + - INT: 4字节 + - BIGINT: 8字节 + +- 时间类型 + - DATE:3字节 + - TIMESTAMP:4字节 + - DATETIME:8字节 + +- 字段属性 + NULL 属性 占用一个字节。如果一个字段是 NOT NULL 的,则没有此属性。 + +#### rows +rows 也是一个重要的字段。MySQL 查询优化器根据统计信息,估算 SQL 要查找到结果集需要扫描读取的数据行数。这个值非常直观显示 SQL 的效率好坏,原则上 rows 越少越好。 + +#### Extra +EXPLAIN 中的很多额外的信息会在 Extra 字段显示,常见的有以下几种内容: +- Using filesort + + 当 Extra 中有 Using filesort 时,表示 MySQL 需额外的排序操作,不能通过索引顺序达到排序效果。一般有 Using filesort,都建议优化去掉,因为这样的查询 CPU 资源消耗大。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo order by name\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: ALL + possible_keys: NULL + key: NULL + key_len: NULL + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Using filesort + 1 row in set, 1 warning (0.00 sec) + + ``` + +- Using index + + **覆盖索引扫描**,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。 + demo 如下: + ``` + + mysql> EXPLAIN select * from demo order by id desc\G; + *************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: demo + partitions: NULL + type: index + possible_keys: NULL + key: PRIMARY + key_len: 4 + ref: NULL + rows: 2 + filtered: 100.00 + Extra: Backward index scan + 1 row in set, 1 warning (0.00 sec) + + ``` + +- Using temporary + + 查询有使用临时表,一般出现于排序,分组和多表 join 的情况,查询效率不高,建议优化。 + +- Using where + + 列数据是从仅仅使用了索引中的信息而没有读取实际行的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示 MySQL 服务器将在存储引擎检索行后再进行过滤。 + +### 优化数据访问 +#### 减少返回数据的数量 +- 只返回必要的列:最好不要使用 SELECT * 语句。 +- 只返回必要的行:使用 LIMIT 语句来限制返回的数据。 + +#### 减少服务器端扫描的行数 +- 最有效的方式是使用索引来覆盖查询。 +- 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。 + +### 重构查询方式 +#### 切分大查询 +一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 + +```SQL + +DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 12 MONTH); +rows_affected = 0 +do { + rows_affected = do_query( + "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 12 MONTH) LIMIT 5000") +} while rows_affected> 0 + +``` + + +#### 分解大连接查询 +将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有: +- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 +- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。 +- 减少锁竞争。(select *** for update) +- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。 + +## MySQL事物隔离级别与锁总结 锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是 MySQL 在服务器层和存储引擎层的并发控制。 +### MySQL 事物隔离级别 +说到 MySQL 的锁,先来了解一下 MySQL 的事物隔离级别。 + +#### 事务的四个重要特性 --- ACID 特性 +- 原子性(Atomicity) + + 事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。 + +- 一致性(Consistency) + + 指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。 + +- 隔离性(Isolation) + + 要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行结果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自完整的数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。 **Tips:MySQL 通过锁机制来保证事务的隔离性**。 + +- 持久性(Durability) + 事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS(关系型数据库管理系统,例如 Oracle、MySQL 等)本身发生的故障。**Tips:MySQL 使用 redo log 来保证事务的持久性**。 + +#### 事务的四种隔离级别 +在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。 + +| 隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read)| +| :----: | :----: | :----: | :----: | +| 未提交读(Read Uncommitted) | 可能 | 可能 | 可能 | +| 已提交读(Read Committed) | 不可能 | 可能 | 可能 | +| 可重复读(Repeated Read) | 不可能 | 不可能 | 可能 | +| 可串行化(Serializable) | 不可能 | 不可能 | 不可能 | + +- 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。 +- 已提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。 +- 可重复读(Repeated Read):在同一个事务内的查询都是事务开始时刻一致的,InnoDB 默认级别。在 SQL 标准中,该隔离级别消除了不可重复读,但是还存在幻读。 +- 可串行化(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。 + +通过上述描述可以看出,Read Uncommitted 这种级别,数据库一般都不会用,而且任何操作都不会加锁。 + +#### MySQL 事务级别详解 +以下信息都是针对 MySQL 8.0 版本进行测试。 + +新建一张测试表 demo,SQL 如下: + +```sql + +CREATE TABLE `demo` ( + `ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo name', + `author` varchar(64) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'demo author', + PRIMARY KEY (`ID`), + KEY `IX_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +``` + +`查看 MySQL 当前事务级别`:select @@session.transaction_isolation; + +**未提交读(Read Uncommitted)** + +该隔离级别的事务会读到其它未提交事务的数据,此现象也称之为**脏读**。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'READ-UNCOMMITTED'; + +insert into demo(name, author) values('name1', 'tommy'); + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Read Uncommitted,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务,将 ID 为 1 对应的 name1 的记录更新为 name2。 + +> begin; + update demo set name = 'name2' where id = 1; + select * from demo; -- 此时看到一条 name 为 name2 的记录 + +- 登录 MySQL 终端 2,开启一个事务后查看表中的数据。 + +>use demo; + begin; + select * from demo; -- 此时看到一条 name 为 name2 的记录 + + +最后一步读取到了 MySQL 终端 1 中未提交的事务(没有 commit 提交动作),即产生了**脏读**,大部分业务场景都不允许**脏读**出现,但是此隔离级别下数据库的并发是最好的。由于会出现**脏读**,所以这种隔离级别一般数据库都不会使用。 + + +**已提交读(Read Committed)** + +一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重复读问题,Oracle 和 SQL Server 的默认隔离级别,但是不是 MySQL 的默认隔离级别。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'READ-COMMITTED'; + +insert into demo(name, author) values('name1', 'tommy'); + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Read Committed,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务,将 ID 为 1 对应的 name1 的记录更新为 name2。 + +> begin; + update demo set name = 'name2' where id = 1; + select * from demo; -- 此时看到一条 name 为 name2 的记录 + +- 登录 MySQL 终端 2,开启一个事务后查看表中的数据。 + +> use demo; + begin; + select * from demo; -- 此时看到一条 name 为 name1 的记录 + +- 切换 MySQL 终端 1,提交事务。 + +> commit; + +- 切换 MySQL 终端 2。 + +> select * from test; -- 此时看到一条 name 为 name2 的记录 + +MySQL 终端 2 在开启了一个事务之后,在第一次读取 demo 表(此时 MySQL 终端 1 的事务还未提交)时 name 的值为 'name1',在第二次读取 demo 表(此时 MySQL 终端 1 的事务已经提交)时 name 列的值 'name1' 已经变为 'name2',说明在此隔离级别下只能读取到已提交的事务。 + + +**可重复读(Repeated Read)** + +该隔离级别是 MySQL 默认的隔离级别,在同一个事务里,select 的结果是事务开始时时间点的状态,因此,同样的 select 操作读到的结果会是一致的,但是,会有**幻读**现象。MySQL 的 InnoDB 引擎可以通过 next-key locks(行锁) 机制来避免**幻读**。使用行锁来避免**幻读**会在后续锁的介绍中进行解释。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'REPEATABLE-READ'; + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Repeated Read,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务。 + +> begin; + select * from demo; -- 无记录 + +- 登录 MySQL 终端 2,开启一个事务后查看表中的数据。 + +> begin; + select * from demo; -- 无记录 + +- 切换 MySQL 终端 1,提交事务。 + +> insert into demo(id, name, author) values(1, 'name1', 'tommy'); + commit; + +- 切换 MySQL 终端 2。 + +> select * from demo; --此时查询还是无记录 + + 以上可以证明,在该隔离级别下已经读取不到别的已提交的事务,如果想看到 MySQL 终端 1 提交的事务,在 MySQL 终端 2 将当前事务提交后再次查询就可以读取到 MySQL 终端 1 提交的事务。我们接着实验,看看在该隔离级别下是否会存在别的问题。 + +- 此时接着在 MySQL 终端 2 插入一条数据。 + +> insert into demo(id, name, author) values(1, 'name1', 'tommy'); Duplicate entry '1' for key 'PRIMARY',主键冲突。 + + 这时你肯定会有疑问,明明在上一步没有数据,为什么在这里会报错呢?其实这就是该隔离级别下可能产生的问题,MySQL 称之为幻读。注意我在这里强调的是 MySQL 数据库,Oracle 数据库对于幻读的定义可能有所不同。 + + +**可串行化(Serializable)** + +在该隔离级别下事务都是串行顺序执行的,MySQL 数据库的 InnoDB 引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。 + +准备数据: + +```sql + +SET @@session.transaction_isolation = 'SERIALIZABLE'; + +``` + +- 准备两个终端,MySQL 终端 1 和 MySQL 终端 2,再准备一张测试表 demo,写入一条测试数据并调整隔离级别为 Serializable,任意一个终端执行即可。 + +- 登录 MySQL 终端 1,开启一个事务,并写入一条数据。 + +> begin; + insert into demo(id, name, author) values(1, 'name1', 'tommy'); + +- 登录 MySQL 终端 2,开启一个事务。 + +> begin; + select * from test; -- 此时会一直卡住 + +- 立马切换到 MySQL 终端 1,提交事务。 + +> commit; + +一旦事务提交,MySQL 终端 2 会立马返回 ID 为 1 的记录,否则会一直卡住,直到超时,其中超时参数是由 innodb_lock_wait_timeout 控制。该隔离级别的数据库并发能力最弱,因为每条 select 语句都会加锁。 + ### 锁机制 -#### 共享锁和排它锁 +InnoDB 实现了两种类型的行级锁: +**共享锁(也称为 S 锁)**:允许事务读取一行数据。 +可以使用 SQL 语句 select * from tableName where ... lock in share mode; 手动加 S 锁。 + +**独占锁(也称为 X 锁)**:允许事务删除或更新一行数据。 +可以使用 SQL 语句 select * from tableName where ... for update; 手动加 X 锁。 + +S 锁和 S 锁是兼容的,X 锁和其它锁都不兼容,举个例子,事务 T1 获取了一个行 r1 的 S 锁,另外事务 T2 可以立即获得行 r1 的 S 锁,此时 T1 和 T2 共同获得行 r1 的 S 锁,此种情况称为锁兼容,但是另外一个事务 T2 此时如果想获得行 r1 的 X 锁,则必须等待 T1 对行 r 锁的释放,此种情况也成为锁冲突。 + +为了实现多粒度的锁机制,InnoDB 还有两种内部使用的意向锁,由 InnoDB 自动添加,且都是表级别的锁。 + +**意向共享锁(IS)**:事务即将给表中的各个行设置共享锁,事务给数据行加 S 锁前必须获得该表的 IS 锁。 +**意向排他锁(IX)**:事务即将给表中的各个行设置排他锁,事务给数据行加 X 锁前必须获得该表 IX 锁。 + +**意向锁解决的问题**:如果下一个事务试图在该表级别上应用共享和排它锁,则会受到由第一个任务控制的表级别意向锁的阻塞。下一个事务在其锁定该表前不必检查各个页或行锁,而只需检查该表上的意向锁。 + +#### 表级意向锁和行级锁的兼容互斥性 +| 锁类型 | X | IX | S | IS | +| :----: | :----: | :----: | :----: | :----: | +| X | 互斥 | 互斥 | 互斥 | 互斥 | +| IX | 互斥 | 兼容 | 互斥 | 兼容 | +| S | 互斥 | 互斥 | 兼容 | 兼容 | +| IS | 互斥 | 兼容 | 兼容 | 兼容 | + #### 锁粒度 @@ -15,3 +601,105 @@ MySQL 是我们日常开发中用到的最多的关系型数据库,我们需 #### MylSAM 表级锁模式 #### MylSAM 加表锁方法 + +### 死锁案例分析 +- show variables like 'innodb_deadlock_detect'; +- show status like 'table_locks%'; +- show status like 'innodb_row_lock%'; +- show engine innodb status; + +## 主从复制 +- 数据分布 +- 负载平衡(Load Balancing) +- 备份 +- 高可用性(High Availability)和容错 + +### 相关命令 +- show status 查看整个 mysql 状态,这个命令也可以看到 Seconds_Behind_Master。 +- show master status 查看主库信息 +- show slave status 查看从库信息 + + 查看参数 Seconds_Behind_Master 来看主从是否延迟。 + - 0:该值为零,是我们极为渴望看到的情况,表示主从复制良好,可以认为lag不存在。 + - 正值:表示主从已经出现延时,数字越大表示从库落后主库越多。 + - 负值:几乎很少见,我只是听一些资深的DBA说见过,其实,这是一个BUG值,该参数是不支持负值的,也就是不应该出现。 + +- show processlist 查看数据库线程列表信息 + +### 原理 + +#### 基于语句的复制 +在 MySQL5.0 及之前的版本只支持基于语句的复制。基于语句复制的模式下,主库会记录那些造成数据更改的事件,当备库读取并重放这些事件时,备库只是把主库上执行过的 SQL 再执行一遍。 + +**优点** +- 实现简单。 +- 二进制日志里的事件更加的紧凑。(全部是需要执行的 SQL 语句) + +**缺点** +- 执行语句的时间不同。(机器的 CPU 和内存可能很不一样) +- 还有一些动态数据,比如 `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`,还有一些使用函数的语句,比如包含 `CURRENT_USER` 的语句。 +- 更新是串行的。(需要考虑锁带来的性能消耗) + +#### 基于行的复制 +在 MySQL5.1 开始支持基于行的复制,这种方式会将实际的数据记录到二进制日志中。 + +**优点** +- 可以正确的复制每一行。(不存在基于语句的复制出现的那种问题) + +- 可以更高效地复制数据。(备库不用重放 MySQL 的事件,这个也是针对具体的 SQL,有的 SQL 可以提高效率,有的确会降低效率。) + ```sql + insert into tab1 (col1, col2, sum_col3) select col1, col2, sum(col1, col2) from tab2 group by col1, col2; # 基于行的复制只需要把插入结果记录下来 + + update tab3 set col1 = 0; // 基于行的复制就要在二进制中记录全表的数据 + ``` +- 有利于数据的恢复 + +**缺点** +- 无法判断数据库做了什么,因为不知道执行的 SQL。 +- 针对上述全表数据更新的时候,效率会很低。 + +#### 复制文件 +- mysql-bin.index + + 该文件是 MySQL 用来识别具体的二进制 binlog 文件;该文件记录磁盘上 binlog 文件。 + +- mysql-relay-bin.index + + 中继日志的索引文件。跟 mysql-bin.index 作用类似。 + +- master.info + + 保存备库连接到主库所需要的信息,格式为纯文本。这个文件以文本的方式记录了复制用户的密码。故要注意该文件的权限。 + +- relay-log.info + + 包含当前备库复制的二进制日志和中继日志的坐标(及备库复制到主库的具体位置)。 + + +#### 发送复制事件到其它的备库 +当设置 log_slave_updates 时,你可以让 slave 扮演其它 slave 的 master。此时,slave 把 SQL 线程执行的事件写进行自己的二进制日志(binary log),然后,它的 slave 可以获取这些事件并执行它。 + + + +#### 复制过滤器 +复制过滤可以让你只复制服务器中的一部分数据,有两种复制过滤:在 master 上过滤二进制日志中的事件;在 slave 上过滤中继日志中的事件。 + + + +## 读写分离 +基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。 +读写分离可以提高系统的效率,特别是对于写少读多的系统,使用读写分离可以大大提高系统的效率。这也是从库会有多个的原因,读的时候可以做负载均衡(可以通过主健或者用户 id 等 hash 的方式,也可以使用 Round Robin 轮询算法;负载均衡算法有很多种,这里就不一一列举),让读请求分布到不同的从库上,提高读请求的效率。 + +## 持久化数据分析 + +数据InnoDB到磁盘需要经过 + +- InnoDB buffer pool, Redo log buffer。这个是InnoDB应用系统本身的缓冲。 +- page cache /Buffer cache(可通过o_direct绕过)。这个是vfs层的缓冲。 +- Inode cache/directory buffer。这个也是vfs层的缓冲。需要通过O_SYNC或者fsync()来刷新。 +- Write-Back buffer。(可设置存储控制器参数绕过) +- Disk on-broad buffer。(可通过设置磁盘控制器参数绕过) + + + + diff --git a/architecture/redis.md b/architecture/redis.md index e69de29..bf13fd0 100644 --- a/architecture/redis.md +++ b/architecture/redis.md @@ -0,0 +1,32 @@ +# Redis +Redis 作为一种 KV 缓存服务器,有着极高的性能,相对于 Memcache,Redis 支持更多种数据类型,因此在业界应用广泛。 + +## 性能高的原因 +- 纯内存操作 +- 单线程 +- 高效的数据结构 +- 合理的数据编码 +- 其他方面的优化 + +## 适用场景 +在 Redis 中,常用的 5 种数据结构和应用场景如下: + +- String:缓存、计数器、分布式锁等。 +- List:链表、队列、微博关注人时间轴列表等。 +- Hash:用户信息、Hash 表等。 +- Set:去重、赞、踩、共同好友等。 +- ZSet:访问量排行榜、点击量排行榜等。 + +## 数据结构分析 + +### SDS + +### 字典 + +### 跳跃表 + +### List + +### Set + +### ZSet diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/Duck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/Duck.java new file mode 100644 index 0000000..dc55e99 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/Duck.java @@ -0,0 +1,70 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 鸭子超类(抽象类) + * + * @Author : TommyYang + * @Time : 2021年04月05日 15:57 + * @Software: IntelliJ IDEA + * @File : Duck.java + */ +public abstract class Duck { + + public Duck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + this.flyBehavior = flyBehavior; + this.quackBehavior = quackBehavior; + } + + /** + * 名称 + */ + private String name; + + /** + * 飞行行为 + */ + private FlyBehavior flyBehavior; + + /** + * 呱呱叫行为 + */ + private QuackBehavior quackBehavior; + + /** + * 鸭子的叫声 + */ + public void performQuack() { + this.quackBehavior.quack(); + } + + /** + * 鸭子游泳 + */ + public void swim() { + System.out.println("游泳"); + } + + + /** + * 执行飞行行为 + */ + public void performFly() { + this.flyBehavior.fly(); + } + + /** + * 鸭子外观都不相同,所以设计为抽象方法 + */ + public abstract void display(); + + public void setFlyBehavior(FlyBehavior flyBehavior) { + this.flyBehavior = flyBehavior; + } + + public void setQuackBehavior(QuackBehavior quackBehavior) { + this.quackBehavior = quackBehavior; + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MainExecutor.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MainExecutor.java new file mode 100644 index 0000000..ac5502c --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MainExecutor.java @@ -0,0 +1,43 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.Duck; +import cn.tommyyang.designpatterns.duckgame.RubberDuck; +import cn.tommyyang.designpatterns.duckgame.WoodDuck; +import cn.tommyyang.designpatterns.duckgame.behavior.impl.FlyWithNoWay; +import cn.tommyyang.designpatterns.duckgame.behavior.impl.MuteQuack; +import cn.tommyyang.designpatterns.duckgame.behavior.impl.Squeak; + +/** + * 鸭子游戏执行器 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:00 + * @Software: IntelliJ IDEA + * @File : MainExecutor.java + */ +public class MainExecutor { + + public static void main(String[] args) { + + // 实现不能飞行的橡皮鸭 + RubberDuck rubberDuck = new RubberDuck(new FlyWithNoWay(), new Squeak()); + execute(rubberDuck); + + // 实现不能飞也不能叫的橡皮鸭 + WoodDuck woodDuck = new WoodDuck(new FlyWithNoWay(), new MuteQuack()); + execute(woodDuck); + + // 后续各种鸭子实现,根据业务方的需求,设置其行为即可,行为可扩展,可复用 + // 这就是"策略模式",各种策略实现好,组合使用即可 + } + + + /** + * 鸭子游戏开始运行 + */ + private static void execute(Duck duck) { + duck.display(); + duck.performQuack(); + duck.performFly(); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MallardDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MallardDuck.java new file mode 100644 index 0000000..0bc89f2 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/MallardDuck.java @@ -0,0 +1,29 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 野鸭类 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:00 + * @Software: IntelliJ IDEA + * @File : MallardDuck.java + */ +public class MallardDuck extends Duck { + + /** + * 构造器 + */ + public MallardDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("绿头"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RedheadDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RedheadDuck.java new file mode 100644 index 0000000..999f6b2 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RedheadDuck.java @@ -0,0 +1,28 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 红头鸭 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:02 + * @Software: IntelliJ IDEA + * @File : RedheadDuck.java + */ +public class RedheadDuck extends Duck { + /** + * 构造器 + */ + public RedheadDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("红头"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RubberDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RubberDuck.java new file mode 100644 index 0000000..08b9ed1 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/RubberDuck.java @@ -0,0 +1,28 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * 橡皮鸭 + * + * @Author : TommyYang + * @Time : 2021年04月05日 16:17 + * @Software: IntelliJ IDEA + * @File : RubberDuck.java + */ +public class RubberDuck extends Duck { + /** + * 构造器 + */ + public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 具体实现鸭子外观 + */ + public void display() { + System.out.println("橡皮鸭"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/WoodDuck.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/WoodDuck.java new file mode 100644 index 0000000..e620fef --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/WoodDuck.java @@ -0,0 +1,26 @@ +package cn.tommyyang.designpatterns.duckgame; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月10日 22:23 + * @Software: IntelliJ IDEA + * @File : WoodDuck.java + */ +public class WoodDuck extends Duck { + /** + * 构造器 + */ + public WoodDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { + super(flyBehavior, quackBehavior); + } + + /** + * 木头鸭外观 + */ + public void display() { + System.out.println("木头鸭"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/FlyBehavior.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/FlyBehavior.java new file mode 100644 index 0000000..28f878d --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/FlyBehavior.java @@ -0,0 +1,18 @@ +package cn.tommyyang.designpatterns.duckgame.behavior; + +/** + * 鸭子飞行行为接口 + * + * @Author : TommyYang + * @Time : 2021年04月06日 00:10 + * @Software: IntelliJ IDEA + * @File : FlyBehavior.java + */ +public interface FlyBehavior { + + /** + * 定义飞行方法 + */ + void fly(); + +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/QuackBehavior.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/QuackBehavior.java new file mode 100644 index 0000000..c1880c7 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/QuackBehavior.java @@ -0,0 +1,18 @@ +package cn.tommyyang.designpatterns.duckgame.behavior; + +/** + * 鸭子呱呱叫行为接口 + * + * @Author : TommyYang + * @Time : 2021年04月06日 00:10 + * @Software: IntelliJ IDEA + * @File : QuackBehavior.java + */ +public interface QuackBehavior { + + /** + * 定义呱呱叫方法 + */ + void quack(); + +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithNoWay.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithNoWay.java new file mode 100644 index 0000000..35280ca --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithNoWay.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; + +/** + * 不能飞行行为定义 + * + * @Author : TommyYang + * @Time : 2021年04月10日 22:16 + * @Software: IntelliJ IDEA + * @File : FlyWithNoWay.java + */ +public class FlyWithNoWay implements FlyBehavior { + /** + * 不能飞行行为实现 + */ + @Override + public void fly() { + System.out.println("no way to fly"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWay.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWay.java new file mode 100644 index 0000000..fcf0bd8 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWay.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; + +/** + * 实现飞行方法 + * + * @Author : TommyYang + * @Time : 2021年04月07日 23:23 + * @Software: IntelliJ IDEA + * @File : FlyWithWay.java + */ +public class FlyWithWay implements FlyBehavior { + /** + * 实现飞行行为 + */ + @Override + public void fly() { + System.out.println("fly with way"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWings.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWings.java new file mode 100644 index 0000000..2918389 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/FlyWithWings.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.FlyBehavior; + +/** + * 通过翅膀飞行实现类 + * + * @Author : TommyYang + * @Time : 2021年04月07日 23:21 + * @Software: IntelliJ IDEA + * @File : FlyWithWings.java + */ +public class FlyWithWings implements FlyBehavior { + /** + * 实现飞行行为 + */ + @Override + public void fly() { + System.out.println("fly with wings"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/MuteQuack.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/MuteQuack.java new file mode 100644 index 0000000..0e6d65c --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/MuteQuack.java @@ -0,0 +1,21 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月09日 22:31 + * @Software: IntelliJ IDEA + * @File : MuteQuack.java + */ +public class MuteQuack implements QuackBehavior { + /** + * 实现 quack 方式 + * 不能叫 + */ + @Override + public void quack() { + // nothing + System.out.println("no quack"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Quack.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Quack.java new file mode 100644 index 0000000..1ce977d --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Quack.java @@ -0,0 +1,19 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月09日 22:29 + * @Software: IntelliJ IDEA + * @File : Quack.java + */ +public class Quack implements QuackBehavior { + /** + * 实现 quack 方式 + */ + @Override + public void quack() { + System.out.println("呱呱叫"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Squeak.java b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Squeak.java new file mode 100644 index 0000000..010ef06 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/duckgame/behavior/impl/Squeak.java @@ -0,0 +1,19 @@ +package cn.tommyyang.designpatterns.duckgame.behavior.impl; + +import cn.tommyyang.designpatterns.duckgame.behavior.QuackBehavior; + +/** + * @Author : TommyYang + * @Time : 2021年04月09日 22:30 + * @Software: IntelliJ IDEA + * @File : Squeak.java + */ +public class Squeak implements QuackBehavior { + /** + * 实现 quack 方式 + */ + @Override + public void quack() { + System.out.println("吱吱叫"); + } +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/observable/WeatherData.java b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/WeatherData.java new file mode 100644 index 0000000..35ae682 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/WeatherData.java @@ -0,0 +1,56 @@ +package cn.tommyyang.designpatterns.observable; + +import cn.tommyyang.designpatterns.observable.display.CurrentConditionsDisplay; + +/** + * 观察者模式 + * WeatherData 类 + * + * @Author : TommyYang + * @Time : 2021年04月18日 15:51 + * @Software: IntelliJ IDEA + * @File : WeatherData.java + */ +public class WeatherData { + /** + * 温度 + */ + private float temperature; + + /** + * 湿度 + */ + private float humidity; + + /** + * 气压 + */ + private float pressure; + + public float getTemperature() { + return this.temperature; + } + + public float getHumidity() { + return this.humidity; + } + + public float getPressure() { + return pressure; + } + + /** + * 一旦气象测量更新,此方法会被调用 + */ + public void measurementsChanged() { + float temp = getTemperature(); + float humidity = getHumidity(); + float pressure = getPressure(); + + // 布告板数据更新 + // 针对具体的实现编程,导致后续增删布告板时必须修改程序 + CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); + currentConditionsDisplay.update(temp, humidity, pressure); + } + +} diff --git a/architecture/src/main/java/cn/tommyyang/designpatterns/observable/display/CurrentConditionsDisplay.java b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/display/CurrentConditionsDisplay.java new file mode 100644 index 0000000..f10a615 --- /dev/null +++ b/architecture/src/main/java/cn/tommyyang/designpatterns/observable/display/CurrentConditionsDisplay.java @@ -0,0 +1,15 @@ +package cn.tommyyang.designpatterns.observable.display; + +/** + * @Author : TommyYang + * @Time : 2021年04月18日 16:00 + * @Software: IntelliJ IDEA + * @File : CurrentConditionsDisplay.java + */ +public class CurrentConditionsDisplay { + + public void update(float temp, float humidity, float pressure) { + + } + +} diff --git a/architecture/uml.md b/architecture/uml.md new file mode 100644 index 0000000..60fc393 --- /dev/null +++ b/architecture/uml.md @@ -0,0 +1,57 @@ +# UML 图总结 +## UML 类图 +> 描述类、接口、协作及他们之间的关系的图。显示系统中类的静态结构。 + +### 有什么作用? +描述软件系统的静态结构 +- 对系统的词汇建模 +- 对简单协作建模 +- 对逻辑数据库模式建模 + +### 类图说明 +> 类名为斜体则为抽象类类方法为斜体则为抽象方法第一行:类名称第二行:类属性第三行:类方法类/属性/方法说明: '-' 表示私有(private) '#'表示保护(protected)'+'表示公开(public) + + + +### 依赖关系 +> `虚线箭头`表示。 + + + + +### 继承关系 +> `实线空心三角形箭头`表示。 + + + +### 实现关系 +> `虚线空心三角形箭头`表示。 + + + +### 关联关系 +> `实线`表示。 + + + +### 组合关系 +> `实线黑色菱形箭头`表示。 + + + +### 聚合关系 +> `实线空心菱形箭头`表示。 + + + +## UML 对象图 +> 显示了某一时刻的一组对象及它们之间的关系。对象图可被看作是类图的实例,用来表达各个对象在某一时刻的状态。 + + + +### 对象图说明 +> stu 实例名称,Student 所属类。第一行:对象名称第二行:实例属性具体值 + +- stu:Student 标准表示法 +- :Student 匿名表示法 +- stu 省略类名表示法 \ No newline at end of file diff --git a/bigdata/hadoop.md b/bigdata/hadoop.md index 55bc9c2..607ec19 100644 --- a/bigdata/hadoop.md +++ b/bigdata/hadoop.md @@ -8,6 +8,19 @@ ## HDFS ## MapReduce +MapReduce是一种用于大规模数据处理的编程模型和计算框架。它被广泛应用于分布式系统中,旨在提供高效、可扩展和可靠的数据处理解决方案。 + +MapReduce的核心思想是将大规模数据集分成小的数据块,然后并行处理这些数据块。这个过程包括两个主要阶段:Map阶段和Reduce阶段。 + +在Map阶段,数据集被划分成多个小的输入块,每个输入块由一个Map任务处理。Map任务将输入块转换成键值对的集合。键值对的生成是通过应用用户自定义的Map函数来实现的。这个函数将输入数据转换成一个或多个键值对,其中键表示数据的特定属性,值则包含与该键相关联的信息。 + +在Reduce阶段,Map任务生成的键值对集合被分组和排序,然后传递给Reduce任务进行处理。Reduce任务的数量通常与计算集群中的处理节点数量相匹配。Reduce任务将相同键的键值对进行合并、计算和聚合操作,生成最终的输出结果。Reduce函数也是用户自定义的,根据需要执行各种操作,例如求和、计数、平均值等。 + +MapReduce的优点在于它的并行性和可伸缩性。通过将数据划分成小块并将其分发给多个处理节点进行并行处理,MapReduce可以有效地处理大规模数据集,提高处理速度和性能。此外,它提供了自动处理故障和容错的机制,保证了计算的可靠性。 + +MapReduce在分布式系统中得到了广泛应用。它是Apache Hadoop框架的核心组件之一,被用于处理大数据和进行复杂的数据分析任务。MapReduce还可以在其他领域中发挥作用,如搜索引擎、机器学习和图形处理等。 + +总而言之,MapReduce是一种强大的编程模型和计算框架,通过将大规模数据集划分成小块并在分布式环境中并行处理,提供了高效、可扩展和可靠的数据处理解决方案。它在大数据领域的应用前景广阔,并在多个领域中发挥着重要的作用。 ## Yarn @@ -21,4 +34,4 @@ 5. MapRedeuce ApplicationMaster 启动之后,立马向 Resource manager 注册,并为 MapReduce 申请资源。 6. MapReduce ApplicationMaster 申请到容器之后立马和 NodeManager 通信,将用户的 MapReduce 程序分发到对应的 Container 中运行,运行的是 Map 进行和 Reduce 进程。 7. Map 和 Reduce 进程执行期间与 MapReduce ApplicationMaster 进行通信,汇报自身的运行状态,如果运行结束,MapReduce ApplicationMaster ApplicationMaster 会向 ResourceManager 注销并释放所有的容器资源。 -8. 最后返回 Reduce 的所有结果。 \ No newline at end of file +8. 最后返回 Reduce 的所有结果。 diff --git a/bigdata/kafka_1.md b/bigdata/kafka_1.md index 31e341d..f67511b 100644 --- a/bigdata/kafka_1.md +++ b/bigdata/kafka_1.md @@ -1,6 +1,6 @@ ## kafka的几个重要概念 -- `Broker`:消息中间件处理结点,一个 Kafka 节点就是一个 broker,多个 broker 可以组成一个 Kafka 集群; +- `Broker`:消息中间件处理节点,一个 Kafka 节点就是一个 broker,多个 broker 可以组成一个 Kafka 集群; - `Topic`:一类消息,例如 note impression 日志、 click 日志等都可以以 topic 的形式存在,Kafka 集群能够同时负责多个 topic 的分发; - `Partition`:topic 物理上的分组,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队; - `segment`:每个 partition 又由多个segment file组成; @@ -12,7 +12,7 @@ kafka 的 message 是以 topic 为基本单位,不同 topic 之间是相互独 同一个 topic 的不同 partition 可以分布在不同的 broker 上,这正是分布式的设计。 partition 是以文件夹的形式存储在 broker 机器上。 topic 与 partition 的关系如下: - + ## Partition中的文件 ### segment file @@ -23,17 +23,17 @@ topic 与 partition 的关系如下: 2. 自0.10.0.1开始的kafka segment file组成:多了一部分,还有 .timeindex 索引文件,基于时间的索引文件;目前支持的时间戳类型有两种: CreateTime 和 LogAppendTime 前者表示 producer 创建这条消息的时间;后者表示 broker 接收到这条消息的时间(严格来说,是 leader broker 将这条消息写入到 log 的时间) 3. segment file 命名规则:partition 的第一个 segment 从0开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset, ofsset 的数值最大为64位(long 类型),20位数字字符长度,没有数字用0填充。如下图所示: 老版本 segment file 结构: - + 新版本 segment file 结构: - + 4. 关于 segment file 中 index 索引文件与 data 文件对应关系图,这里我们借用网上的一个图片,如下所示: - + 5. segment的索引文件中存储着大量的元数据,数据文件中存储着大量消息,索引文件中的元数据指向对应数据文件中的 message 的物理偏移地址。以索引文件中的6,1407为例,在数据文件中表示第6个 message(在全局 partition 表示第368775个 message),以及该消息的物理偏移地址为1407。 6. Kafka message 结构如下图: - + `注`:.index文件的第一个数是 message 相对 offset,后面的数字是该消息的物理偏移地址。 @@ -44,7 +44,7 @@ topic 与 partition 的关系如下: - 查找某个 offset 的时候,是顺序查找。想想,如果文件很大的话,查找的效率就会很低;这个是要解决的。 2. Kakfa 是如何解决上述问题的呢 -通过上述分析发现,如果 Kafka 只有一会文件的话,插入新数据的效率是没问题的;只是在查找的时候,效率很低。 +通过上述分析发现,如果 Kafka 只有一个文件的话,插入新数据的效率是没问题的;只是在查找的时候,效率很低。 `解决办法` @@ -65,7 +65,7 @@ topic 与 partition 的关系如下: - 相对 offset: 由于数据文件分段以后,除了第一个数据以外,每个数据文件的起始 offset 不为0,相对 offset 表示 该 message 相对于其所属数据文件中最小的 offset 的大小,即在每个文件中都是从1开始。 然后查找具体 offset 的时候, -只需要通过二分查找找到具体的在哪个文件中,然后在用 offset - 文件名对应的数值, 即可确定在文件中的相对 offset。 +只需要通过二分查找找到具体的在哪个文件中,然后再用 offset - 文件名对应的数值, 即可确定在文件中的相对 offset。 存储相对 offset 的好处是*可以减小索引文件占用的空间*。 - position: 表示该条 message 在数据文件中的绝对位置, 只要打开数据文件并移动文件指针到这个 position 就可以 diff --git a/bigdata/pom.xml b/bigdata/pom.xml index 443e492..862fc11 100644 --- a/bigdata/pom.xml +++ b/bigdata/pom.xml @@ -16,7 +16,7 @@ org.apache.hive hive-exec - 3.0.0 + 3.1.1 org.apache.zookeeper diff --git a/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java b/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java index 34f3ae8..abc8c96 100644 --- a/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java +++ b/bigdata/src/main/java/cn/tommyyang/bigfile/BigFile.java @@ -6,16 +6,17 @@ /** * @author TommyYang on 2019年04月09日 + * + * 使用说明 + * run cmd: java -cp bigdata-1.0-SNAPSHOT.jar cn.tommyyang.bigfile.BigFile */ - -//run cmd: java -cp bigdata-1.0-SNAPSHOT.jar cn.tommyyang.bigfile.BigFile public class BigFile { public static void main(String[] args) throws IOException { //BigFileTool.readContent(args[0], args[1]); FileService fileService = new FileService("./smallfiles/sink-0.txt"); fileService.countWords(); - //System.out.println(fileService.getTreeMap()); + System.out.println(fileService.getTreeMap()); System.out.println(fileService.getTreeMap().firstEntry().getKey() + ":" + fileService.getTreeMap().firstEntry().getValue()); } diff --git a/bigdata/src/main/java/cn/tommyyang/hive/udf/ContainsString.java b/bigdata/src/main/java/cn/tommyyang/hive/udf/ContainsString.java new file mode 100644 index 0000000..4e6ea8a --- /dev/null +++ b/bigdata/src/main/java/cn/tommyyang/hive/udf/ContainsString.java @@ -0,0 +1,31 @@ +package cn.tommyyang.hive.udf; + +import org.apache.hadoop.hive.ql.exec.UDFArgumentException; +import org.apache.hadoop.hive.ql.metadata.HiveException; +import org.apache.hadoop.hive.ql.udf.generic.GenericUDF; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; + +/** + * @Author : TommyYang + * @Time : 2019年09月19日 15:22 + * @Software: IntelliJ IDEA + * @File : ContainsString.java + */ +public class ContainsString extends GenericUDF { + + @Override + public ObjectInspector initialize(ObjectInspector[] objectInspectors) throws UDFArgumentException { + return null; + } + + @Override + public Object evaluate(DeferredObject[] deferredObjects) throws HiveException { + return null; + } + + @Override + public String getDisplayString(String[] strings) { + return null; + } + +} diff --git a/books/README.md b/books/README.md index dd27b6a..3c2a045 100644 --- a/books/README.md +++ b/books/README.md @@ -18,8 +18,18 @@ | Java8 实战.pdf | [下载](https://pan.baidu.com/s/1y5m1hgn9cJT7pyE5qI9UuQ) | og1w | | 深入理解 Java 虚拟机第二版.pdf | [下载](https://pan.baidu.com/s/1mFE-B03b5Dwuz3_CJgHA7g) | c1j1 | +## 算法 +| 书名 | 链接 | 提取码 | +| :----: | :----: | :----: | +| 编程珠玑(第 2 版).pdf | [下载](https://pan.baidu.com/s/174v9WNIHBFpAxmy9uCYX-g) | 5698 | + ## 架构 | 书名 | 链接 | 提取码 | | :----: | :----: | :----: | | 微服务设计.pdf | [下载](https://pan.baidu.com/s/1uaCQhagPU1ElrC2zuBePdA) | bfir | -| 大型网站技术架构.pdf | [下载](https://pan.baidu.com/s/1F_CpOz-0sspDjGktG1z3Yw) | wf1l | \ No newline at end of file +| 大型网站技术架构.pdf | [下载](https://pan.baidu.com/s/1F_CpOz-0sspDjGktG1z3Yw) | wf1l | + +## 人工智能 +### 读书笔记 +#### A/B测试 + \ No newline at end of file diff --git a/codeinterview/README.md b/codeinterview/README.md index e231672..ec8289b 100644 --- a/codeinterview/README.md +++ b/codeinterview/README.md @@ -3,4 +3,8 @@ ## 面试中遇到的具体编码问题 -Question1:[多线程(使用线程池)按顺序打印 0-100;(至少3个线程交替按顺序打印)](order_print_num.md) +- [多线程(使用线程池)按顺序打印 0-100;(至少3个线程交替按顺序打印)](order-print-num.md) +- [爬楼梯问题](dynamic-programming.md#L11) + +## 算法相关 +- [动态规划](dynamic-programming.md) \ No newline at end of file diff --git a/codeinterview/dynamic-programming.md b/codeinterview/dynamic-programming.md new file mode 100644 index 0000000..aa3d083 --- /dev/null +++ b/codeinterview/dynamic-programming.md @@ -0,0 +1,81 @@ +# 动态规划(Dynamic Programming) +动态规划其实和分治策略是类似的,也是将一个原问题分解为若干个规模较小的子问题,递归的求解这些子问题,然后合并子问题的解得到原问题的解。 +区别在于这些子问题会有重叠,一个子问题在求解后,可能会再次求解,于是我们想到将这些子问题的**解存储起来**,当下次再次求解这个子问题时,直接拿过来就是。 +其实就是说,动态规划所解决的问题是分治策略所解决问题的一个子集,只是这个子集更适合用动态规划来解决从而得到更小的运行时间。 +**即用动态规划能解决的问题分治策略肯定能解决,只是运行时间长了**。因此,分治策略一般用来解决子问题相互对立的问题,称为标准分治,而动态规划用来解决子问题重叠的问题。 + +将 **动态规划** 的概念关键点抽离出来描述就是这样的: +- 动态规划法试图只解决每个子问题一次。 +- 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 + +## 爬楼梯问题 +下面通过爬楼梯问题来分析使用动态规划的解决问题(该问题也是在现场面试中遇到的)。 + +**问题描述**:爬楼梯,我们每次可以走 1 级阶梯或者 2 级阶梯,那我们走 50(n) 级阶梯总共有多少种走法。 +在现场思考过程中,如果没有使用动态规划的话,那就要用递归的概念去做。 + +### 递归方式 +我们在高中的数学课里面应该学习过规律推理,那我们可以很容易的去推理出来公式: +1 级台阶: 1 种走法(f(1)) +2 级台阶: 2 种走法(f(2)) +3 级台阶: 3 种走法(f(3) = f(2) + f(1)) +4 级台阶: 5 种走法(f(4) = f(3) + f(2)) +5 级台阶: 8 种走法(f(5) = f(4) + f(3)) +... +n 级台阶: f(n) = f(n-1) + f(n-2) **(n> 2)** + +通过以上公式,很容易推导出一下递归代码: + +``` java + +int f(int n) { + if (n == 1) return 1; + if (n == 2) return 2; + return f(n-1) + f(n-2); +} + +``` + +code: + +``` java + +int f(int n) { + if (n == 1) return 1; + if (n == 2) return 2; + // a 保存倒数第二个子状态数据,b 保存倒数第一个子状态数据, temp 保存当前状态的数据 + int a = 1, b = 2; + int temp = a + b; + for (int i = 3; i <= n; i++) { + temp = a + b; + a = b; + b = temp; + } + return temp; +} + +``` + +### 从递归到动态规划 +还是以**爬台阶**为例,如果以递归的方式解决的话,那么这种方法的时间复杂度为O(2^n) + +### 详解动态规划 + +**动态规划**中包含三个重要的概念: +- 最优子结构 +- 边界 +- 状态转移公式 + +在**爬台阶问题**中: + +f(10) = f(9) + f(8) 是最优子结构 +f(1) 与 f(2) 是边界 +f(n) = f(n-1) + f(n-2) 状态转移公式 + +**爬台阶问题**只是动态规划中相对简单的问题,因为它只有一个变化维度,如果涉及多个维度的话,那么问题就变得复杂多了。 + +难点就在于找出**动态规划**中的这三个概念。感觉跟我们高中学习的推理证明题有点像,通过现在的条件推断出结果。 + +## 国王和金矿问题 +### 问题描述 +有一个国家发现了 5 座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是 10 人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿? \ No newline at end of file diff --git a/codeinterview/order_print_num.md b/codeinterview/order-print-num.md similarity index 100% rename from codeinterview/order_print_num.md rename to codeinterview/order-print-num.md diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java b/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java index dc2a9cc..55c7de4 100644 --- a/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/ListNode.java @@ -13,6 +13,11 @@ public ListNode(int val) { this.val = val; } + public ListNode(int val, ListNode next) { + this.val = val; + this.next = next; + } + public ListNode getNext() { return next; } diff --git a/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java b/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java index bd85c99..745cd4d 100644 --- a/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java +++ b/codeinterview/src/main/java/cn/tommyyang/listnode/ReverseLinkedList.java @@ -1,6 +1,10 @@ package cn.tommyyang.listnode; /** + * 链表反转 + * 1、迭代算法 + * 2、递归算法 + * * @Author : TommyYang * @Time : 2019-06-19 18:29 * @Software: IntelliJ IDEA @@ -8,21 +12,74 @@ */ public class ReverseLinkedList { - public ListNode reverse (ListNode head) { - + /** + * 迭代算法反转 + * + * @param head + * @return + */ + public ListNode reverseWithIterate(ListNode head) { ListNode cur = head; ListNode next; - ListNode left = null; + ListNode pre = null; while (cur != null) { next = cur.next; - cur.next = left; - left = cur; + cur.next = pre; + pre = cur; cur = next; } + return pre; + } + + /** + * 递归算法反转 + * + * @param head + * @return + */ + public ListNode reverseWithRecursion(ListNode head) { + if (head == null || head.next == null) { + return head; + } + + ListNode newNode = reverseWithRecursion(head.next); + + head.next.next = head; + head.next = null; + + return newNode; + } + + public static void main(String[] args) { + ListNode node5 = new ListNode(5, null); + ListNode node4 = new ListNode(4, node5); + ListNode node3 = new ListNode(3, node4); + ListNode node2 = new ListNode(2, node3); + ListNode node1 = new ListNode(1, node2); - return left; + print(node1); + ListNode newNode = new ReverseLinkedList().reverseWithRecursion(node1); + + print(newNode); } + /** + * 打印链表 + * + * @param head 链表头 + */ + private static void print(ListNode head) { + while (head != null) { + System.out.print(head.val); + if (head.next != null) { + System.out.print("->"); + } + head = head.next; + } + System.out.println(); + } + + } diff --git a/datastructure/4-threadpool.md b/datastructure/4-threadpool.md deleted file mode 100644 index 576089a..0000000 --- a/datastructure/4-threadpool.md +++ /dev/null @@ -1,54 +0,0 @@ -# 线程池 -线程池,从字面意义上来讲,是指管理一组同构工作线程的资源池。线程是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程(Work Thread)的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。线程池简化了线程的管理工作,为用户开发多线程应用提供了更加方便的 API。 - -**好处** -- 线程池可以重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。 -- 当请求到达时,工作线程通常已经存在,因此不会由于等待线程创建线程而延迟任务的执行,从而提高响应性。 -- 通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保证忙碌状态,同时还可以防止多线程相互竞争资源而使应用程序耗尽内存或失败。 - -## Executor 框架 -采用生产者-消费者模式,提交任务的操作相当于生产者(生产待完成的工作单元),执行任务的线程相当于消费者(执行完这些工作单元)。 - -```java -public interface Executor { - - /** - * - * @param command the runnable task - * @throws RejectedExecutionException if this task cannot be - * accepted for execution - * @throws NullPointerException if command is null - */ - void execute(Runnable command); -} -``` - -虽然 Executor 是个很简单的接口,但它却为灵活且强大的异步执行框架提供了基础,该框架能支持多种不同类型的任务执行策略。它提供了一种标准的方法将任务的提交过程与执行过程解藕开来,并用 Runnable 来表示任务。Executor 的实现还提供了对生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视等机制。 - -## ThreadPoolExecutor -线程池的具体实现类。以下的线程池都是由 ThreadPoolExecutor 来具体实现的。 - -## 分类 -- newSingleThreadPool -- newFixedThreadPool -- newCachedThreadPool -- newScheduledThreadPool -- newWorkStealingPool - -## 具体分析 -### newSingleThreadPool -Executors.newSingleThreadPool() 是一个单线程的 Executor,它创建单个线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool 能确保依照任务在队列中的顺序来串行执行(比如 FIFO、 LIFO、优先级)。 - -### newFixedThreadPool -Executors.newFixedThreadPool() 创建一个固定长度的线程池,每当提交一个任务的时就创建一个线程,直到达到线程池的最大数量,这时,线程池的规模将不在变化(如果某个线程由于发生了未预期的 Exception 而结束,那么线程池会补充一个新的线程)。 - -### newCachedThreadPool -Executors.newCachedThreadPool() 创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。 - -### newScheduledThreadPool -Executors.newScheduledThreadPool() 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer。 - -### newWorkStealingPool -Executors.newWorkStealingPool() 创建了一个工作窃取池,具体地由 ForkJoinPool 构成;具体就是先把大任务 fork 成小任务,然后再把小任务的结果 join 起来,最后得到一个具体的结果。 - -## ThreadPoolExecutor \ No newline at end of file diff --git a/datastructure/README.md b/datastructure/README.md deleted file mode 100644 index c679649..0000000 --- a/datastructure/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 数据结构篇 - -## String、StringBuilder和StringBuffer -| 类名| 描述| 是否可| 线程安全性| -|:---- |:---- |:---- |:---- | -|String |字符串常量 | 不可变类 | 线程安全 | -|StringBuilder |字符串变量 | 可变类 | 线程不安全 | -|StringBuffer |字符串变量 | 可变类 | 线程安全 | - -[详细介绍](stringbuilderandstringbuffer.md) - -## BlockingQueue -[详细介绍](blockingqueueanddeque.md) - -## 线程池相关 -[详细介绍](4-threadpool.md) \ No newline at end of file diff --git "a/img/AB346円265円213円350円257円225円.png" "b/img/AB346円265円213円350円257円225円.png" new file mode 100644 index 0000000..5071b60 Binary files /dev/null and "b/img/AB346円265円213円350円257円225円.png" differ diff --git "a/img/tommy345円255円246円344円271円240円345円275円225円.png" "b/img/tommy345円255円246円344円271円240円345円275円225円.png" new file mode 100644 index 0000000..133cc3d Binary files /dev/null and "b/img/tommy345円255円246円344円271円240円345円275円225円.png" differ diff --git a/io/Multi-Thread-Write_File.md b/io/Multi-Thread-Write-File.md similarity index 97% rename from io/Multi-Thread-Write_File.md rename to io/Multi-Thread-Write-File.md index 7545f9c..07bf223 100644 --- a/io/Multi-Thread-Write_File.md +++ b/io/Multi-Thread-Write-File.md @@ -96,7 +96,7 @@ public class BigFileWriter1 { ``` -经过上述代码改进后,代码效率基本可以了,但是由于对源码不熟悉,如果专业的人看到这段代码,会觉得我们比较业余,因为那个 synchronized 是抢眼且多余的,在专业的人看来会非常的不舒服的。由于 IO 操作是我们日常编程中使用到最多的 API,但是我们确对源码是那么的不熟悉。 +经过上述代码改进后,代码效率基本可以了,但是由于对源码不熟悉,如果专业的人看到这段代码,会觉得我们比较业余,因为那个 synchronized 是抢眼且多余的,在专业的人看来会非常的不舒服的。由于 IO 操作是我们日常编程中使用到最多的 API,但是我们却对源码是那么的不熟悉。 ## JDK 中 Writer 源码分析 我们先来看看 Writer 的构造方法: @@ -136,7 +136,7 @@ protected Writer(Object lock) { ``` -从以上源码可以看出,Writer 里面关键的流部门,都会有 lock 锁进行同步;所以,对于同一个 writer instance 是线程安全的;所以我们写同一个文件的时候使用同一个 writer instance 是线程安全的。也就是说我们使用的 Writer、FileWriter、BufferedWriter 是线程安全的。 +从以上源码可以看出,Writer 里面关键的流部分,都会有 lock 锁进行同步;所以,对于同一个 writer instance 是线程安全的;所以我们写同一个文件的时候使用同一个 writer instance 是线程安全的。也就是说我们使用的 Writer、FileWriter、BufferedWriter 是线程安全的。 具体的 write 方法源码分析如下: @@ -160,7 +160,7 @@ public void write(String str, int off, int len) throws IOException { ``` -在初始话 Writer Instance 的时候,我们会确定一个同步锁对象,所以只要我们使用的是一个 Writer 对象,则可以保证线程安全。 +在初始化 Writer Instance 的时候,我们会确定一个同步锁对象,所以只要我们使用的是一个 Writer 对象,则可以保证线程安全。 ## 改进版本二 通过以上源码分析,我们可以很清楚地改进最优的代码如下: diff --git a/javabase/README.md b/javabase/README.md new file mode 100644 index 0000000..16994e2 --- /dev/null +++ b/javabase/README.md @@ -0,0 +1,103 @@ +# Java 基础知识 + +## 基础 +### Integer类 +```java + +public class Test { + public static void main(String[] args){ + Integer a = new Integer(129); + Integer b = new Integer(129); + + // 1 + System.out.println(a == b); + + + Integer c = new Integer(127); + Integer d = new Integer(127); + // 2 + System.out.println(c == d); + } +} + +``` + +上述代码可以看出1处 输出 false; 2 处输出 true。 +从Integer类源码中,有一个内部类,代码如下: +```java + +private static class IntegerCache { + static final int low = -128; + static final int high; + static final Integer cache[]; + + static { + // high value may be configured by property + int h = 127; + String integerCacheHighPropValue = + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); + if (integerCacheHighPropValue != null) { + try { + int i = parseInt(integerCacheHighPropValue); + i = Math.max(i, 127); + // Maximum array size is Integer.MAX_VALUE + h = Math.min(i, Integer.MAX_VALUE - (-low) -1); + } catch( NumberFormatException nfe) { + // If the property cannot be parsed into an int, ignore it. + } + } + high = h; + + cache = new Integer[(high - low) + 1]; + int j = low; + for(int k = 0; k < cache.length; k++) + cache[k] = new Integer(j++); + + // range [-128, 127] must be interned (JLS7 5.1.7) + assert IntegerCache.high>= 127; + } + + private IntegerCache() {} + } + +``` +从上述源码可以找出出现上述1处、2处结果的原因。 + + +### Calendar类 +> 添加天数:DAY_OF_MONTH、DAY_OF_YEAR、DAY_OF_WEEK、DAY_OF_WEEK_IN_MONTH的区别 + +就单纯的add操作结果都一样,因为都是将日期+1, 不管是月的日期中加1还是年的日期中加1。 +强行解释区别如下: +- `DAY_OF_MONTH` 的主要作用是get(DAY_OF_MONTH),用来获得这一天在是这个月的第多少天。 +- `DAY_OF_YEAR` 的主要作用get(DAY_OF_YEAR),用来获得这一天在是这个年的第多少天。 +- 同样,`DAY_OF_WEEK`,用来获得当前日期是一周的第几天;`DAY_OF_WEEK_IN_MONTH`,用来获取 day 所在的周是这个月的第几周 + +### [String、StringBuilder和StringBuffer](stringbuilderandstringbuffer.md) +| 类名| 描述| 是否可| 线程安全性| +|:---- |:---- |:---- |:---- | +|String |字符串常量 | 不可变类 | 线程安全 | +|StringBuilder |字符串变量 | 可变类 | 线程不安全 | +|StringBuffer |字符串变量 | 可变类 | 线程安全 | + +### [关键字篇](keywords) +- [transient](keywords/transient.md) +- [volatile](keywords/volatile.md) + + +## [异常篇](exception.md) + +## [Java8 篇](java8) + +## :house_with_garden: [数据结构篇](datastructure) +- [BlockingQueue和BlockingDeque](datastructure/blockingqueueanddeque.md) + * Queue + * Deque + * LinkedList + * ArrayBlockingQueue + * LinkedBlockingQueue + * LinkedBlockingDeque + +## 多线程相关 +- [线程池](threadpool.md) + diff --git a/javabase/datastructure/README.md b/javabase/datastructure/README.md new file mode 100644 index 0000000..41f3057 --- /dev/null +++ b/javabase/datastructure/README.md @@ -0,0 +1,5 @@ +# 数据结构篇 + +## [BlockingQueue](blockingqueueanddeque.md) + +## [线程池相关](../threadpool.md) \ No newline at end of file diff --git a/datastructure/blockingqueueanddeque.md b/javabase/datastructure/blockingqueueanddeque.md similarity index 100% rename from datastructure/blockingqueueanddeque.md rename to javabase/datastructure/blockingqueueanddeque.md diff --git a/datastructure/pom.xml b/javabase/datastructure/pom.xml similarity index 90% rename from datastructure/pom.xml rename to javabase/datastructure/pom.xml index b9a6a53..09511d5 100644 --- a/datastructure/pom.xml +++ b/javabase/datastructure/pom.xml @@ -16,8 +16,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.8 - 1.8 + 1.17 + 1.17 diff --git a/datastructure/src/main/java/cn/tommyyang/RunTest.java b/javabase/datastructure/src/main/java/cn/tommyyang/RunTest.java similarity index 74% rename from datastructure/src/main/java/cn/tommyyang/RunTest.java rename to javabase/datastructure/src/main/java/cn/tommyyang/RunTest.java index 88c871e..553b8b2 100644 --- a/datastructure/src/main/java/cn/tommyyang/RunTest.java +++ b/javabase/datastructure/src/main/java/cn/tommyyang/RunTest.java @@ -13,7 +13,14 @@ public class RunTest { public static void main(String[] args) throws ExecutionException, InterruptedException { - runForkJoinPool(); +// runForkJoinPool(); + + Thread t = new Thread(()-> + System.out.println("aa") + ); + + t.start(); + t.start(); } private static void runForkJoinPool() throws ExecutionException, InterruptedException { @@ -27,9 +34,12 @@ private static void runForkJoinPool() throws ExecutionException, InterruptedExce Future r1 = forkJoinPool.submit(countTask); System.out.println(r1.get()); - + ExecutorService service = Executors.newWorkStealingPool(1); + service.execute(() -> System.out.println("aa")); +// ThreadPoolExecutor // Math // LinkedBlockingQueue // HashMap + } } diff --git a/datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java b/javabase/datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java similarity index 100% rename from datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java rename to javabase/datastructure/src/main/java/cn/tommyyang/forkjoinpool/CountTask.java diff --git a/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java b/javabase/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java similarity index 100% rename from datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java rename to javabase/datastructure/src/main/java/cn/tommyyang/queue/ArrayBlockingQueueDemo.java diff --git a/datastructure/src/main/java/cn/tommyyang/string/StringTest.java b/javabase/datastructure/src/main/java/cn/tommyyang/string/StringTest.java similarity index 100% rename from datastructure/src/main/java/cn/tommyyang/string/StringTest.java rename to javabase/datastructure/src/main/java/cn/tommyyang/string/StringTest.java diff --git a/javabase/exception.md b/javabase/exception.md new file mode 100644 index 0000000..47cbae6 --- /dev/null +++ b/javabase/exception.md @@ -0,0 +1,157 @@ +# 异常介绍 +异常就是有异于常态,和正常情况不一样,有错误出现。在java中,阻止当前方法或作用域的情况,称之为异常。 + +## 异常分类 + + +## Exception具体实现 +在系统开发中,平时经常需要使用的两种异常,一种是需要检查(checked)的,一种是不需要检查(unchecked)的。 +那为什么需要两种异常呢? +- 用来区分告警的优先级。系统异常优先级高,因为说明系统服务、代码存在问题。 + - 业务异常,是已知的,因为其他客观因素导致的,比如用户输入的身份证格式有问题、用户购买商品时金额不足等。 + - 系统异常,是未知的,不知道啥时候会发生,如果发生了说明系统本身或者系统上下游存在问题,需要立马告警出来,让相关开发者感知到;以便发现问题和后续优化问题。比如:系统上下游服务抖动、请求超时、请求参数存在问题等。 +- 使代码更清洁,该处理(checked)的异常内部处理掉,无法处理(unchecked)的异常告警出来。 + +往往对于开发者来说,比较难区分,何为系统系统,何为业务异常。其中系统异常是unchecked的,业务异常是checked。 + +### RuntimeException +`RuntimeException`是在Java虚拟机的正常操作期间可以抛出的那些异常的超类,是Exception的子类,是Exception中unchecked子类的超类。 +比如系统上下游抖动、请求超时等,是允许在系统运行期间抛出的,所以该类异常应该继承自`RuntimeException`;且无需检查(unchecked)。 +所以系统异常应该继承自`RuntimeException`。 + +开发时具体实现: + +```java + +/** + * @Author : TommyYang + * @Time : 2021年03月27日 12:40 + * @Software: IntelliJ IDEA + * @File : SystemException.java + */ +public class SystemException extends RuntimeException { + + private String code; + + public SystemException(String code) { + this.code = code; + } + + public SystemException(String message, String code) { + super(message); + this.code = code; + } + + @Override + public String toString() { + return "SystemException{" + + "code='" + code + '\'' + + '}'; + } + +} + +``` + +### Exception +异常类和任何不是RuntimeException的子类的子类都是检查异常。 检查的异常需要在方法或构造函数的throws子句中声明,如果它们可以通过执行方法或构造函数抛出,并在方法或构造函数边界之外传播。 +比如用户输入的身份证格式有问题、用户购买商品时金额不足等,这些是在开发系统的时候,就会已经的会出现这样的问题,这类异常是应该内部处理(checked)掉,而不应该告警出来。 +所以业务异常应该继承自`Exception`,且需要检查(checked)。 + +开发时具体实现: + +```java + +/** + * @Author : TommyYang + * @Time : 2021年03月27日 12:40 + * @Software: IntelliJ IDEA + * @File : BusinessException.java + */ +public class BusinessException extends Exception { + + private String code; + + public BusinessException(String code) { + this.code = code; + } + + public BusinessException(String message, String code) { + super(message); + this.code = code; + } + + @Override + public String toString() { + return "BusinessException{" + + "code='" + code + '\'' + + '}'; + } +} + +``` + +### 测试 + +```java + +/** + * @Author : TommyYang + * @Time : 2021年03月27日 13:19 + * @Software: IntelliJ IDEA + * @File : ExceptionTest.java + */ +public class ExceptionTest { + + /** + * 测试checked异常 + */ + @Test + public void testException() { + try { + throwsBusinessException(); + } catch (BusinessException e) { + System.out.println("这是一个业务异常,内部处理掉" + e.toString()); + } + } + + /** + * 测试unchecked异常 + */ + @Test + public void testRuntimeException() { + throwsSystemException(); + } + + /** + * 抛出业务异常(checked) + * 所以需要throw出去,让外部调用方感知到,这是一个checked的异常,是已经问题 + */ + private void throwsBusinessException() throws BusinessException { + throw new BusinessException("403"); + } + + /** + * 抛出系统异常(unchecked) + */ + private void throwsSystemException() { + throw new SystemException("400"); + } + +} + +``` + +## Error介绍 +`Error`表示严重的问题,合理的应用程序不应该试图捕获。 大多数这样的错误是异常情况。 ThreadDeath错误虽然是"正常"的条件,但也是Error一个子类,因为大多数应用程序不应该试图抓住它。 + +`Error`是由虚拟机生成并抛出,大多数错误与代码开发者所执行的操作无关。 +常见的Error,比如Java虚拟机运行错误(VirtualMachineError);当JVM执行操作所需的内存资源不够时,将出现OutOfMemoryError;当这些异常发生时,JVM一般会选择线程终止。 +还有部分Error是发生在虚拟机试图执行应用时,比如类定义错误(NoClassDefFoundError)、链接错误(LinkageError);这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。 +对于合理设计的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。 +在Java中,错误通常是使用Error的子类描述。 + + + + + diff --git a/javabase/img/exception_category.png b/javabase/img/exception_category.png new file mode 100644 index 0000000..e04bca1 Binary files /dev/null and b/javabase/img/exception_category.png differ diff --git a/java8/README.md b/javabase/java8/README.md similarity index 100% rename from java8/README.md rename to javabase/java8/README.md diff --git a/java8/pom.xml b/javabase/java8/pom.xml similarity index 95% rename from java8/pom.xml rename to javabase/java8/pom.xml index 894ed41..b0be4ac 100644 --- a/java8/pom.xml +++ b/javabase/java8/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - java8 + javabase diff --git a/java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java rename to javabase/java8/src/main/java/cn/tommyyang/functioninterface/ApplePredicate.java diff --git a/java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java rename to javabase/java8/src/main/java/cn/tommyyang/functioninterface/DefaultInterface.java diff --git a/java8/src/main/java/cn/tommyyang/functioninterface/Demo.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Demo.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/functioninterface/Demo.java rename to javabase/java8/src/main/java/cn/tommyyang/functioninterface/Demo.java diff --git a/java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java rename to javabase/java8/src/main/java/cn/tommyyang/functioninterface/Predicate.java diff --git a/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java similarity index 96% rename from java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java rename to javabase/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java index c4832ad..81a340d 100644 --- a/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java +++ b/javabase/java8/src/main/java/cn/tommyyang/functioninterface/ProgressDemo.java @@ -6,6 +6,7 @@ import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * @Author : TommyYang @@ -38,6 +39,8 @@ public static List processApple(List appleList, ApplePredicate p) } public static void main(String[] args) { + IntStream.range(0, 10).forEach(System.out::println); +// Collection // test1(); @@ -45,7 +48,7 @@ public static void main(String[] args) { // test3(); - test3_(); +// test3_(); // test4(); @@ -106,6 +109,7 @@ public static void test4() { apples.stream().filter(predicate1).forEach(System.out::println); apples.stream().filter(predicate1.negate()).forEach(System.out::println); + } // group by diff --git a/java8/src/main/java/cn/tommyyang/model/Apple.java b/javabase/java8/src/main/java/cn/tommyyang/model/Apple.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/model/Apple.java rename to javabase/java8/src/main/java/cn/tommyyang/model/Apple.java diff --git a/java8/src/main/java/cn/tommyyang/model/Fruit.java b/javabase/java8/src/main/java/cn/tommyyang/model/Fruit.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/model/Fruit.java rename to javabase/java8/src/main/java/cn/tommyyang/model/Fruit.java diff --git a/java8/src/main/java/cn/tommyyang/model/Pear.java b/javabase/java8/src/main/java/cn/tommyyang/model/Pear.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/model/Pear.java rename to javabase/java8/src/main/java/cn/tommyyang/model/Pear.java diff --git a/java8/src/main/java/cn/tommyyang/streamapi/CountService.java b/javabase/java8/src/main/java/cn/tommyyang/streamapi/CountService.java similarity index 100% rename from java8/src/main/java/cn/tommyyang/streamapi/CountService.java rename to javabase/java8/src/main/java/cn/tommyyang/streamapi/CountService.java diff --git a/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java b/javabase/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java similarity index 90% rename from java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java rename to javabase/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java index 8a76c20..5a1e8fd 100644 --- a/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java +++ b/javabase/java8/src/main/java/cn/tommyyang/streamapi/StreamAPI.java @@ -26,8 +26,9 @@ public class StreamAPI { } public static void main(String[] args) { - testDisplay(); +// testDisplay(); // testAdd(); + streamDisplay(); } public static void testAdd() { @@ -83,14 +84,16 @@ public static void defaultAdd() { public static void streamDisplay() { // stream.forEach(System.out::println); - stream.forEach(item -> { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(item); - }); +// stream.forEach(item -> { +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// System.out.println(item); +// }); + + addItems.forEach(System.out::println); System.out.println("stream display end ------"); } diff --git a/keywords/README.md b/javabase/keywords/README.md similarity index 100% rename from keywords/README.md rename to javabase/keywords/README.md diff --git a/keywords/pom.xml b/javabase/keywords/pom.xml similarity index 100% rename from keywords/pom.xml rename to javabase/keywords/pom.xml diff --git a/keywords/res/transient.png b/javabase/keywords/res/transient.png similarity index 100% rename from keywords/res/transient.png rename to javabase/keywords/res/transient.png diff --git a/keywords/res/transienttest.txt b/javabase/keywords/res/transienttest.txt similarity index 100% rename from keywords/res/transienttest.txt rename to javabase/keywords/res/transienttest.txt diff --git a/keywords/src/main/java/transientkey/TransientTest.java b/javabase/keywords/src/main/java/transientkey/TransientTest.java similarity index 100% rename from keywords/src/main/java/transientkey/TransientTest.java rename to javabase/keywords/src/main/java/transientkey/TransientTest.java diff --git a/keywords/src/main/java/volatilekey/ordering/AThread.java b/javabase/keywords/src/main/java/volatilekey/ordering/AThread.java similarity index 100% rename from keywords/src/main/java/volatilekey/ordering/AThread.java rename to javabase/keywords/src/main/java/volatilekey/ordering/AThread.java diff --git a/keywords/src/main/java/volatilekey/ordering/BThread.java b/javabase/keywords/src/main/java/volatilekey/ordering/BThread.java similarity index 100% rename from keywords/src/main/java/volatilekey/ordering/BThread.java rename to javabase/keywords/src/main/java/volatilekey/ordering/BThread.java diff --git a/keywords/src/main/java/volatilekey/ordering/Model.java b/javabase/keywords/src/main/java/volatilekey/ordering/Model.java similarity index 84% rename from keywords/src/main/java/volatilekey/ordering/Model.java rename to javabase/keywords/src/main/java/volatilekey/ordering/Model.java index 88efd0e..3299d02 100644 --- a/keywords/src/main/java/volatilekey/ordering/Model.java +++ b/javabase/keywords/src/main/java/volatilekey/ordering/Model.java @@ -5,7 +5,7 @@ */ public class Model { - public static int a = 10; + public volatile static int a; public static int b = 1; public static int c = 2; public static int d = 3; diff --git a/keywords/src/main/java/volatilekey/ordering/RunTest.java b/javabase/keywords/src/main/java/volatilekey/ordering/RunTest.java similarity index 93% rename from keywords/src/main/java/volatilekey/ordering/RunTest.java rename to javabase/keywords/src/main/java/volatilekey/ordering/RunTest.java index 9759d2d..7bfb73d 100644 --- a/keywords/src/main/java/volatilekey/ordering/RunTest.java +++ b/javabase/keywords/src/main/java/volatilekey/ordering/RunTest.java @@ -8,12 +8,12 @@ public class RunTest { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10000; i++){ + Model.a = 10; Thread at = new Thread(new AThread()); Thread bt = new Thread(new BThread()); at.start(); - Thread.sleep(1); bt.start(); bt.join(); diff --git a/keywords/src/main/java/volatilekey/unsafe/Counter.java b/javabase/keywords/src/main/java/volatilekey/unsafe/Counter.java similarity index 100% rename from keywords/src/main/java/volatilekey/unsafe/Counter.java rename to javabase/keywords/src/main/java/volatilekey/unsafe/Counter.java diff --git a/keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java b/javabase/keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java similarity index 100% rename from keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java rename to javabase/keywords/src/main/java/volatilekey/unsafe/OwnUnSafe.java diff --git a/keywords/src/main/java/volatilekey/unsafe/Runtest.java b/javabase/keywords/src/main/java/volatilekey/unsafe/Runtest.java similarity index 100% rename from keywords/src/main/java/volatilekey/unsafe/Runtest.java rename to javabase/keywords/src/main/java/volatilekey/unsafe/Runtest.java diff --git a/keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java b/javabase/keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java similarity index 100% rename from keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java rename to javabase/keywords/src/main/java/volatilekey/unsafe/ThreadCommunicate.java diff --git a/keywords/src/main/java/volatilekey/visibility/RunTest.java b/javabase/keywords/src/main/java/volatilekey/visibility/RunTest.java similarity index 100% rename from keywords/src/main/java/volatilekey/visibility/RunTest.java rename to javabase/keywords/src/main/java/volatilekey/visibility/RunTest.java diff --git a/keywords/transient.md b/javabase/keywords/transient.md similarity index 100% rename from keywords/transient.md rename to javabase/keywords/transient.md diff --git a/keywords/volatile.md b/javabase/keywords/volatile.md similarity index 100% rename from keywords/volatile.md rename to javabase/keywords/volatile.md diff --git a/datastructure/stringbuilderandstringbuffer.md b/javabase/stringbuilderandstringbuffer.md similarity index 100% rename from datastructure/stringbuilderandstringbuffer.md rename to javabase/stringbuilderandstringbuffer.md diff --git a/javabase/threadpool.md b/javabase/threadpool.md new file mode 100644 index 0000000..d2f8b77 --- /dev/null +++ b/javabase/threadpool.md @@ -0,0 +1,199 @@ +# 线程池 +线程池,从字面意义上来讲,是指管理一组同构工作线程的资源池。线程是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程(Work Thread)的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。线程池简化了线程的管理工作,为用户开发多线程应用提供了更加方便的 API。 + +**好处** +- 线程池可以重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。 +- 当请求到达时,工作线程通常已经存在,因此不会由于等待线程创建线程而延迟任务的执行,从而提高响应性。 +- 通过适当调整线程池的大小,可以创建足够多的线程以便使处理器保证忙碌状态,同时还可以防止多线程相互竞争资源而使应用程序耗尽内存或失败。 + +## Executor 框架 +采用生产者-消费者模式,提交任务的操作相当于生产者(生产待完成的工作单元),执行任务的线程相当于消费者(执行完这些工作单元)。 + +```java +public interface Executor { + + /** + * + * @param command the runnable task + * @throws RejectedExecutionException if this task cannot be + * accepted for execution + * @throws NullPointerException if command is null + */ + void execute(Runnable command); +} +``` + +虽然 Executor 是个很简单的接口,但它却为灵活且强大的异步执行框架提供了基础,该框架能支持多种不同类型的任务执行策略。它提供了一种标准的方法将任务的提交过程与执行过程解藕开来,并用 Runnable 来表示任务。Executor 的实现还提供了对生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视等机制。 + +## ThreadPoolExecutor +线程池的具体实现类。以下的线程池都是由 ThreadPoolExecutor 来具体实现的。 + +## 分类 +- newSingleThreadPool +- newFixedThreadPool +- newCachedThreadPool +- newScheduledThreadPool +- newWorkStealingPool + +## 具体分析 +### newSingleThreadPool +Executors.newSingleThreadPool() 是一个单线程的 Executor,它创建单个线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool 能确保依照任务在队列中的顺序来串行执行(比如 FIFO、 LIFO、优先级)。 + +### newFixedThreadPool +Executors.newFixedThreadPool() 创建一个固定长度的线程池,每当提交一个任务的时就创建一个线程,直到达到线程池的最大数量,这时,线程池的规模将不在变化(如果某个线程由于发生了未预期的 Exception 而结束,那么线程池会补充一个新的线程)。 + +### newCachedThreadPool +Executors.newCachedThreadPool() 创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。 + +### newScheduledThreadPool +Executors.newScheduledThreadPool() 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer。 + +### newWorkStealingPool +Executors.newWorkStealingPool() 创建了一个工作窃取池,具体地由 ForkJoinPool 构成;具体就是先把大任务 fork 成小任务,然后再把小任务的结果 join 起来,最后得到一个具体的结果。 + +### ThreadPoolExecutor 源码分析 + +#### 构造方法 + +```java + +public class ThreadPoolExecutor extends AbstractExecutorService { + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + Executors.defaultThreadFactory(), defaultHandler); + } + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + threadFactory, defaultHandler); + } + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + RejectedExecutionHandler handler) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + Executors.defaultThreadFactory(), handler); + } + + public ThreadPoolExecutor(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler) { + if (corePoolSize < 0 || + maximumPoolSize <= 0 || + maximumPoolSize < corePoolSize || + keepAliveTime < 0) + throw new IllegalArgumentException(); + if (workQueue == null || threadFactory == null || handler == null) + throw new NullPointerException(); + this.acc = System.getSecurityManager() == null ? + null : + AccessController.getContext(); + this.corePoolSize = corePoolSize; + this.maximumPoolSize = maximumPoolSize; + this.workQueue = workQueue; + this.keepAliveTime = unit.toNanos(keepAliveTime); + this.threadFactory = threadFactory; + this.handler = handler; + } + +} + + +``` + +#### 7 个参数 +- corePoolSize + + 核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存 keepAliveTime 限制。除非将 allowCoreThreadTimeOut 设置为 true。 + +- maximumPoolSize + + 线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的 LinkedBlockingDeque 时,这个值无效。 + +- keepAliveTime + + 非核心线程的闲置超时时间,超过这个时间就会被回收。 + +- unit + + 指定 keepAliveTime 的单位,如 TimeUnit.SECONDS。当将 allowCoreThreadTimeOut 设置为 true 时对 corePoolSize 生效。 + +- workQueue + + 线程池中的任务队列。常用的有三种队列,`SynchronousQueue`,`LinkedBlockingDeque`,`ArrayBlockingQueue`。 + +- threadFactory + + 线程工厂,提供创建新线程的功能。ThreadFactory 是一个接口,只有一个方法,如下: + + ```java + + public interface ThreadFactory { + Thread newThread(Runnable r); + } + + ``` + + 通过线程工厂可以对线程的一些属性进行定制。 + + 默认工厂源码: + + ```java + + static class DefaultThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + DefaultThreadFactory() { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + namePrefix = "pool-" + + poolNumber.getAndIncrement() + + "-thread-"; + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, + namePrefix + threadNumber.getAndIncrement(), + 0); + if (t.isDaemon()) + t.setDaemon(false); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + } + + ``` + +- handler + + `RejectedExecutionHandler` 也是一个接口,同时也只有一个方法,如下: + ```java + + public interface RejectedExecutionHandler { + void rejectedExecution(Runnable var1, ThreadPoolExecutor var2); + } + + ``` + 当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用 RejectedExecutionHandler 的 rejectedExecution 方法。 \ No newline at end of file diff --git a/jvm/README.md b/jvm/README.md index a78b5dd..00b7071 100644 --- a/jvm/README.md +++ b/jvm/README.md @@ -63,9 +63,9 @@ Class 文件中除了有类的版本、字段、方法、接口等描述信息 内存的分配担保就好比我们现在使用支付宝里面的花呗,如果我们信誉很好,在 98% 的情况下都能按时偿还,于是支付宝会默认我们会在下一月也能按时按量的偿还我们的预支,只需要有一个担保人能保证如果我下次不能还款时,可以帮助你还钱,那支付宝就认为我们预支花呗是没有风险的。内存的分配担保也一样,如果另外一块 Survivor 空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。具体怎么分配担保会在后续分析。 ### 标记-整理算法 -复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的时,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对使用的内存中所有对象都 100% 存活的极端情况,所以在老年代一般不能直接选用复制算法。 +复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对使用的内存中所有对象都 100% 存活的极端情况,所以在老年代一般不能直接选用复制算法。 -根据老年代存活时间较长的特点,有人提出了另一种"标记-整理"(Mark-Compact)的算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象想一端移动,然后直接清理掉边界以外的内存。 +根据老年代存活时间较长的特点,有人提出了另一种"标记-整理"(Mark-Compact)的算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉边界以外的内存。 ### 分代收集算法 这种算法没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的算法。 diff --git a/jvm/garbage_collectors.md b/jvm/garbage_collectors.md index f7ad73d..313552a 100644 --- a/jvm/garbage_collectors.md +++ b/jvm/garbage_collectors.md @@ -13,7 +13,7 @@ - G1 收集器 按收集器的回收对象以及收集器之间的联系,可以参照下图一: - + 通过上图可以发现,Serial、ParNew、 Parallel Scavenge 是用来收集新生代的收集器,CMS、 Serial Old、Parallel Old 是用来收集老年代的收集器;然后 CMS 是不能和 Parallel Scavenge 结合起来用的,CMS 是可以和 Serial Old 联合起来收集老年代的。 @@ -22,19 +22,19 @@ Serial 收集器是最基本、发展历史最悠久的收集器,(在JDK 1.3.1 之前)是虚拟机新生代收集的唯一选择。通过名字,大家也可以发现该收集器是一个单线程的收集器,但它的"单线程"的意义并不仅仅说明它只会使用一个 CPU 或 一条收集线程去完成垃圾收集工作,更重要的是在它进行收集时,必须暂停其它所有的工作线程,直到它收集结束。"Stop The World"就是该收集器的一个特点,这项工作是由虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,这对很多应用来说都是难以接受的。 Serial 收集器的工作过程如下图二: - + -对于 "Stop The World" 带给用户的不良体验,虚拟机的设计者们表示完全理解,但是表示非常委屈:"你妈妈在给你打扫房间的时候,肯定也会让你处在一个不会干扰她打扫的状态,比如坐在椅子上不动,或者到房间外面去,不然她一边打扫,你一边乱仍垃圾,这房间还能打扫完?"这确实是一个合情合理的矛盾,虽然垃圾收集这项工作听起来和打扫房间属于一个性质的,但实际上肯定还要比打扫房间复杂很多很多的。 +对于 "Stop The World" 带给用户的不良体验,虚拟机的设计者们表示完全理解,但是表示非常委屈:"你妈妈在给你打扫房间的时候,肯定也会让你处在一个不会干扰她打扫的状态,比如坐在椅子上不动,或者到房间外面去,不然她一边打扫,你一边乱扔垃圾,这房间还能打扫完?"这确实是一个合情合理的矛盾,虽然垃圾收集这项工作听起来和打扫房间属于一个性质的,但实际上肯定还要比打扫房间复杂很多很多的。 垃圾收集器也是处在一个不断发展的过程中,由 Serial(串行)收集器到 Parallel(并行)收集器,再到 Concurrent Mark Sweep(CMS)乃至 Garbage First(G1)收集器,我们看到一个个越来越优秀(也越来越复杂)的收集器出现,用户线程的停顿时间在不断缩短。 -Serial 收集器是虚拟机运行在 Client 模式下的默认新生代收集器。原因是由于在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十甚至一两百兆的新生代(仅仅是新声代的内存,桌面程序大多数情况下不会再大了),停顿时间完全可以控制在几十毫秒最多一百毫秒之内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial 收集器对于运行在 Client 模式下的虚拟机来说是一个很好的选择。 +Serial 收集器是虚拟机运行在 Client 模式下的默认新生代收集器。原因是由于在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十甚至一两百兆的新生代(仅仅是新生代的内存,桌面程序大多数情况下不会再大了),停顿时间完全可以控制在几十毫秒最多一百毫秒之内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial 收集器对于运行在 Client 模式下的虚拟机来说是一个很好的选择。 ## ParNew 收集器 ParNew 收集器其实就是 Serial 收集器的多线程版本。其它的与 Serial 收集器基本相同,也是 Stop The World,在实现上,这两种收集器也共用了相当多的代码。 ParNew 收集器的工作过程如下图三: - + ParNew 收集器是许多运行在 Server 模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因,除了 Serial 收集器外,目前只有它能与 CMS 收集器配合工作。 CMS 收集器是在 JDK 1.5时期推出的一款真正意义上的并发(Concurrent)收集器,它第一次实现了让垃圾收集器与用户线程(基本上)同时工作,用前面的例子来说就是做到了在你妈妈打扫房间的同时你还能一边往地上扔垃圾。 diff --git a/keywords/src/main/java/transientkey/RunTest.java b/keywords/src/main/java/transientkey/RunTest.java deleted file mode 100644 index daa8e1e..0000000 --- a/keywords/src/main/java/transientkey/RunTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package transientkey; - -import java.io.*; - -/** - * Created by TommyYang on 2018/3/19. - */ -public class RunTest { - - public static void main(String[] args) throws IOException, ClassNotFoundException { - //write Serializable object to file - TransientTest test = new TransientTest(); - FileOutputStream fileOutputStream = new FileOutputStream("KeyWords/res/transienttest.txt"); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); - objectOutputStream.writeObject(test); - objectOutputStream.flush(); - objectOutputStream.close(); - - //get Serializable object from file - ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("KeyWords/res/transienttest.txt")); - TransientTest transientTest = (TransientTest) objectInputStream.readObject(); - System.out.println("a=" + transientTest.getA() + "\t" +"b=" + transientTest.getB()); - } - -} diff --git a/lock/README.md b/lock/README.md index 5ef845c..1a32b14 100644 --- a/lock/README.md +++ b/lock/README.md @@ -10,7 +10,7 @@ 悲观锁(Pessimistic Lock)就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次都在拿数据的时候上锁。这样别人拿数据的时候就会被挡住,直到悲观锁释放,想获取数据的线程再去获取锁,然后再获取数据。 -**悲观锁阻塞事务,乐观锁回滚重试**,它们个有优缺点,没有好坏之分,只有适应场景的不同区别。比如:乐观锁适合用于写比较少的情况下,即冲突真的很少发生的场景,这样可以省去锁的开销,加大了系统的整个吞吐量。但是如果经常产生冲突,上层应用会不断的进行重试,这样反而降低了性能,所以这种场景悲观锁比较合适。 +**悲观锁阻塞事务,乐观锁回滚重试**,它们各有优缺点,没有好坏之分,只有适应场景的不同区别。比如:乐观锁适合用于写比较少的情况下,即冲突真的很少发生的场景,这样可以省去锁的开销,加大了系统的整个吞吐量。但是如果经常产生冲突,上层应用会不断的进行重试,这样反而降低了性能,所以这种场景悲观锁比较合适。 总结:**乐观锁适合写比较少,冲突很少发生的场景;而写多,冲突多的场景适合使用悲观锁**。 @@ -166,7 +166,7 @@ ReentrantLock 内部实现了 FairSync 和 NonfairSync 两个内部类来实现 首先,我们需要理解的是什么是**中断**。 Java 中并没有提供任何可以直接中断线程的方法,只提供了**中断机制**。那么何为**中断机制**呢?线程 A 向线程 B 发出"请你停止运行"的请求,就是调用 Thread.interrupt() 的方法(当然线程 B 本身也可以给自己发送中断请求,即 Thread.currentThread().interrupt()),但线程 B 并不会立即停止运行,而是自行选择在合适的时间点以自己的方式响应中断,也可以直接忽略此中断。也就是说,Java 的**中断不能直接终止线程**,只是设置了状态为响应中断的状态,需要被中断的线程自己决定怎么处理。这就像在读书的时候,老师在晚自习时叫学生自己复习功课,但学生是否复习功课,怎么复习功课则完全取决于学生自己。 -回到锁的分析上来,如果线程 A 持有锁,线程 B 等待持获取该锁。由于线程 A 持有锁的时间过长,线程 B 不想继续等了,我们可以让线程 B 中断自己或者在别的线程里面中断 B,这种就是 **可中段锁**。 +回到锁的分析上来,如果线程 A 持有锁,线程 B 等待持获取该锁。由于线程 A 持有锁的时间过长,线程 B 不想继续等了,我们可以让线程 B 中断自己或者在别的线程里面中断 B,这种就是 **可中断锁**。 在 Java 中, synchronized 锁是**不可中断锁**,而 Lock 的实现类都是 **可中断锁**。从而可以看出 JDK 自己实现的 Lock 锁更加的灵活,这也就是有了 synchronized 锁后,为什么还要实现那么些 Lock 的实现类。 diff --git a/lock/pom.xml b/lock/pom.xml index 2dca5c6..8a56184 100644 --- a/lock/pom.xml +++ b/lock/pom.xml @@ -17,7 +17,19 @@ keywords 1.0-SNAPSHOT - + + org.redisson + redisson + 3.10.7 + + + + + org.apache.zookeeper + zookeeper + 3.4.14 + + \ No newline at end of file diff --git a/lock/src/main/java/cn/tommyyang/distributedlock/RedisDistributedLock.java b/lock/src/main/java/cn/tommyyang/distributedlock/RedisDistributedLock.java new file mode 100644 index 0000000..c5c4fd1 --- /dev/null +++ b/lock/src/main/java/cn/tommyyang/distributedlock/RedisDistributedLock.java @@ -0,0 +1,10 @@ +package cn.tommyyang.distributedlock; + +/** + * @author TommyYang on 2019年05月12日 + */ +public class RedisDistributedLock { + + + +} diff --git a/machinelearning/model.md b/machinelearning/model.md new file mode 100644 index 0000000..6ed8ec6 --- /dev/null +++ b/machinelearning/model.md @@ -0,0 +1,391 @@ +# 相关模型介绍 +# 分类模型 + +## 决策树 +决策树(Decision Tree)算法是一种基本的分类与回归方法,是最经常使用的数据挖掘算法之一。我们这章节只讨论用于分类的决策树。 + +决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是 if-then 规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。 + +决策树学习通常包括 3 个步骤: 特征选择、决策树的生成和决策树的修剪。 + +### 决策树场景 +一个叫做 "二十个问题" 的游戏,游戏的规则很简单: 参与游戏的一方在脑海中想某个事物,其他参与者向他提问,只允许提 20 个问题,问题的答案也只能用对或错回答。问问题的人通过推断分解,逐步缩小待猜测事物的范围,最后得到游戏的答案。 + +**一个邮件分类系统,大致工作流程如下**: + + +- 首先检测发送邮件域名地址。如果地址为 myEmployer.com, 则将其放在分类 "无聊时需要阅读的邮件"中。 +- 如果邮件不是来自这个域名,则检测邮件内容里是否包含单词 "曲棍球" , 如果包含则将邮件归类到 "需要及时处理的朋友邮件"。 +- 如果不包含则将邮件归类到 "无需阅读的垃圾邮件" 。 + +**假如你了解足球,让你预测世界杯 32 个球队哪支球队是冠军**: + + +- 在 1-16 么?在 +- 在 9-16 么?在 +- 在 13-16 么? 在 +- 在 15 -16 么? 在 +- 是 15 么? 不是 + +答案:16 号球队夺冠。 + +`tips` +最多通过 5 次机会就猜测出哪只球队是冠军球队。这个 5 代表的是 5 bit(比特),相当于是 2^5=32,通过 5 bit 可以描述出这个信息量。 +如果是 64 支球队,那么需要 6 bit 来描述这个信息量。 + +### 信息熵(香农熵) +香农通过世界杯预测冠军的问题提出了信息熵,故信息熵也叫香农熵,信息熵用来度量信息量;信息量的度量等于不确定性的多少。 +通过上述问题描述,相信读者已经了解了信息量的比特数和所有可能情况的对数函数 log 有关。(log32 = 5, log64 = 6)。 + +当然,如果是你对足球比较了解,通过历届球队的实力比较了解,以及世界杯参赛球队球星也比较了解。你可能会对一些球队能力的强弱进行分组, +因为你了解的信息量会多一些,所以猜测的不确定性就会小一些,从而你可能不需要 5 次就可以猜中。故香农提出了如下公式来计算信息熵: +H(x) = -(P1 * logP1 + P2 * logP2 + P3 * logP3 + ... + P32 * logP32), +其中P1、P2、P3...P32 分别代表 32 支球队夺冠的概率。 + +### 信息增益 +信息增益 = 信息熵 - 条件熵。 + +条件熵:在你得知一个信息(条件)后,得出结论还需要多少信息量。 + +信息增益越大,说明你得到的这个信息就越重要。 + +### 决策树定义 +分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型: 内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。 + +用决策树对需要测试的实例进行分类: 从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。 + +### 决策树工作原理 +如何构造一个决策树? + +使用 create_branch() 方法,伪代码如下所示: +``` + +def createBranch(): +''' +此处运用了迭代的思想。 感兴趣可以搜索 迭代 recursion, 甚至是 dynamic programing。 +''' + 检测数据集中的所有数据的分类标签是否相同: + If so return 类标签 + Else: + 寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征) + 划分数据集 + 创建分支节点 + for 每个划分的子集 + 调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中 + return 分支节点 + +``` + +### 决策树开发流程 +- 收集数据: 可以使用任何方法。 +- 准备数据: 树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART)。 +- 分析数据: 可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。 +- 训练算法: 构造树的数据结构。 +- 测试算法: 使用训练好的树计算错误率。 +- 使用算法: 此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。 + +### 决策树算法特点 +- 优点: 计算复杂度不高,输出结果易于理解,数据有缺失也能跑,可以处理不相关特征。 +- 缺点: 容易过拟合。 +- 适用数据类型: 数值型和标称型。 + +### 决策树案例分析 +#### 实例一 鱼类非鱼类问题 +决策树如下: + + +##### 项目概述 +根据以下 2 个特征,将动物分成两类: 鱼类和非鱼类。 + +特征: +- 不浮出水面是否可以生存。 +- 是否有脚蹼(du 第三声)。 + +##### 构造数据集 +``` python +# 构造数据集 +def create_data_set(): + """ + :return: data_set 数据集, labels 标签数组 + """ + data_set = [ + [1, 1, 'yes'], + [1, 1, 'yes'], + [1, 0, 'no'], + [0, 1, 'no'], + [0, 1, 'no'], + [0, 0, 'no'] + ] + + labels = ['no surfacing', 'flippers'] + + return data_set, labels +``` + +##### 计算香农熵(经验熵)shannonEnt +```python + +# 计算香农熵(经验熵)shannonEnt +def calc_shannon_ent(data_set): + """ + + :param data_set: 数据集 + :return: shannon_ent 香农熵 + + """ + + # 计算 list 的长度,表示计算参与训练的数据量 + num_entries = len(data_set) + # 计算分类标签 label 出现的次数 + label_counts = {} + + for feat_vec in data_set: + # 存储当前实例的标签存储,即每一行数据的最后一个数据代表的是标签 + current_label = feat_vec[-1] + # 为所有可能的分类创建字典,如果当前值不存在,则扩展字典并将当前值加入字典。每个键值都记录了当前类别出现的次数。 + if current_label not in label_counts.keys(): + label_counts[current_label] = 0 + label_counts[current_label] += 1 + + # 对于 label 标签的占比,求出 label 标签的香农熵 + shannon_ent = 0.0 + for key in label_counts: + # 使用所有类标签的发生频率计算类别出现的概率 + prob = float(label_counts[key]) / num_entries + # 计算香农熵 + shannon_ent -= prob * log(prob, 2) + + return shannon_ent + +``` + +##### 将指定特征值等于 value 的行剩下列作为子数据集 +``` python +# 将指定特征值等于 value 的行剩下列作为子数据集 +def split_data_set(data_set, index, value): + """ + (通过遍历 data_set 数据集,求出 index 对应的 column 列的值为 value 的行) + 就是依据 index 列进行分类,如果index列的数据等于 value 的时候,就要将 index 划分到我们创建的新的数据集中 + + :param data_set: 数据集 待划分的数据集 + :param index: 每一行的 index 列 划分数据集的特征 + :param value: index 列对应的 value 值 需要返回的特征的值 + :return: + ret_data_set 指定特征值等于 value 的行剩下列(不包含值等于该 value 的列)作为子数据集 + """ + ret_data_set = [] + for feat_vec in data_set: + # index 列为 value 的数据集[该数据集需要排除 index 列] + # 判断 index 列的值是否为 value + if feat_vec[index] == value: + # chop out index used for splitting + # [:index]表示前 index 行,即若 index 为 2,就是取 feat_vec 的前 2 行 + reduced_feat_vec = feat_vec[:index] + ''' + 请百度查询一下: extend和append的区别 + music_media.append(object) 向列表中添加一个对象object + music_media.extend(sequence) 把一个序列seq的内容添加到列表中 (跟 += 在list运用类似, music_media += sequence) + 1、使用append的时候,是将object看作一个对象,整体打包添加到music_media对象中。 + 2、使用extend的时候,是将sequence看作一个序列,将这个序列和music_media序列合并,并放在其后面。 + music_media = [] + music_media.extend([1,2,3]) + print music_media + #结果: + #[1, 2, 3] + + music_media.append([4,5,6]) + print music_media + #结果: + #[1, 2, 3, [4, 5, 6]] + + music_media.extend([7,8,9]) + print music_media + #结果: + #[1, 2, 3, [4, 5, 6], 7, 8, 9] + ''' + # 取 index+1 行开始,后面的所有行数据 + reduced_feat_vec.extend(feat_vec[index + 1:]) + + # 收集结果值 index 列为 value 的行[该行需要排除 index 列] + ret_data_set.append(reduced_feat_vec) + return ret_data_set +``` + +##### 选择信息增益最大的特征列 +```python + +def choose_best_feature_to_split(data_set): + """ + :param data_set: 数据集 + :return: + best_feature 最优的特征列 + """ + # 求第一行有多少列的 Feature, 最后一列是 label 列 + num_features = len(data_set[0]) - 1 + # 数据集的原始信息熵 + base_entropy = calc_shannon_ent(data_set) + # 最优的信息增益值, 和最优的 feature 编号 + best_info_gain, best_feature = 0.0, -1 + + # iterate over all the features + for i in range(num_features): + # create a list of all the examples of this feature + # 获取对应的 feature 下的所有数据 + feat_list = [example[i] for example in data_set] + # get a set of unique values + # 获取剔重后的集合,使用set对list数据进行去重 + unique_vals = set(feat_list) + print("unique_vals:", unique_vals) + # 创建一个临时的信息熵 + new_entropy = 0.0 + # 遍历某一列的 value 集合,计算该列的信息熵 + # 遍历当前特征中的所有唯一属性值,对每个唯一属性值划分一次数据集,计算数据集的新熵值,并对所有唯一特征值得到的熵求和。 + for val in unique_vals: + sub_data_set = split_data_set(data_set, i, val) + # 计算概率 + prob = len(sub_data_set) / float(len(data_set)) + print("prob:", prob) + # 计算信息熵 + new_entropy += prob * calc_shannon_ent(sub_data_set) + print("new_entropy:", new_entropy) + # gain[信息增益]: 划分数据集前后的信息变化, 获取信息熵最大的值 + # 信息增益是熵的减少或者是数据无序度的减少。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。 + info_gain: float = base_entropy - new_entropy + print('info_gain=', info_gain, 'best_feature=', i, base_entropy, new_entropy) + if info_gain> best_info_gain: + best_info_gain = info_gain + best_feature = i + return best_feature + +``` + +##### 构建决策树 +``` python + +def create_tree(data_set, labels): + """ + :param data_set: 数据集 + :param labels: label 标签 + :return: 返回决策树 + """ + class_list = [example[-1] for example in data_set] + # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行 + # 第一个停止条件: 所有的类标签完全相同,则直接返回该类标签。 + # count() 函数是统计括号中的值在list中出现的次数 + if class_list.count(class_list[0]) == len(class_list): + return class_list[0] + # 如果数据集只有 1 列,那么最初出现 label 次数最多的一类,作为结果 + # 第二个停止条件: 使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。 + if len(data_set[0]) == 1: + return majority_cnt(class_list) + # 选择最优的列,得到最优列对应的label含义 + best_feat = choose_best_feature_to_split(data_set) + # 获取label的名称 + best_feat_label = labels[best_feat] + # 初始化myTree + my_tree = {best_feat_label: {}} + # 注: labels列表是可变对象,在PYTHON函数中作为参数时传址引用,能够被全局修改 + # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list + del (labels[best_feat]) + # 取出最优列,然后它的branch做分类 + feat_vals = [example[best_feat] for example in data_set] + unique_vals = set(feat_vals) + for val in unique_vals: + # 求出剩余的标签label + sub_labels = labels[:] + # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数create_tree() + my_tree[best_feat_label][val] = create_tree(split_data_set(data_set, best_feat, val), sub_labels) + # print('myTree', val, my_tree) + return my_tree + +``` + +##### 通过输入的特征,预测分类 +``` python + +def classify(input_tree, feat_labels, test_vec): + """ + 给输入的节点,进行分类 + :param input_tree: 决策树模型 + :param feat_labels: feature标签对应的名称 + :param test_vec: 测试输入的数据 + :return: + class_label 分类的结果值,需要映射 label 才能知道名称 + """ + # 获取 tree 的根节点对于的 key 值 + first_str = list(input_tree.keys())[0] + # 通过key得到根节点对应的value + second_dict = input_tree[first_str] + # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类 + feat_index = feat_labels.index(first_str) + # 测试数据,找到根节点对应的label位置,也就知道从输入的数据的第几位来开始分类 + key = test_vec[feat_index] + feat_val = second_dict[key] + print('+++', first_str, 'xxx', second_dict, '---', key, '>>>', feat_val) + # 判断分枝是否结束: 判断 feat_val 是否是 dict 类型 + if isinstance(feat_val, dict): + class_label = classify(feat_val, feat_labels, test_vec) + else: + class_label = feat_val + return class_label + +``` + +##### 选择出现次数最多的一个结果 +``` python + +def majority_cnt(class_list): + """ + 选择出现次数最多的一个结果 + :param class_list: label 列的集合 + :return: best_feature 最优的特征列 + """ + # -----------majorityCnt的第一种方式 start------------------------------------ + class_count = {} + for vote in class_list: + if vote not in class_count.keys(): + class_count[vote] = 0 + class_count[vote] += 1 + # 倒叙排列classCount得到一个字典集合,然后取出第一个就是结果(yes/no),即出现次数最多的结果 + sorted_class_count = sorted(class_count.iteritems(), key=operator.itemgetter(1), reverse=True) + print('sortedClassCount:', sorted_class_count) + return sorted_class_count[0][0] + # -----------majorityCnt的第一种方式 end------------------------------------ + + # # -----------majorityCnt的第二种方式 start------------------------------------ + # major_label = Counter(classList).most_common(1)[0] + # return major_label + # # -----------majorityCnt的第二种方式 end------------------------------------ + +``` + + +## GBDT +GBDT: Gradient Boost Decision Tree。DT-Decision Tree决策树,GB是Gradient Boosting,是一种学习策略,GBDT的含义就是用Gradient Boosting的策略训练出来的DT模型。可以处理二分类问题。 + + +## LightGBM + + +## XGBoost +### 分类树与回归树 +XGBoost是以CART回归树作为基本分类器。 +分类树:分类树的样本输出都是以类别的形式,比如说判断用户会不会购买华为Mate40,判断西瓜是甜还是不甜。 + +回归树:回归树的样本输出是数值的形式,比如给某人发放房屋贷款的数额,给某人发放的红包金额。 + +### Boost介绍 +Boost可以用于回归和分类问题,它每一步会产生一个弱分类器 (如决策树),然后通过加权累加起来变成一个强分类器。比如每一步都会产生一个f(x),F(x)=sum(fi(x)),其实就是一堆分类器通过加权合并成一个强分类器。 + +### 提升树 +首先要明确一点,xgboost 是基于提升树的。 + +什么是提升树,简单说,就是一个模型表现不好,我继续按照原来模型表现不好的那部分训练第二个模型,依次类推。 + +来几个形象的比喻就是: + +做题的时候,第一个人做一遍得到一个分数,第二个人去做第一个人做错的题目,第三个人去做第二个人做错的题目,以此类推,不停的去拟合从而可以使整张试卷分数可以得到100分(极端情况)。 + +把这个比喻替换到模型来说,就是真实值为100,第一个模型预测为90,差10分,第二个模型以10为目标值去训练并预测,预测值为7,差三分,第三个模型以3为目标值去训练并预测,以此类推。 + +## Random Forests diff --git a/machinelearning/recommend_system.md b/machinelearning/recommend_system.md new file mode 100644 index 0000000..d0ebaba --- /dev/null +++ b/machinelearning/recommend_system.md @@ -0,0 +1,53 @@ +# 推荐系统整理 +该篇主要对推荐系统进行一些整理。 + +## 推荐系统涉及到的一些模块 +核心模块有用户、Item(包括文章、视频、商品、音乐、电影等)、用户实时画像、Item 特征等。 +核心服务有召回服务、推荐服务、排序服务等。 + +如下图: + + +比较重要就是要去了解 NLP(自然语言处理),基于 NLP 的特征分析。基于模型的排序服务,比如决策树、FM 模型、FFM 模型、双线性 FFM 模型、DNN、Wide&Deep 等。排序模型个人觉得讲的比较好的一篇文章,[推荐排序模型](https://www.infoq.cn/article/vKoKh_ZDXcWRh8fLSsRp)。 + +## 推荐模型时序图 + + +- 1.2.3 和 1.2.4 应该是在一步同时异步执行的 +- 1.3 获取文章title、content、发布时间、作者等相关信息 +- 1.2.3 里面包括 label 画像召回、LDA(Topic) 画像召回、用户与作者亲密度召回(我理解为基于社交关系的召回,这一类文章加上推荐理由效果会更好) + +tips: 为什么 Doc2vec 画像单独弄成一个微服务,因为 vector 是一个 300 维的 float 类型的数组,计算量特别大;所以做成 T+1 模式,提前计算好,然后索引到文件中。(使用的是开源的 Annoy 进行文件索引)。 + +## 实时画像时序图 + + +- 流处理框架使用当下比较流行的 Flink。 +- Flink 支持批量和流处理两种模式。 +- 批量计算时只是对接数据源不一样,数据仓库大多数为 Hive。 + +## 数据分析 +通过之前做过的一些视频、文章推荐举例。 + +- 多策略召回。 +- Rank 模型训练特征由离线转为实时。 +- 特征精细化,比如由文章总体 CTR 变为每个策略下的 CTR;不同分类下文章的时效性不一样,所以 CTR 计算周期不一样,比如有的文章在一开始 CTR 特别高,但随着时间变化,慢慢降低,但是相比其它的一些文章,还是很高,这时候,就需要调整 CTR 计算周期。 +- 分析重要的特征加入到训练模型,比如对文章来说:CTR、CDR;统计用户在每个分类下的 CTR、CDR,可以更加具体的描述出用户对不同分类的喜欢程度。 + +## 名词介绍 +- CTR:Click-Through-Rate,点击通过率。 +- CDR:Completion-reaDing-Rate,阅读完成率。 + +## 文章推荐 +- [从 FFM 到 DeepFFM,推荐排序模型到底哪家强?](https://www.infoq.cn/article/vKoKh_ZDXcWRh8fLSsRp) +- [NLP](https://easyai.tech/ai-definition/nlp/) +- [LDA](https://www.hankcs.com/nlp/lda-java-introduction-and-implementation.html) + + + + + + + + + diff --git a/machinelearning/tensorflow.md b/machinelearning/tensorflow.md new file mode 100644 index 0000000..4b0e209 --- /dev/null +++ b/machinelearning/tensorflow.md @@ -0,0 +1,2 @@ +# tensorflow 入门 +## 环境搭建 \ No newline at end of file diff --git a/mymp.jpeg b/mymp.jpeg new file mode 100644 index 0000000..317c843 Binary files /dev/null and b/mymp.jpeg differ diff --git a/pom.xml b/pom.xml index 3f161f9..0742932 100644 --- a/pom.xml +++ b/pom.xml @@ -10,16 +10,16 @@ 1.0-SNAPSHOT sortpro - keywords web lock bigdata codeinterview io test - datastructure + javabase/datastructure architecture - java8 + javabase/java8 + javabase/keywords diff --git a/test/pom.xml b/test/pom.xml index eb1bf75..0805452 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -27,6 +27,21 @@ grpc-stub 1.20.0 + + com.alibaba + fastjson + 1.2.60 + + + commons-beanutils + commons-beanutils + 1.8.0 + + + org.apache.commons + commons-collections4 + 4.1 + diff --git a/test/src/main/java/HiveTableChange.java b/test/src/main/java/HiveTableChange.java new file mode 100644 index 0000000..92d1115 --- /dev/null +++ b/test/src/main/java/HiveTableChange.java @@ -0,0 +1,77 @@ +import java.util.stream.IntStream; + +/** + * @Author : TommyYang + * @Time : 2019年08月22日 11:29 + * @Software: IntelliJ IDEA + * @File : HiveTableChange.java + */ +public class HiveTableChange { + + // CASCADE; add or replace column with CASCADE will update table and partition metadata; + + public static void main(String[] args) { + StringBuilder sBuilder = new StringBuilder("ALTER TABLE hdfs.rank_user_features ADD COLUMNS ("); +// StringBuilder sBuilder = new StringBuilder("CREATE EXTERNAL TABLE `hdfs.rank_user_features`("); + builder(sBuilder, "add"); + + System.out.println(sBuilder.toString()); + } + + private static void builder(StringBuilder sBuilder, String mode) { + if (!mode.equals("add")) { + sBuilder.append("`batch_id` string, ").append(" `gender` int, ") + .append("`entity_type` string, ").append("`entity_id` string, "); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`click_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`impr_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`read_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + } + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`latest_click_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`latest_impr_label%d` int", index); + sBuilder.append(latesClickStr).append(", "); + }); + + IntStream.range(1, 86).forEach(index -> { + String latesClickStr = String.format("`latest_read_label%d` int", index); + if (index < 85) { + sBuilder.append(latesClickStr).append(","); + } else { + sBuilder.append(latesClickStr); + } + }); + + if (mode.equals("add")){ + sBuilder.append(") CASCADE;"); + } else if (mode.equals("replace")) { + sBuilder.append(",dt string) CASCADE;"); + } else if (mode.equals("create")) { + sBuilder.append(")\n"); + sBuilder.append("PARTITIONED BY (`dt` string)\n") + .append("ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'\n") + .append("WITH SERDEPROPERTIES ('field.delim'='\\t', 'serialization.format'='\\t')\n") + .append("STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'\n") + .append("OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'\n") + .append("LOCATION 'hdfs://ns1/data/frontpagenote_recommend_records/user_features';"); + } + + } + +} diff --git a/test/src/main/java/StringUitls.java b/test/src/main/java/StringUitls.java new file mode 100644 index 0000000..f4807b6 --- /dev/null +++ b/test/src/main/java/StringUitls.java @@ -0,0 +1,27 @@ +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Stream; + +/** + * @Author : TommyYang + * @Time : 2019-09-17 16:23 + * @Software: IntelliJ IDEA + * @File : StringUitls.java + */ +public class StringUitls { + + public static void main(String[] args) throws IOException { + Stream lines = Files.lines(Paths.get("/Users/tommy/Documents/aa.txt"), Charset.defaultCharset()); + lines.forEach(line -> { +// System.out.println(line.trim()); + int start = line.trim().indexOf(">"); + int end = line.trim().lastIndexOf("<"); + String value = line.trim().substring(start + 1, end); + line = line.trim().replace("", String.format("", value)); + System.out.println(line); + }); + } + +} diff --git a/test/src/main/java/Test.java b/test/src/main/java/Test.java index 15d98bf..32c30ef 100644 --- a/test/src/main/java/Test.java +++ b/test/src/main/java/Test.java @@ -1,18 +1,198 @@ -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import com.google.gson.Gson; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** * @author TommyYang on 2019年04月12日 */ public class Test { - public static void main(String[] args){ - testDelItemFromMap(); + public static void main(String[] args) throws InterruptedException { +// testDelItemFromMap(); + +// List aList = new ArrayList(1000); +// IntStream.range(0, 999).forEach(index -> aList.add(index)); +// Set res = new HashSet(); +// aList.parallelStream().forEach(i -> res.add(i)); +// aList.parallelStream().forEach(i -> res.add(i)); +// +// System.out.println(res.size()); + +// List vals = new ArrayList(); +// Map> map = new HashMap(14); +// +// for (Integer i = 0; i <= 126; i++) { +// vals.add(i); +// } + + + List bList = new ArrayList(); + B b1 = new B(1); + b1.setInWhiteList(true); + B b2 = new B(2); + b2.setInWhiteList(true); + B b3 = new B(3); + b3.setInWhiteList(true); + B b4 = new B(4); + b4.setInWhiteList(true); + bList.add(b1); +// bList.add(b1); + bList.add(b2); + bList.add(b3); + bList.add(b4); + + List aList = new ArrayList(); + aList.add(new A(2, "AAA")); + aList.add(new A(3, "AAA1")); + aList.add(new A(5, "AAA2")); + aList.add(new A(7, "AAA3")); + aList.add(new A(9, "AAA4")); + IntStream.range(10, 100).forEach(item -> aList.add(new A(item, "AAA" + item))); + + A a = aList.stream().filter(item -> item.getId() == 10).findFirst().get(); + System.out.println(a); + +// Map map = new HashMap(); +// IntStream.range(1, 6).forEach(item -> map.put(item, "CCC" + item)); +// +// Long t1 = System.currentTimeMillis(); +// +//// aList.parallelStream().forEach(a -> { +//// if (map.containsKey(a.id)) { +//// a.setValue(map.get(a.id)); +//// } +//// }); +// aList.parallelStream().map(A::getValue).collect(Collectors.toList()); +// +// Long t2 = System.currentTimeMillis(); +// +//// aList.stream().forEach(a -> { +//// if (map.containsKey(a.id)) { +//// a.setValue(map.get(a.id)); +//// } +//// }); +// aList.stream().map(A::getValue).collect(Collectors.toList()); +// +// Long t3 = System.currentTimeMillis(); +// +// aList.forEach(a -> { +// if (map.containsKey(a.id)) { +// a.setValue(map.get(a.id)); +// } +// }); +// Long t4 = System.currentTimeMillis(); +// +// System.out.println(aList); +// +// System.out.println("parallelStream:" + (t2 - t1)); +// System.out.println("stream:" + (t3 - t2)); +// System.out.println("direct foreach:" + (t4 - t3)); +// +// +// List list = Lists.newArrayList("288867","286818","280552","253830","253813","253811","253808","253805","253803","253802","243284","229414","229411","228492","194795","193587","193586","193003","168281","131257","125441","124735","117672","117670","117669","117668","117666","117665","117663","117661","117646","117645","117644","117643","108262","105711","97446","97445","86982","75899","75897","75895","75894","75892","75890","75889","75887","75885","75882","75880","75876","75874","75869","75865","75864","75863","75861","75860","75858","75855","75854","75853","75852","75851","75850","75849","75846","75842","75840","75838","75837","75836","75834","75832","75831","75830","75828","75826","75825","75824","75822","75821","75820","75819","75818","75816","75815","75814","75813","75812","75811","75810","75809","75808","75807","75806","75805","75804","75803","75802","75801","75800","75799","75798","75797","75796","75795","75794","75793","75792","75791","75790","75787","1"); +// System.out.println(JSONObject.toJSONString(list)); +// System.out.println(list.size()); + + List valList = Lists.newArrayList( + + ); + StringBuilder stringBuilder = new StringBuilder("[\"java.util.ArrayList\", [\n"); + for (Integer val : valList) { + stringBuilder.append("[\"java.lang.Long\", " + val + "],\n"); + } + stringBuilder.append("]]"); + System.out.println(stringBuilder); + +// try (Stream stream = Files.lines(Paths.get("/Users/mtdp/aa.md"))) { +// StringBuilder sb = new StringBuilder(); +// stream.forEach(line -> sb.append(line).append(",\n")); +// +// System.out.println(sb.toString()); +// +// +// } catch (IOException e) { +// e.printStackTrace(); +// } + + } + + + static class B { + private int id; + private boolean inWhiteList; + + public B(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public boolean isInWhiteList() { + return inWhiteList; + } + + public void setInWhiteList(boolean inWhiteList) { + this.inWhiteList = inWhiteList; + } + + @Override + public String toString() { + return "B{" + + "id=" + id + + ", inWhiteList=" + inWhiteList + + '}'; + } } + static class A { + private Integer id; + + private String value; + + public A(Integer id, String value) { + this.id = id; + this.value = value; + } + + public Integer getId() { + return id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "A{" + + "id=" + id + + ", value='" + value + '\'' + + '}'; + } + } - public static void testDelItemFromMap(){ + public static void testDelItemFromMap() { Map map = new HashMap(); map.put("aa", "aa"); map.put("bb", "bb"); @@ -20,9 +200,9 @@ public static void testDelItemFromMap(){ map.put("dd", "dd"); Iterator> it = map.entrySet().iterator(); - while (it.hasNext()){ + while (it.hasNext()) { Map.Entry val = it.next(); - if (val.getValue().equals("aa")){ + if (val.getValue().equals("aa")) { it.remove(); } } diff --git a/test/src/main/java/cn/tommyyang/demo/Person.java b/test/src/main/java/cn/tommyyang/demo/Person.java new file mode 100644 index 0000000..ff39183 --- /dev/null +++ b/test/src/main/java/cn/tommyyang/demo/Person.java @@ -0,0 +1,61 @@ +package cn.tommyyang.demo; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * @Author : TommyYang + * @Time : 2019年07月02日 12:56 + * @Software: IntelliJ IDEA + * @File : Person.java + */ +public class Person { + + private final Date birthDate; + + public Person(Date birthDate) { + this.birthDate = birthDate; + } + + // Other fields, methods, and constructor omitted + // Don't do this! + public boolean isBabyBoomer() { + // Unnecessary allocation of expensive object + Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0); + Date boomStart = gmtCal.getTime(); + gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0); + Date boomEnd = gmtCal.getTime(); + + return birthDate.compareTo(boomStart)>= 0 && + birthDate.compareTo(boomEnd) < 0; + } + +} + +class Person1 { + private final Date birthDate; + + public Person1(Date birthDate) { + this.birthDate = birthDate; + } + // Other fields, methods, and constructor omitted + + // The starting and ending dates of the baby boom + private static final Date BOOM_START; + private static final Date BOOM_END; + + static { + Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0); + BOOM_START = gmtCal.getTime(); + gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0); + BOOM_END = gmtCal.getTime(); + } + + public boolean isBabyBoomer() { + return birthDate.compareTo(BOOM_START)>= 0 && + birthDate.compareTo(BOOM_END) < 0; + } +} \ No newline at end of file diff --git a/test/src/main/java/cn/tommyyang/grpc/GrpcClient.java b/test/src/main/java/cn/tommyyang/grpc/GrpcClient.java index 26da756..6407069 100644 --- a/test/src/main/java/cn/tommyyang/grpc/GrpcClient.java +++ b/test/src/main/java/cn/tommyyang/grpc/GrpcClient.java @@ -32,6 +32,7 @@ public void greet() throws IOException { GreeterOuterClass.Request request = GreeterOuterClass.Request .newBuilder() .setName("aaa") + .setAge(1) .build(); try { @@ -54,7 +55,7 @@ public void greet() throws IOException { } public static void main(String[] args) throws IOException, ClassNotFoundException { - GrpcClient client = new GrpcClient("localhost", 50051); + GrpcClient client = new GrpcClient("127.0.0.1", 50051); client.greet(); } diff --git a/test/src/main/java/cn/tommyyang/grpc/GrpcServer.java b/test/src/main/java/cn/tommyyang/grpc/GrpcServer.java index 5f9b06a..e564639 100644 --- a/test/src/main/java/cn/tommyyang/grpc/GrpcServer.java +++ b/test/src/main/java/cn/tommyyang/grpc/GrpcServer.java @@ -67,9 +67,9 @@ static class GreeterImpl extends GreeterGrpc.GreeterImplBase{ public void hello(GreeterOuterClass.Request request, StreamObserver responseObserver) { try { GreeterOuterClass.Response response = GreeterOuterClass.Response.newBuilder() - .setMsg("hello" + request.getName()).build(); - //responseObserver.onNext(response); - //responseObserver.onCompleted(); + .setMsg("hello" + request.getName() + "; age:" + request.getAge()).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); throw new Exception("error"); } catch (Exception e){ Metadata metadata = new Metadata(); diff --git a/test/src/main/java/cn/tommyyang/grpc/protobuf/GreeterOuterClass.java b/test/src/main/java/cn/tommyyang/grpc/protobuf/GreeterOuterClass.java index 0e5860e..f1aae17 100644 --- a/test/src/main/java/cn/tommyyang/grpc/protobuf/GreeterOuterClass.java +++ b/test/src/main/java/cn/tommyyang/grpc/protobuf/GreeterOuterClass.java @@ -27,6 +27,11 @@ public interface RequestOrBuilder extends */ com.google.protobuf.ByteString getNameBytes(); + + /** + * uint32 age = 2; + */ + int getAge(); } /** * Protobuf type {@code cn.tommyyang.grpc.protobuf.Request} @@ -42,6 +47,7 @@ private Request(com.google.protobuf.GeneratedMessageV3.Builder> builder) { } private Request() { name_ = ""; + age_ = 0; } @java.lang.Override @@ -74,6 +80,11 @@ private Request( name_ = s; break; } + case 16: { + + age_ = input.readUInt32(); + break; + } default: { if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { @@ -140,6 +151,15 @@ public java.lang.String getName() { } } + public static final int AGE_FIELD_NUMBER = 2; + private int age_; + /** + * uint32 age = 2; + */ + public int getAge() { + return age_; + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -157,6 +177,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (!getNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_); } + if (age_ != 0) { + output.writeUInt32(2, age_); + } unknownFields.writeTo(output); } @@ -169,6 +192,10 @@ public int getSerializedSize() { if (!getNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_); } + if (age_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(2, age_); + } size += unknownFields.getSerializedSize(); memoizedSize = size; return size; @@ -187,6 +214,8 @@ public boolean equals(final java.lang.Object obj) { boolean result = true; result = result && getName() .equals(other.getName()); + result = result && (getAge() + == other.getAge()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -200,6 +229,8 @@ public int hashCode() { hash = (19 * hash) + getDescriptor().hashCode(); hash = (37 * hash) + NAME_FIELD_NUMBER; hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + AGE_FIELD_NUMBER; + hash = (53 * hash) + getAge(); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -335,6 +366,8 @@ public Builder clear() { super.clear(); name_ = ""; + age_ = 0; + return this; } @@ -362,6 +395,7 @@ public cn.tommyyang.grpc.protobuf.GreeterOuterClass.Request build() { public cn.tommyyang.grpc.protobuf.GreeterOuterClass.Request buildPartial() { cn.tommyyang.grpc.protobuf.GreeterOuterClass.Request result = new cn.tommyyang.grpc.protobuf.GreeterOuterClass.Request(this); result.name_ = name_; + result.age_ = age_; onBuilt(); return result; } @@ -414,6 +448,9 @@ public Builder mergeFrom(cn.tommyyang.grpc.protobuf.GreeterOuterClass.Request ot name_ = other.name_; onChanged(); } + if (other.getAge() != 0) { + setAge(other.getAge()); + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -511,6 +548,32 @@ public Builder setNameBytes( onChanged(); return this; } + + private int age_ ; + /** + * uint32 age = 2; + */ + public int getAge() { + return age_; + } + /** + * uint32 age = 2; + */ + public Builder setAge(int value) { + + age_ = value; + onChanged(); + return this; + } + /** + * uint32 age = 2; + */ + public Builder clearAge() { + + age_ = 0; + onChanged(); + return this; + } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { @@ -1134,10 +1197,11 @@ public cn.tommyyang.grpc.protobuf.GreeterOuterClass.Response getDefaultInstanceF static { java.lang.String[] descriptorData = { "\n\rgreeter.proto022円032円cn.tommyyang.grpc.proto" + - "buf\"027円\n007円Request022円014円\n004円name030円001円 001円(\t\"027円\n010円Response" + - "022円013円\n003円msg030円001円 001円(\t2_\n007円Greeter022円T\n005円Hello022円#.cn.t" + - "ommyyang.grpc.protobuf.Request032円$.cn.tomm" + - "yyang.grpc.protobuf.Response\"000円b006円proto3" + "buf\"$\n007円Request022円014円\n004円name030円001円 001円(\t022円013円\n003円age030円002円 001円(" + + "\r\"027円\n010円Response022円013円\n003円msg030円001円 001円(\t2_\n007円Greeter022円T\n" + + "005円Hello022円#.cn.tommyyang.grpc.protobuf.Requ" + + "est032円$.cn.tommyyang.grpc.protobuf.Respons" + + "e\"000円b006円proto3" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { @@ -1156,7 +1220,7 @@ public com.google.protobuf.ExtensionRegistry assignDescriptors( internal_static_cn_tommyyang_grpc_protobuf_Request_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_cn_tommyyang_grpc_protobuf_Request_descriptor, - new java.lang.String[] { "Name", }); + new java.lang.String[] { "Name", "Age", }); internal_static_cn_tommyyang_grpc_protobuf_Response_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_cn_tommyyang_grpc_protobuf_Response_fieldAccessorTable = new diff --git a/test/src/main/java/demo/Demo1.java b/test/src/main/java/demo/Demo1.java new file mode 100644 index 0000000..0b2e4ba --- /dev/null +++ b/test/src/main/java/demo/Demo1.java @@ -0,0 +1,19 @@ +package demo; + +/** + * @Author : TommyYang + * @Time : 2019年08月11日 14:32 + * @Software: IntelliJ IDEA + * @File : Demo1.java + */ +public class Demo1 { + + public static void main(String[] args) { + + TreeNode rootNode = new TreeNode(1); + + } + + + +} diff --git a/test/src/main/java/demo/Event.java b/test/src/main/java/demo/Event.java new file mode 100644 index 0000000..7d1cca5 --- /dev/null +++ b/test/src/main/java/demo/Event.java @@ -0,0 +1,37 @@ +package demo; + +import java.util.Date; + +/** + * @Author : TommyYang + * @Time : 2019年07月31日 16:58 + * @Software: IntelliJ IDEA + * @File : Event.java + */ +public class Event { + + private String noteId; + private String type; + private Date time; + + public Event(String noteId, String type) { + this.noteId = noteId; + this.type = type; + } + + public String getNoteId() { + return noteId; + } + + public String getType() { + return type; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } +} diff --git a/test/src/main/java/demo/TreeNode.java b/test/src/main/java/demo/TreeNode.java new file mode 100644 index 0000000..1fc4e47 --- /dev/null +++ b/test/src/main/java/demo/TreeNode.java @@ -0,0 +1,54 @@ +package demo; + +/** + * @Author : TommyYang + * @Time : 2019年08月11日 14:33 + * @Software: IntelliJ IDEA + * @File : TreeNode.java + */ +public class TreeNode { + + private TreeNode leftNode; + private TreeNode rithtNode; + private int data; + private boolean isLeft; + + public TreeNode(int data) { + this.data = data; + } + + public int getData() { + return data; + } + + public TreeNode getLeftNode() { + return leftNode; + } + + public void setLeftNode(TreeNode leftNode) { + this.leftNode = leftNode; + } + + public TreeNode getRithtNode() { + return rithtNode; + } + + public void setRithtNode(TreeNode rithtNode) { + this.rithtNode = rithtNode; + } + + public boolean isLeft() { + return isLeft; + } + + public void setLeft(boolean left) { + isLeft = left; + } + + public void display(TreeNode rootNode, boolean isLeft) { + while (rootNode != null) { + System.out.println(rootNode.getData()); + } + + } +} diff --git a/test/src/main/protos/greeter.proto b/test/src/main/protos/greeter.proto index 0a9b76b..bc717be 100644 --- a/test/src/main/protos/greeter.proto +++ b/test/src/main/protos/greeter.proto @@ -8,6 +8,7 @@ service Greeter { message Request { string name = 1; + uint32 age = 2; } message Response { diff --git "a/web/POJO343円200円201円BO343円200円201円VO343円200円201円DO343円200円201円DTO343円200円201円DAO343円200円201円PO etc.md" "b/web/POJO343円200円201円BO343円200円201円VO343円200円201円DO343円200円201円DTO343円200円201円DAO343円200円201円PO etc.md" new file mode 100644 index 0000000..56827c3 --- /dev/null +++ "b/web/POJO343円200円201円BO343円200円201円VO343円200円201円DO343円200円201円DTO343円200円201円DAO343円200円201円PO etc.md" @@ -0,0 +1,26 @@ +# JavaWeb 中 POJO、BO、VO、DO、DTO、DAO、PO 详细介绍 +## PO +**PO**(持久对象)是 Persistent Object 的缩写,用于表示数据库中的一条记录映射成 Java 对象。PO 仅仅用于表示数据,没有任何数据操作。通常遵守 Java Bean 规范,拥有 getter/setter 方法。 + +## BO +**BO**(业务对象)是 Business Object 的缩写,用于表示一个业务对象,可以进行 PO 与 VO/DTO 之间的转换。BO 通常位于业务层,要区别于直接对外提供服务的服务层;BO 提供了基本业务单元的基本业务操作,在设计上属于被服务层业务流程调用的对象,一个业务流程可能需要调用多个 BO 来完成。也可以理解我们开发中的 Service 对象。 + +## DO +**DO**(领域对象)是 Domain Object 的缩写。个人觉得可以代替 PO,用于表示数据库对象, + +## VO +**VO** 是 Value Object 的缩写,用来表示一个与前端进行交互的 Java 对象。通常拥有 Java Bean 的规范,拥有 getter/setter 方法。 + +## DTO +**DTO**(数据传输对象) 是 Data Transfer Object 的缩写,用于表示一个数据传输对象。DTO 通常用于不同服务或服务不同分层之间的数据传输。DTO 与 VO 概念类似,并且通常情况下字段基本一致。但 DTO 与 VO 又有一些不同,这个不同之处主要实在设计理念上,比如 API 服务需要使用的 DTO 就可能与 VO 存在差异。通常拥有 Java Bean 的规范,拥有 getter/setter 方法。 + +## DAO +**DAO**(数据访问对象) 是 Data Access Object 的缩写,用来表示一个数据访问的对象。使用 DAO 访问数据库,包括 CRUD 等操作,与 PO 一起使用。DAO 一般在持久层,完全封装数据库操作,对外暴露的方法使得上层应用不需要关注数据库相关的任何信息。 + +## POJO +**POJO** 是 Plain Ordinary Java Object 的缩写。是 PO、DO、VO、DTO 的统称。不会将一个对象命名为以 POJO 结尾。 + + +**注意**:为什么需要定义这些 Object 对象呢? + +个人觉得是因为每一层负责每一层的工作,然后也可以减少传输数据量的大小和保护数据库结构不外泄。如果你所有地方都使用 PO 对象,那么前端就很轻易的知道了你的数据库结构,同时有些数据根本不用传到前端的(可以节省传输数据量的大小),比如我们一些数据库里面表示是否删除、更新时间等这些字段,是不需要让前端知道的。 \ No newline at end of file diff --git a/web/README.md b/web/README.md index 37c2a93..1d6fc59 100644 --- a/web/README.md +++ b/web/README.md @@ -1,6 +1,8 @@ # Web 篇 该篇章我们主要来介绍一些 Web 后端的知识,比如 http 协议, Cookie, Session, Jsp 等。 +## [一次完整的HTTP请求过程](http-processing.md) + ## Cookie 详解 首先我们需要介绍一下,在Web开发过程中为什么会引入Cookie。 @@ -27,10 +29,6 @@ Session机制:采用的是在服务器端保持Http状态信息的方案。 [Session详解](http://blog.tommyyang.cn/2017/03/15/Session详解-2017/) -## Spring 全家桶 -- [Spring 配置](spring-config.md) -- [Spring IOC](spring-ioc.md) -- Spring AOP -- SpringMVC -- SpringBoot -- SpringCloud \ No newline at end of file +## [Spring 全家桶](spring.md) + +## [Filter-Interceptor](filter-interceptor.md) \ No newline at end of file diff --git a/web/filter-interceptor.md b/web/filter-interceptor.md new file mode 100644 index 0000000..17d58a3 --- /dev/null +++ b/web/filter-interceptor.md @@ -0,0 +1,35 @@ +# Filter 和 Interceptor 详解 +Filter 和 Interceptor 都是可以处理 HttpServletRequest、HttpServletResponse 的。Filter 是 JDK 自带的接口,而 Interceptor 是 Spring 封装的接口,可以理解成一种特殊的 Filter,下面会具体分析。 + +## Filter +Filter 对**用户请求**进行**预处理**,接着将请求交给 Servlet 进行**处理**并**生成响应**,最后Filter再对**服务器响应**进行后处理。Filter 是可以复用的代码片段,常用来转换**Http 请求**、**响应**和**头信息**。Filter 不像 Servlet,它不能产生**响应**,而是只**修改**对某一资源的**请求**或者**响应**。 + +## Interceptor +类似**面向切面编程**中的**切面**和**通知**,我们通过**动态代理**对一个 service() 方法添加**通知**进行功能增强。比如说在方法执行前进行**初始化处理**,在方法执行后进行**后置处理**。**拦截器**的思想和**AOP**类似,区别就是**拦截器**只能对 Controller 的 HTTP 请求进行拦截。 + +## 两者的区别 +- Filter 是基于**函数回调**的,而 Interceptor 则是基于 Java **反射**和**动态代理**。 +- Filter 依赖于 Servlet 容器,而 Interceptor 不依赖于 Servlet 容器。 +- Filter 对几乎**所有的请求**起作用,而 Interceptor 只对 Controller 请求起作用。 + +## 两者在一个 Http 请求中的执行顺序 +- 对于自定义 Servlet 对请求分发流程: + + - Filter 过滤请求处理。 + - Servlet 处理请求。 + - Filter 过滤响应处理。 + +- 对于自定义 Controller 的请求分发流程: + + - Filter 过滤请求处理。 + - Interceptor 拦截请求处理。 + - 对应的 HandlerAdapter 处理请求。 + - Interceptor 拦截响应处理。 + - Interceptor 的最终处理。 + - Filter 过滤响应处理。 + +## 示例 +更多 web 开发相关示例请关注[spring-demo](https://github.com/joyang1/spring-demo)。 + + + diff --git a/web/http-processing.md b/web/http-processing.md new file mode 100644 index 0000000..49b092c --- /dev/null +++ b/web/http-processing.md @@ -0,0 +1,216 @@ +# HTTP 请求的完整过程 +一次 HTTP 请求的整个过程包括:DNS 解析、建立 TCP 连接、客户端请求、服务端响应、断开 TCP 连接。 +本文主要从以上几个方面来讲解一次完整的 HTTP 请求。 + +## HTTP 起源 +今天我们能够在网络中畅游,都得益于一位计算机科学家蒂姆·伯纳斯·李的构想。1991 年 8 月 6 日,**蒂姆·伯纳斯·李**在位于欧洲粒子物理研究所(CERN)的 NeXT 计算机上,正式公开运行[世界上第一个Web网站](http://info.cern.ch),建立起基本的互联网基础概念和技术体系,由此开启了网络信息时代的序幕。 + +伯纳斯·李的提案包含了网络的基本概念并逐步建立了所有必要的工具: + +- 提出 HTTP (Hypertext Transfer Protocol) 超文本传输协议,允许用户通过单击超链接访问资源。 +- 提出使用HTML超文本标记语言(Hypertext Markup Language)作为创建网页的标准。 +- 创建了统一资源定位器 URL (Uniform Resource Locator)作为网站地址系统,就是沿用至今的 http://www URL 格式。 +- 创建第一个 **Web 浏览器**,称为万维网浏览器,这也是一个 Web 编辑器。 +- 创建第一个 [Web 服务器](http://info.cern.ch)以及描述项目本身的第一个Web页面。 + +HTTP 协议一共有五大特点: + +- 支持客户/服务器模式。 +- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。 +- 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由 Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记。 +- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 +- 无状态:无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息(Cookie 和 Session 孕育而生,后期再讲)。 + +## DNS 解析 +DNS( Domain Name System) 是"域名系统"的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于 TCP/IP 网络,它所提供的服务是用来将主机名和域名转换为 IP 地址的工作。 + +关于 DNS 的获取流程: + +DNS 是应用层协议,事实上他是为其他应用层协议工作的,包括不限于 HTTP 和 SMTP 以及 FTP,用于将用户提供的主机名解析为 ip 地址。具体过程如下: +- 用户主机上运行着 DNS 的客户端,就是我们的 PC 机或者手机客户端运行着 DNS 客户端。 +- 浏览器将接收到的 url 中抽取出域名字段,就是访问的主机名,比如 `http://www.baidu.com/`,并将这个主机名传送给 DNS 应用的客户端。 +- DNS 客户机端向 DNS 服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式 DNS 集群的工作)。 +- 该 DNS 客户机最终会收到一份回答报文,其中包含有该主机名对应的 IP 地址。 +- 一旦该浏览器收到来自 DNS 的 IP 地址,就可以向该IP地址定位的 HTTP 服务器发起 TCP 连接。 + +## 建立 TCP 连接 +相信大家都知道 **HTTP 是一个基于 TCP/IP 协议簇来传递数据**。TCP/IP 协议在进行连接的时候都需要进行**三次握手**,所以 HTTP 在连接服务器的时候也需要进行三次握手。 + +TCP/IP 是互联网相关的各类协议簇的总称。也有另一种说法 TCP/IP 是 TCP 和 IP 两种协议。 TCP/IP 四层模型如下: + + + +TCP 报文包 = TCP 头信息 + TCP 数据体,而在 TCP 头信息中包含了 6 种控制位(上图红色框中),这六种标志位就代表着 TCP 连接的状态: + +- SYN:表示请求建立一个连接(同步序号) +- URG:紧急数据(urgent data)—这是一条紧急信息 +- ACK:确认已收到(确认序号) +- PSH:尽可能快地将数据送往接收进程 +- RST:表示要求对方重新建立连接 +- FIN:表示通知对方本端已经完成数据发送,要关闭连接了 + +TCP 建立连接过程 --- 三次握手 + + + +**过程说明**: +- 客户端发送位码为 syn=1,随机产生 seq number=1234567 的数据包到服务器,服务器由SYN=1知道客户端要求建立联机(客户端:我要连接你) +- 服务器收到请求后要确认联机信息,向 A 发送ack number=(客户端的seq+1),syn=1,ack=1,随机产生seq=7654321的包(服务器:好的,你来连吧) +- 客户端收到后检查 ack number 是否正确,即第一次发送的 seq number+1 ,以及位码 ack 是否为1,若正确,客户端会再发送 ack number=(服务器的seq+1),ack=1,服务器收到后确认seq值与ack=1则连接建立成功。(客户端:好的,我来了) + +**注意注意注意,重要的问题说三次**:为什么 http 建立连接需要三次握手,不是两次或四次? + +个人理解:确认双方信道可以实现最低限度的**全双工**(可以合并第 2,3 步),三次是最少的安全次数,两次不安全,四次浪费资源。 + +如果觉得理解不对的,可以下方留言或者在 issue 里面去讨论。 + +## 客户端请求 +三次握手之后,客户端和服务端的连接就已经建立好了,客户端就可以向服务器端发送 HTTP 请求。 + +### HTTP 请求报文结构 +**TCP 报文包 = TCP 头信息 + TCP 数据体**,TCP 头信息的结构如下: + +TCP 数据体,也就是 HTTP 请求报文。结构如下: + + + +### HTTP 请求实例 + +``` +GET1 /settings/user_has_gravatar2 HTTP/1.13 + + +Host: github.com +Connection: keep-alive +Accept: application/json +X-Requested-With: XMLHttpRequest +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: cors +Referer: https://github.com/settings/profile +Accept-Encoding: gzip, deflate, br +Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7 +Cookie: _octo=GH1.1.503958834.1571276454; _ga=GA1.2.104159404.1571276456; _device_id=d0247b2a88a0126139fad221e62f2c91; user_session=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; __Host-user_session_same_site=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; logged_in=yes; dotcom_user=joyang1; has_recent_activity=1; tz=Asia%2FShanghai; _gat=1 +4 + +username=tommyyang&userid=1681685 + +``` + +- 1是请求方法,HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种 GET 和 POST,如果是 RESTFUL 接口的话一般会用到 GET、POST、DELETE、PUT。 +- 2为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL +- 3是协议名称及版本号 +- 4是HTTP的报文头,报文头包含若干个属性,格式为"属性名:属性值",服务端据此获取客户端的信息 +- 5是报文体,GET 方法 username=tommyyang&userid=168168 通过请求 URL 传递参数,如"/settings/user_has_gravatar?username=tommyyang&userid=168168"的方式传递请求参数。 + +`参数说明如下`: +Host: 域名。 +Connection: 连接状态。 +User-Agent:客户端使用的操作系统和浏览器的名称和版本,有些网站会限制请求浏览器。 +Referer:跳转到该网页的地址,表示此请求来自哪里,有些网站会限制请求来源。 + +## 服务端响应 +服务器在收到客户端请求,然后对请求处理完后需要响应并返回给客户端,而 HTTP 响应报文结构与请求结构体一致。 + +### HTTP 响应报文结构 +HTTP 响应报文结构与请求报文结构类似,包括: + +- 报文首部。 +- 空行(CR + LF),表示报文主体开始。 +- 报文主体。 +- 空行(CR + LF),表示报文主体结束。 + +结构如下: + + + +### 响应状态码 +| | 类别 | 原因短语 | +| :----: | :----: | :----: | +| 1XX | Informational(信息状态码)| 接受的请求正在处理 | +| 2XX | Success(成功状态码) | 请求正常处理完毕 | +| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | +| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | +| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | + + +### HTTP 响应实例 + +``` +HTTP/1.11 200 OK2 + +3 +Server: GitHub.com +Date: 2020年1月20日 11:17:40 GMT +Status: 304 Not Modified +Vary: X-PJAX +Cache-Control: max-age=0, private, must-revalidate +Set-Cookie: user_session=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; path=/; expires=2020年2月03日 11:17:40 -0000; secure; HttpOnly +Set-Cookie: __Host-user_session_same_site=Z3ZgN7swYstKY35aXI_GD_u4A3Jk-pZ-5bVBXRCBpPmrjfV9; path=/; expires=2020年2月03日 11:17:40 -0000; secure; HttpOnly; SameSite=Strict +Set-Cookie: has_recent_activity=1; path=/; expires=2020年1月20日 12:17:40 -0000 +X-Request-Id: 1bc86002-496d-4d6a-a028-9fd129fac631 +Strict-Transport-Security: max-age=31536000; includeSubdomains; preload +Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin +Expect-CT: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors" +Content-Security-Policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; connect-src 'self' uploads.github.com www.githubstatus.com collector.githubapp.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com wss://live.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com github-cloud.s3.amazonaws.com *.githubusercontent.com; manifest-src 'self'; media-src 'none'; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com +X-GitHub-Request-Id: BEF1:4193:BBF9FF:1933DED:5E258C54 +Content-Type: application/json; charset=utf-8 +ETag: W/"b086cd16a5d1e1190981cda623503729" +X-Frame-Options: deny +X-Content-Type-Options: nosniff +X-XSS-Protection: 1; mode=block +Content-Encoding: gzip + +4 +6f +{"has_gravatar":false} +0 +``` + +- 1 报文协议及版本 +- 2 状态码及状态描述 +- 3 响应头 +- 4 响应体 + +## 断开连接 +在服务器响应完毕后,一次会话就结束了,这时候连接会断开么? + +### 长短连接 + +是否断开,我们需要根据 HTTP 版本来确定: + +- HTTP/1.0 版本的时候,客户端与服务器完成一个请求/响应之后,会将之前建立的 **TCP 连接断开**,下次请求的时候又要重新建立 TCP 连接, + 这也就是**短连接**。 +- 在 HTTP1.0 发布仅半年后(1997年1月) ,HTTP/1.1 版本发布并带来一个新的功能:在客户端与服务器完成一次请求/响应之后,允许不断开 TCP 连接, + 这意味着下次请求就直接使用这个 TCP 连接而不需要重新握手建立新连接,这也被称为**长连接**。 + +**tips:长连接是指一次TCP连接允许多次HTTP会话,HTTP永远都是一次请求/响应,会话结束,HTTP本身不存在长连接之说**。 + +早在 1999 年 HTTP1.1 就推广普及,现在浏览器在请求时请求头中都会携带一个参数:**Connection:keep-alive**,这表示浏览器要求与服务器建立长连接, +而服务器也可以设置是否愿意建立长连接。 + +### 长连接的优缺点 +- 优点:当网站中有大量静态资源(图片、css、js等)被请求时就可以开启长连接,这些静态资源就可以通过一次 TCP 连接发送。 +- 缺点:当客户端请求一次就不再请求时,服务器却一直开着长连接,资源被占用着,严重浪费资源。 + +### 断开连接过程 + +**在建立 TCP 连接时是三次握手,而断开 TCP 连接是四次挥手**。 + +TCP 断开连接过程 --- 四次挥手结构图如下: + + + +在 TCP 连接建立的时候,讲到了标志位:**FIN 表示通知对方自身要断开连接了**。 + +**注意注意注意,重要的问题说三次**:为什么断开连接是四次挥手呢? + +个人理解:**由于 TCP 要支持半关闭连接**。在建立连接的时候是全双工的,A <=> B 双方都可以读写。断开的时候需要支持半关闭,意味着 TCP 支持客户端和服务端双方独立关闭通道;因此会有两次独立的关闭写通道的请求。一次关闭请求(FIN),对应一个 ACK,也就有了四次挥手。 + +如果觉得理解不对的,可以下方留言或者在 issue 里面去讨论。 + +## 拓展 + +- 了解下 HTTP2.0。 +- HTTP & RPC,了解下 HTTP 与 RPC 的区别,为什么要使用 RPC? +- HTTP & HTTPS,了解学习为什么现在应用更多的使用 HTTPS? diff --git a/web/spring-config.md b/web/spring-config.md index 80bf1de..49a30d1 100644 --- a/web/spring-config.md +++ b/web/spring-config.md @@ -11,13 +11,58 @@ Spring 配置的方式可以分为: - Spring 注解 - @Controller + - @RestController - @Service - @Repository - @Component - @Autowired + - @ModelAttribute + - @RequestBody + - @PathVariable + - @RequestMapping + - @RequestParam + - @ResponseBody + - @ModelAttribute + - @Cacheable + - @CacheEvict + - @Scope - Java 注解 + + - @Resource + - @PostConstruct + + 被 @PostConstruct 修饰的方法会在服务器加载 Servlet 的时候运行,并且只会被服务器调用一次,类似于 Servlet 的 init()方法。被 @PostConstruct 修饰的方法会在构造函数之后,init() 方法之前运行。 + + - @PreConstruct + + 被 @PreConstruct 修饰的方法会在服务器卸载 Servlet 的时候运行,并且只会被服务器调用一次,类似于 Servlet 的 destroy() 方法。被 @PreConstruct 修饰的方法会在 destroy() 方法之后运行,在 Servlet 被彻底卸载之前。 + +- 自定义注解(annotation) + + [参考](https://github.com/joyang1/spring-demo) + +### Spring 中 @Autowired, @Resource 和 @Inject 有何差异? +三个注释中的两个属于 Java 扩展包:@Resource 属于 javax.annotation.Resource 包和 @Inject 属于 javax.inject.Inject 包。 +@Autowired注解属于org.springframework.beans.factory.annotation包。 + +`@Autowired` + +| 特性 | 说明 | +| :---- | :---- | +| 原理 | 根据类型来自动注入(ByType)| +| 注入类型 | 既可以注入一个接口,也可以直接注入一个实例 | +| 限制 | 1.当注入一个接口时,这个接口只能有一个实现类,如果存在一个以上的实现类,那么Spring会抛出异常,因为两个同样的接口实现类,它不知道该选择哪一个来注入。2.当注入一个实例时,跟接口类似,如果这个实例在XML配置文件中声明了两个不同的Bean,那么Spring也会抛出异常。| +| 解决办法 | @Autowired配合@Qualifier来使用,通过@Qualifier来指明要注入Bean的name。| + +`@Resource` + +| 特性 | 说明 | +| :---- | :---- | +| 原理 | 如果指定了name属性, 那么就按name属性的名称装配;如果没有指定name属性, 那就按照要注入对象的字段名查找依赖对象;如果按默认名称查找不到依赖对象, 那么就按照类型查找。 | +| 注入类型 | 既可以注入一个接口,也可以直接注入一个实例 | ## Spring 在 xml 如何配置 Map、List 1. 使用 `xmlns:util="http://www.springframework.org/schema/util` 在 xml 中配置 map 和 list 的 bean。 2. 使用 Java 自带的 @Resource(name="***") 给具体需要的元素赋值。 + diff --git a/web/spring-ioc.md b/web/spring-ioc.md index 2864ff0..b042136 100644 --- a/web/spring-ioc.md +++ b/web/spring-ioc.md @@ -83,4 +83,100 @@ Spring IoC 容器不仅提供了 IoC 支持,还提供了 IoC 之外的支持 ``` - \ No newline at end of file +### BeanFactory 之 xml 配置 +所有注册到容器的业务对象,在 Spring 称之为 bean。故每一个对象在 xml 中的映射也自然地对应一个 的元素。而把这些 元素组织起来的就是 。 + +#### beans 作为 xml 配置文件中最顶层的元素,拥有如下几个元素: +- description 0 个或 1 个 +- bean 0 个 或 多个 +- import +- alias + +#### beans 对 bean 进行管理的属性 +- **default-lazy-init** + + 其值可以为 true 或 false,默认值为 false。用来标志对所有的 进行延迟初始化。 + +- **default-autowire** + + 可以取值为 no、byName、byType、constructor以及 autodetect。默认值为 no,如果使用自动绑定的话,用来标志全体 bean 使用哪一种默认绑定方式。 + +- **default-init-method** + + 如果下面的所有 元素按照某种规则,都有同样初始化方法的方法名,那么可以使用这个属性去统一设置。 + +- **default-destroy-method** + + 与 default-init-method 相对应,如果下面的所有 元素按照某种规则,都有同样对象销毁方法的方法名,那么可以使用该属性统一设置。 + +#### bean 的相关属性 +- id: 对象注册到容器的唯一标志,就跟人的身份证号码一样(重复的话就非常麻烦)。 + +- name: 很灵活,name 可以使用 id 不能使用的一些字符,也可以空格、逗号或者冒号分割来给一个 bean 指定多个 name。name 的作用跟 alias 为 id 指定多个别名基本累似。demo 如下: + + ``` + + + + + + ``` + +- class: 通过 class 指定注册到容器的对象的类型,也就是对象的具体 class 路径。 + +- parent: 实现继承。 + +- scope: BeanFactory 除了拥有作为 IoC Service Provider 的职责,作为一个轻量级容器,它还有着其它的一些职责,其中就包括对象生命周期管理。bean 的生命周期,决定在于 scope 属性的设置。 + + - singleton: 在 Spring 的 IoC 容器中,只存在一个共享实例,所有对象共享该对象的引用。这种对象的生命周期基本与 IoC 容器相同,就是从容器启动后,到第一次被请求后初始化该实例,一直存活到容器退出。所以说这种类型对象的"寿命"基本和 IoC 容器相同。 + + - prototype: IoC 容器在接收到该类型对象的请求后,会每次都重新生成一个新的实例对象给请求方。也就是说,如果一个实例对象不能共享给所有对象的话,那就需要将该 bean 的 scope 声明为 prototype。 + + - request + + - session + + - global session + + - 自定义 scope + + ### Spring 容器之 ApplicationContext + 作为 Spring 提供的较之 BeanFactory 更为先进的 IoC 容器实现,ApplicationContext 除了拥有 BeanFactory 的所有功能外,还进一步扩展了基本容器的功能,包括 BeanFactoryPostProcessor、BeanPostProcessor 以及其他特殊类型 bean 的自动识别、容器启动后 bean 实例的自动初始化、国际化的信息支持、容器内时间发布等。 + + Spring 为 BeanFactory 类型容器提供了 XmlBeanFactory 实现。相应地,它也为 ApplicationContext 类型容器提供了一下几个常用实现。 + - FileSystemXmlApplicationContext + + 在默认情况下,从文件系统加载 bean 定义以及相关资源的 ApplicationContext 实现。 + + - ClassPathXmlApplicationContext + + 在默认情况下,从 Classpath 加载 bean 定义以及相关资源的 ApplicationContext 实现。 + + - XmlWebApplicationContext + + Spring 提供的用于 Web 应用程序的 ApplicationContext 实现。 + +#### Spring 中的 Resource +Spring 框架内部使用 `org.springframework.core.io.Resource` 接口作为所有资源的抽象和访问接口。比如: + +``` java + +BeanFactory beanfactory = new XmlBeanFactory(new ClassPathResource("...")); + +``` + +其中 ClassPathResource 就是 Resource 的一个特定类型的实现,代表的是位于 Classpath 里面的资源。 + +Resource 接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。 + +- ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的 ByteArrayInputStream 并返回。 + +- ClassPathResource。该实现从 Java 应用程序的 ClassPath 中加载具体资源并并进行封装,可以使用指定的类加载器(ClassLoader)或者给定的类进行资源加载。 + +- FileSystemResource。对 java.io.File 类型的封装,故可以以文件或者 URL 的形式对该类型的资源进行访问,只要可以与 File 进行的交互,基本上跟 FileSystemResource 也可以。 + +- UrlResource。通过 java.net.URL 进行的具体资源查找定位的实现类,内部委派 URL 进行具体的资源操作。 + +- InputStreamResource。将给定的 InputStream 视为一种资源的 Resource 实现类,较为少用。可能的情况下,以 ByteArrayResource 以及其它形式资源取而代之。 + +`ResourceLoader` diff --git a/web/spring.md b/web/spring.md new file mode 100644 index 0000000..33023b5 --- /dev/null +++ b/web/spring.md @@ -0,0 +1,7 @@ +# Spring 全家桶 +- [Spring 配置](spring-config.md) +- [Spring IOC](spring-ioc.md) +- Spring AOP +- SpringMVC +- SpringBoot +- SpringCloud \ No newline at end of file AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル
uint32 age = 2;
AltStyle によって変換されたページ (->オリジナル) / アドレス: モード: デフォルト 音声ブラウザ ルビ付き 配色反転 文字拡大 モバイル