diff --git a/Business/DemoBusiness/src/main/java/com/yc/other/thread/ThreadActivity.java b/Business/DemoBusiness/src/main/java/com/yc/other/thread/ThreadActivity.java index 017441a76..b31cbfa37 100644 --- a/Business/DemoBusiness/src/main/java/com/yc/other/thread/ThreadActivity.java +++ b/Business/DemoBusiness/src/main/java/com/yc/other/thread/ThreadActivity.java @@ -8,6 +8,7 @@ import com.yc.other.R; +import java.util.HashMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -426,7 +427,6 @@ public void run() { } - private void test8_1() { Object co = new Object(); System.out.println(co); @@ -501,6 +501,10 @@ private void test9_1() { } catch (Exception e) { System.err.println("捕获到异常了"); } + + HashMap map = new HashMap<>(); + map.put("1","doubi"); + String s = map.get("1"); } public class ExceptionThread1 implements Runnable { diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/00.Java\346\225\260\346\215\256\347\273\223\346\236\204\351\227\256\351\242\230.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/00.Java\346\225\260\346\215\256\347\273\223\346\236\204\351\227\256\351\242\230.md" index 08d164463..9114ffa66 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/00.Java\346\225\260\346\215\256\347\273\223\346\236\204\351\227\256\351\242\230.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/00.Java\346\225\260\346\215\256\347\273\223\346\236\204\351\227\256\351\242\230.md" @@ -1,16 +1,5 @@ #### 目录介绍 -- 3.0.0.3 Collection集合和Map集合的区别?Map集合的特点?说下Map集合整体结构? -- 3.0.0.4 Java集合框架中有哪些类?都有什么特点?集合框架用到Collection接口,这个接口有何特点? - 3.0.1.0 HashSet和TreeSet的区别?是如何保证唯一值的,底层怎么做到的? -- 3.0.1.3 HashMap有哪些特点,简单说一下?HashMap内部的结构是怎样的?简单说一下什么是桶,作用是什么? -- 3.0.1.4 当有键值对插入时,HashMap会发生什么 ? 对于查找一个key时,HashMap会发生什么 ? -- 3.0.1.5 HashMap和Hashtable的区别?HashMap在put、get元素的过程?体现了什么数据结构? -- 3.0.1.6 如何保证HashMap线程安全?底层怎么实现的?HashMap是有序的吗?如何实现有序? -- 3.0.1.7 HashMap存储两个对象的hashcode相同会发生什么?如果两个键的hashcode相同,你如何获取值对象? -- 3.0.1.8 HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标? -- 3.0.1.9 为什么HashMap中String、Integer这样的包装类适合作为K?如果要用对象最为key,该如何操作? -- 3.0.2.0 HashMap是如何扩容的?如何理解HashMap的大小超过了负载因子定义的容量?重新调整HashMap大小存在什么问题吗? -- 3.0.2.1 HashMap是线程安全的吗?多线程条件下put存储数据会发生什么情况?如何理解它并发性? - 3.0.2.2 TreeMap集合结构有何特点?使用场景是什么?将"aababcabcdabcde"打印成a(5)b(4)c(3)d(2)e(1)? - 3.0.2.3 说一下HashSet集合特点?如何存储null值的?HashSet是如何去重操作?手写产生10个1-20之间的随机数要求随机数不能重复案例? diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/14.Set\351\233\206\345\220\210.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/01.Set\351\233\206\345\220\210.md" similarity index 100% rename from "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/14.Set\351\233\206\345\220\210.md" rename to "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/01.Set\351\233\206\345\220\210.md" diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/01.\346\225\260\346\215\256\351\233\206\345\220\210\345\272\217\350\250\200\344\273\213\347\273\215.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/01.\346\225\260\346\215\256\351\233\206\345\220\210\345\272\217\350\250\200\344\273\213\347\273\215.md" index 0b58e0a84..733a7c414 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/01.\346\225\260\346\215\256\351\233\206\345\220\210\345\272\217\350\250\200\344\273\213\347\273\215.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/01.\346\225\260\346\215\256\351\233\206\345\220\210\345\272\217\350\250\200\344\273\213\347\273\215.md" @@ -6,6 +6,9 @@ - 05.集合一些总结 +### 00.一些常见问题思考 +- Java集合框架中有哪些类?都有什么特点?集合框架用到Collection接口,这个接口有何特点? + ### 01.学习集合序言 - 很多写程序的人都听说过一个公式,程序 = 算法 + 数据结构。 @@ -142,15 +145,40 @@ -- **2. Set** - - TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。 - - HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。 - - LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。 -- **3.Map** + +#### 5.3 Map集合 +- Map集合从何而来 + * 我们通过什么东西来标识我们的学生在班级的唯一性. 我们都每一个学生应该存在一个学号 , 而这个学号是唯一的。那么我们就可以通过这个学号来表示我们学生在班级的唯一性。那么也就说,我们的学号和学生的姓名之间应该存在一个对应关系吧 + * 那么我们怎么存储这样对应关系的数据呢?针对这个情况java就给我们提供了另外一种集合进行表示,而这个集合就是Map 。Map集合结构是由两列组成,第一列被称之为键 , 第二例被称之为值 ; 并且我们都知道键应该是唯一的,而对值没有要求。 + ``` + 学号 姓名 + stu001 张三 + stu002 李四 + stu003 王五 + stu004 王五 + stu005 杨充逗比 + ``` +- Collection集合和Map集合的区别? + * Map集合由两列组成(双列集合) , 而Collection集合由一列组成(单列集合) ; Map集合是夫妻对 , Collection孤狼 + * Collection集合中的Set集合可以保证元素的唯一性 , 而Map集合中的键是唯一的 + * Collection集合的数据结构是对存储的元素是有效的,而Map集合的数据结构只和键有关系,和值没有关系 +- Map集合主要有哪些 - TreeMap:基于红黑树实现。 - HashMap:基于哈希表实现。 - HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。 - LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 +- Map集合的特点 + * 将键映射到值的对象 + * 一个映射不能包含重复的键 + * 每个键最多只能映射到一个值 + + + +- **2. Set** + - TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。 + - HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。 + - LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。 + - **4. Queue** - LinkedList:可以用它来实现双向队列。 - PriorityQueue:基于堆结构实现,可以用它来实现优先队列。 diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/12.HashMap2.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/07.HashMap\346\272\220\347\240\201\346\267\261\345\272\246\345\210\206\346\236\220.md" similarity index 95% rename from "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/12.HashMap2.md" rename to "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/07.HashMap\346\272\220\347\240\201\346\267\261\345\272\246\345\210\206\346\236\220.md" index 87f1a22e3..74ab4d7cd 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/12.HashMap2.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/07.HashMap\346\272\220\347\240\201\346\267\261\345\272\246\345\210\206\346\236\220.md" @@ -9,12 +9,16 @@ - 08.Hash函数实现 -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - +### 00.一些常见问题思考 +- HashMap有哪些特点,简单说一下?HashMap内部的结构是怎样的?简单说一下什么是桶,作用是什么? +- HashMap和Hashtable的区别?HashMap在put、get元素的过程?体现了什么数据结构? +- 当有键值对插入时,HashMap会发生什么 ? 对于查找一个key时,HashMap会发生什么 ? +- 如何保证HashMap线程安全?底层怎么实现的?HashMap是有序的吗?如何实现有序? +- HashMap存储两个对象的hashcode相同会发生什么?如果两个键的hashcode相同,你如何获取值对象? +- 为什么HashMap中String、Integer这样的包装类适合作为K?如果要用对象最为key,该如何操作? +- HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标? +- HashMap是如何扩容的?如何理解HashMap的大小超过了负载因子定义的容量?重新调整HashMap大小存在什么问题吗? +- HashMap是线程安全的吗?多线程条件下put存储数据会发生什么情况?如何理解它并发性? ### 01.HashMap内部结构 diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/07.Map\351\233\206\345\220\210.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/07.Map\351\233\206\345\220\210.md" deleted file mode 100644 index 6ea8b97ca..000000000 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/07.Map\351\233\206\345\220\210.md" +++ /dev/null @@ -1,288 +0,0 @@ -### 目录介绍 -- 1.Map集合概述和特点 - * 1.1 Map集合从何而来 - * 1.2 Collection集合和Map集合的区别? - * 1.3 Map集合的特点 -- 2.Map集合功能概述 - * 2.1 Map集合的是一个接口 - * 2.2 Map集合的功能概述 -- 3.集合嵌套 - * 3.1 集合嵌套之HashMap嵌套HashMap - * 3.2 集合嵌套之HashMap嵌套ArrayList - * 3.3 集合嵌套之ArrayList嵌套HashMap - - -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - - - -#### 1.Map集合概述和特点 -- **1.1 Map集合从何而来** - * 我们通过什么东西来标识我们的学生在班级的唯一性. 我们都每一个学生应该存在一个学号 , 而这个学号是唯一的。那么我们就可以通过这个学号来表示我们学生在班级的唯一性。那么也就说,我们的学号和学生的姓名之间应该存在一个对应关系吧 - * 那么我们怎么存储这样对应关系的数据呢?针对这个情况java就给我们提供了另外一种集合进行表示,而这个集合就是Map 。Map集合结构是由两列组成,第一列被称之为键 , 第二例被称之为值 ; 并且我们都知道键应该是唯一的,而对值没有要求。 - ``` - 学号 姓名 - stu001 张三 - stu002 李四 - stu003 王五 - stu004 王五 - stu005 杨充逗比 - ``` - - -- **1.2 Collection集合和Map集合的区别?** - * Map集合由两列组成(双列集合) , 而Collection集合由一列组成(单列集合) ; Map集合是夫妻对 , Collection孤狼 - * Collection集合中的Set集合可以保证元素的唯一性 , 而Map集合中的键是唯一的 - * Collection集合的数据结构是对存储的元素是有效的,而Map集合的数据结构只和键有关系,和值没有关系 - - - -- **1.3 Map集合的特点** - * 将键映射到值的对象 - * 一个映射不能包含重复的键 - * 每个键最多只能映射到一个值 - - - -### 2.Map集合整体结构 -#### 2.1 Map集合整体结构图形 -![image](https://upload-images.jianshu.io/upload_images/4432347-36193cefea2a5f0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - - -#### 2.2 整体结构介绍 -- HashMap 等其他 Map 实现则是都扩展了AbstractMap,里面包含了通用方法抽象。不同 Map的用途,从类图结构就能体现出来,设计目的已经体现在不同接口上。 -- Hashtable 比较特别,作为类似 Vector、Stack 的早期集合相关类型,它是扩展了 Dictionary 类的,类结构上与 HashMap 之类明显不同。 -- 大部分使用 Map 的场景,通常就是放入、访问或者删除,而对顺序没有特别要求,HashMap 在这种情况下基本是最好的选择。HashMap的性能表现非常依赖于哈希码的有效性,请务必掌握hashCode 和 equals 的一些基本约定,比如: - - equals 相等,hashCode 一定要相等。 - - 重写了 hashCode 也要重写 equals。 - - hashCode 需要保持一致性,状态改变返回的哈希值仍然要一致。 - - equals 的对称、反射、传递等特性。 - - - - -### 2.Map集合功能概述 -- **2.1 Map集合的是一个接口** - * HashMap(底层的数据结构是哈希表) - * TreeMap(二叉树) - - -- **2.2 Map集合的功能概述** - * Map集合的基本功能 - ``` - a:添加功能 - V put(K key,V value):添加元素。这个其实还有另一个功能?替换 - 如果键是第一次存储,就直接存储元素,返回null - 如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值 - b:删除功能 - void clear():移除所有的键值对元素 - V remove(Object key):根据键删除键值对元素,并把值返回 - c:判断功能 - boolean containsKey(Object key):判断集合是否包含指定的键 - boolean containsValue(Object value):判断集合是否包含指定的值 - boolean isEmpty():判断集合是否为空 - d:获取功能 - Set> entrySet(): 返回一个键值对的Set集合 - V get(Object key):根据键获取值 - Set keySet():获取集合中所有键的集合 - Collection values():获取集合中所有值的集合 - e:长度功能 - int size():返回集合中的键值对的对数 - ``` - - - - -#### 3.集合嵌套 -- 3.1 集合嵌套之HashMap嵌套HashMap - * 题目 - ``` - 传智播客 - jc 基础班 - 陈玉楼 20 - 高跃 22 - jy 就业班 - 李杰 21 - 曹石磊 23 - ``` -- 代码 - ``` - public static void main(String[] args) { - // 创建大的集合对象 - HashMap> czbkMap = new HashMap>() ; - // 创建基础班的HashMap集合 - HashMap jcHashMap = new HashMap() ; - // 添加元素 - jcHashMap.put("陈玉楼", 20) ; - jcHashMap.put("高跃", 22) ; - // 把jcHashMap存储到czbkMap - czbkMap.put("jc", jcHashMap) ; - // 创建就业班的HashMap集合 - HashMap jyHashMap = new HashMap() ; - // 添加元素 - jyHashMap.put("李杰", 21) ; - jyHashMap.put("曹石磊", 23) ; - // 把jcHashMap存储到czbkMap - czbkMap.put("jy", jyHashMap) ; - System.out.println("-------------------------------------------------------------------------------"); - // HashMap> czbkMap = new HashMap>() ; - Set>> entrySet = czbkMap.entrySet() ; - for(Entry> en : entrySet) { - // 获取键 - String key = en.getKey() ; - System.out.println(key); - // 获取值 - HashMap hashMap = en.getValue() ; - // 遍历hashMap - Set keySet = hashMap.keySet() ; - for(String hashMapkey : keySet){ - // 根据键找值 - Integer value = hashMap.get(hashMapkey) ; - // 输出 - System.out.println("\t" + hashMapkey + "\t" + value); - } - // 换行 - System.out.println(); - } - } - ``` - - -- 3.2 集合嵌套之HashMap嵌套ArrayList - * 题目 - ``` - 三国演义 - 吕布 - 周瑜 - 笑傲江湖 - 令狐冲 - 林平之 - 神雕侠侣 - 郭靖 - 杨过 - ``` -- 代码 - ``` - public static void main(String[] args) { - // 创建大的集合对象 - HashMap> xiaoShuoMap = new HashMap>() ; - // 创建三国演义的List集合 - ArrayList sgList = new ArrayList() ; - // 添加元素 - sgList.add("吕布") ; - sgList.add("周瑜") ; - // 把sgList添加到xiaoShuoMap中 - xiaoShuoMap.put("三国演义", sgList) ; - // 创建笑傲江湖的List集合 - ArrayList xaList = new ArrayList() ; - // 添加元素 - xaList.add("林平之") ; - xaList.add("令狐冲") ; - // 把sgList添加到xiaoShuoMap中 - xiaoShuoMap.put("笑傲江湖", xaList) ; - // 创建神雕侠侣的List集合 - ArrayList sdList = new ArrayList() ; - // 添加元素 - sdList.add("杨过") ; - sdList.add("郭靖") ; - // 把sgList添加到xiaoShuoMap中 - xiaoShuoMap.put("神雕侠侣", sdList) ; - // HashMap> xiaoShuoMap = new HashMap>() ; - Set>> entrySet = xiaoShuoMap.entrySet() ; - for(Entry> en : entrySet) { - // 获取键 - String key = en.getKey() ; - System.out.println(key); - // 获取值 - ArrayList value = en.getValue() ; - // 遍历 - for(String name : value) { - System.out.println("\t" + name); - } - System.out.println(); - } - } - ``` - - -- 3.3 集合嵌套之ArrayList嵌套HashMap - * 问题 - ``` - 周瑜---小乔 - 吕布---貂蝉 - - 郭靖---黄蓉 - 杨过---小龙女 - - 令狐冲---任盈盈 - 林平之---岳灵珊 - ``` -- 代码 - ``` - public static void main(String[] args) { - // 创建大的集合对象 - ArrayList> al = new ArrayList>() ; - // 创建小的HashMap集合 - HashMap sgHashMap = new HashMap() ; - HashMap xaHashMap = new HashMap() ; - HashMap sdHashMap = new HashMap() ; - // 添加元素 - sgHashMap.put("吕布", "貂蝉") ; - sgHashMap.put("周瑜", "小乔") ; - xaHashMap.put("林平之", "岳灵珊") ; - xaHashMap.put("令狐冲", "任盈盈") ; - sdHashMap.put("郭靖", "黄蓉") ; - sdHashMap.put("杨过", "小龙女") ; - // 把小的HashMap添加到al中 - al.add(sgHashMap) ; - al.add(xaHashMap) ; - al.add(sdHashMap) ; - // 遍历 - // ArrayList> al = new ArrayList>() ; - for(HashMap hm : al) { - // 根据键值对对象进行遍历 - Set> entrySet = hm.entrySet() ; - for(Entry en : entrySet) { - // 获取键 - String key = en.getKey() ; - // 获取值 - String value = en.getValue() ; - // 输出 - System.out.println(key + "---" + value); - } - System.out.println(); - } - } - ``` - - - -### 其他介绍 -#### 01.关于博客汇总链接 -- 1.[技术博客汇总](https://www.jianshu.com/p/614cb839182c) -- 2.[开源项目汇总](https://blog.csdn.net/m0_37700275/article/details/80863574) -- 3.[生活博客汇总](https://blog.csdn.net/m0_37700275/article/details/79832978) -- 4.[喜马拉雅音频汇总](https://www.jianshu.com/p/f665de16d1eb) -- 5.[其他汇总](https://www.jianshu.com/p/53017c3fc75d) - - - -#### 02.关于我的博客 -- github:https://github.com/yangchong211 -- 知乎:https://www.zhihu.com/people/yczbj/activities -- 简书:http://www.jianshu.com/u/b7b2c6ed9284 -- csdn:http://my.csdn.net/m0_37700275 -- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/ -- 开源中国:https://my.oschina.net/zbj1618/blog -- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1 -- 邮箱:yangchong211@163.com -- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV -- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles -- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e - - diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/11.HashMap1.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/08.HashMap\351\227\256\351\242\230\346\200\235\350\200\203.md" similarity index 61% rename from "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/11.HashMap1.md" rename to "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/08.HashMap\351\227\256\351\242\230\346\200\235\350\200\203.md" index 651514e54..c3b1696a7 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/11.HashMap1.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/08.HashMap\351\227\256\351\242\230\346\200\235\350\200\203.md" @@ -1,33 +1,17 @@ #### 目录介绍 -- 01.HashMap简单使用 - - 1.1 存储数据 - - 1.2 查找数据 - - 1.3 遍历数据 - - 1.4 结构图 -- 02.HashMap特点 - - 2.1 官方说明 - - 2.2 原理简单说明 - - 2.3 底层实现思考 -- 04.HashMap线程问题 -- 05.测试HashMap效率 -- 06.HashMap性能分析 - - - - -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - +- 01.容量和装载因子 +- 02.HashTable和HashMap +- 03.hashCode和equal +- 04.Key为何需要不可变 +- 05.HashMap为啥要扩容 +- 06.HashMap的table下标 ### 01.HashMap简单使用 #### 1.1 存储数据 - 执行下面的操作时: - ``` + ``` java HashMap map = new HashMap(); map.put("语文", 1); map.put("数学", 2); @@ -51,7 +35,7 @@ #### 1.3 遍历数据 - 第一种方式:for each 遍历 - 1: 获取所有的键对应的Set集合 ;2: 遍历Set获取每一个键 - ``` + ``` java // 先获取所有的键值对对象对应的Set集合 // Set> entrySet() 重点(*****) Set> entrySet = map.entrySet() ; @@ -77,7 +61,7 @@ } ``` - 第二种方式:使用迭代器 - ``` + ``` java //第二种方式,使用迭代器 Iterator> iterator = map.entrySet().iterator(); while (iterator.hasNext()){ @@ -119,7 +103,7 @@ ### 02.HashMap特点 #### 2.1 官方说明 - 在官方文档中是这样描述HashMap的: - - Hash table based**implementation of the Map interface**. This implementation provides all of the optional map operations, and permits null values and the null key. \(The HashMap class is roughly equivalent to Hashtable, except that it is**unsynchronized**and**permits nulls**.\) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time. + - Hash table based**implementation of the Map interface**. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is**unsynchronized**and**permits nulls**.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time. - 几个关键的信息:[博客](https://github.com/yangchong211/YCBlogs) - 基于Map接口实现、允许null键/值、是非同步(这点很重要,多线程注意)、不保证有序(比如插入的顺序)、也不保证序不随时间变化。 - 如何理解允许null键/值? @@ -133,24 +117,25 @@ #### 2.2 原理简单说明 - HashMap基于哈希思想,实现对数据的读写。 - - put存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量\(超过`Load Facotr`则resize为原来的2倍\)。 - - get获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals\(\)方法确定键值对。 + - put存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过`Load Facotr`则resize为原来的2倍)。 + - get获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。 - HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 - - 如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制\(默认是8\),则使用红黑树来替换链表,从而提高速度。 + - 如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。 #### 2.3 底层实现思考 - 底层是用什么实现的? - HashMap 实际上是**数组+链表+红黑树**的结合体…… - 为什么要使用链表? + - 才有hash算法,如果出现碰撞,则需要把碰撞的数据添加到链表上面。 - 为什么要使用红黑树? - - JDK1.8 开始 HashMap 通过使用红黑树来提高元素查找效率 + - 因为链表查找效率低,因此 JDK1.8 开始 HashMap 通过使用红黑树来提高元素查找效率。 ### 03.HashMap线程问题 - HashMap是非线程安全的,那么测试一下,先看下测试代码 - ``` + ``` java private HashMap map = new HashMap(); private void test(){ Thread t1 = new Thread() { @@ -223,7 +208,7 @@ } ``` - 就是启了6个线程,不断的往一个非线程安全的HashMap中put/get内容,put的内容很简单,key和value都是从0自增的整数(这个put的内容做的并不好,以致于后来干扰了我分析问题的思路)。对HashMap做并发写操作,我原以为只不过会产生脏数据的情况,但反复运行这个程序,会出现线程t1、t2被卡住的情况,多数情况下是一个线程被卡住另一个成功结束,偶尔会6个线程都被卡住。[博客](https://github.com/yangchong211/YCBlogs) - - 多线程下直接使用ConcurrentHashMap,解决了这个问题。 + - 多线程下直接使用 ConcurrentHashMap,解决了这个问题。 - CPU利用率过高一般是因为出现了出现了死循环,导致部分线程一直运行,占用cpu时间。问题原因就是HashMap是非线程安全的,多个线程put的时候造成了某个key值Entry key List的死循环,问题就这么产生了。 - 当另外一个线程get 这个Entry List 死循环的key的时候,这个get也会一直执行。最后结果是越来越多的线程死循环,最后导致卡住。我们一般认为HashMap重复插入某个值的时候,会覆盖之前的值,这个没错。但是对于多线程访问的时候,由于其内部实现机制(在多线程环境且未作同步的情况下,对同一个HashMap做put操作可能导致两个或以上线程同时做rehash动作,就可能导致循环键表出现,一旦出现线程将无法终止,持续占用CPU,导致CPU使用率居高不下),就可能出现安全问题了。 @@ -235,11 +220,9 @@ - 需求:测试下不同的初始化大小以及 key 值的 HashCode 值的分布情况的不同对 HashMap 效率的影响 - 测试初始化大小对 HashMap 的性能影响!!! - 首先来定义作为 Key 的类,`hashCode()` 方法直接返回其包含的属性 value。 - ``` + ``` java public class Key { - private int value; - public Key(int value) { this.value = value; } @@ -286,15 +269,14 @@ } ``` - 初始化大小从 100 到 100000 之间以 10 倍的倍数递增,向 HashMap 存入同等数据量的数据,观察不同 HashMap 存入数据消耗的总时间。例子中,各个Key对象之间的哈希码值各不相同,所以键值对在哈希桶数组中的分布可以说是很均匀的了,此时主要影响性能的就是扩容机制了,由上图可以看出各个初始化大小对 HashMap 的性能影响还是很大的。[博客](https://github.com/yangchong211/YCBlogs) - ``` + ``` java 2019-05-07 18:41:48.899 1522-1522/? I/System.out: yc---初始化大小是:20 , 所用时间:20毫秒 2019-05-07 18:41:48.906 1522-1522/? I/System.out: yc---初始化大小是:200 , 所用时间:5毫秒 2019-05-07 18:41:48.906 1522-1522/? I/System.out: yc---初始化大小是:2000 , 所用时间:0毫秒 ``` - 然后测试Key对象之间频繁发生哈希冲突时HashMap的性能 - 令 Key 类的 `hashCode()` 方法固定返回 100,则每个键值对在存入 HashMap 时,一定会发生哈希冲突。可以看到此时存入同等数据量的数据所用时间呈几何数增长了,此时主要影响性能的点就在于对哈希冲突的处理 - - - ``` + ``` java 2019-05-07 18:40:11.213 1003-1003/com.ycbjie.ycexpandview I/System.out: yc---初始化大小是:20 , 所用时间:281毫秒 2019-05-07 18:40:11.459 1003-1003/com.ycbjie.ycexpandview I/System.out: yc---初始化大小是:200 , 所用时间:246毫秒 2019-05-07 18:40:11.673 1003-1003/com.ycbjie.ycexpandview I/System.out: yc---初始化大小是:2000 , 所用时间:213毫秒 @@ -360,6 +342,108 @@ +### 01.容量和装载因子 +- 在HashMap中有两个很重要的参数,容量(Capacity)和负载因子(Load factor) + - 简单的说,Capacity就是bucket的大小,Loadfactor就是bucket填满程度的最大比例。 + - 如果对迭代性能要求很高的话,不要把`capacity`设置过大,也不要把`load factor`设置过小。 + - 当bucket中的entries的数目大于`capacity*load factor`时就需要调整bucket的大小为当前的2倍。 +- 什么是装载因子 + - 装载因子用于规定数组在自动扩容之前可以数据占有其容量的最高比例,即当数据量占有数组的容量达到这个比例后,数组将自动扩容。 + - 装载因子衡量的是一个散列表的空间的使用程度,装载因子越大表示散列表的装填程度越高,反之愈小。因此如果装载因子越大,则对空间的利用程度更高,相对应的是查找效率的降低。 + - 如果装载因子太小,那么数组的数据将过于稀疏,对空间的利用率低,官方默认的装载因子为0.75,是平衡空间利用率和运行效率两者之后的结果。 + - 如果在实际情况中,内存空间较多而对时间效率要求很高,可以选择降低装载因子的值;如果内存空间紧张而对时间效率要求不高,则可以选择提高装载因子的值。[博客](https://github.com/yangchong211/YCBlogs) + - 此外,即使装载因子和哈希算法设计得再合理,也不免会出现由于哈希冲突导致链表长度过长的情况,这将严重影响 HashMap 的性能。为了优化性能,从 JDK1.8 开始引入了红黑树,当链表长度超出 TREEIFY_THRESHOLD 规定的值时,链表就会被转换为红黑树,利用红黑树快速增删改查的特点以提高 HashMap 的性能。 + ``` + //序列化ID + private static final long serialVersionUID = 362498820763181265L; + + //哈希桶数组的默认容量 + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + + //网上很多文章都说这个值是哈希桶数组能够达到的最大容量,其实这样说并不准确 + //从 resize() 方法的扩容机制可以看出来,HashMap 每次扩容都是将数组的现有容量增大一倍 + //如果现有容量已大于或等于 MAXIMUM_CAPACITY ,则不允许再次扩容 + //否则即使此次扩容会导致容量超出 MAXIMUM_CAPACITY ,那也是允许的 + static final int MAXIMUM_CAPACITY = 1 << 30; + + //装载因子的默认值 + //装载因子用于规定数组在自动扩容之前可以数据占有其容量的最高比例,即当数据量占有数组的容量达到这个比例后,数组将自动扩容 + //装载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小 + //对于使用链表的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,则对空间的利用程度更高,相对应的是查找效率的降低 + //如果负载因子太小,那么数组的数据将过于稀疏,对空间的利用率低 + //官方默认的负载因子为0.75,是平衡空间利用率和运行效率两者之后的结果 + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + //为了提高效率,当链表的长度超出这个值时,就将链表转换为红黑树 + static final int TREEIFY_THRESHOLD = 8; + ``` +- HashMap的大小超过了负载因子(`load factor`)定义的容量,怎么办? + - 如果超过了负载因子(默认**0.75**),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。 + + + + +### 02.HashTable和HashMap +- HashTable和HashMap初始化与增长方式 + - 初始化时: + - HashTable在不指定容量的情况下的默认容量为11,且不要求底层数组的容量一定要为2的整数次幂;HashMap默认容量为16,且要求容量一定为2的整数次幂。 + - 扩容时:[博客](https://github.com/yangchong211/YCBlogs) + - Hashtable将容量变为原来的2倍加1;HashMap扩容将容量变为原来的2倍。 +- HashTable和HashMap线程安全性 + - HashTable其方法函数都是同步的(采用synchronized修饰),不会出现两个线程同时对数据进行操作的情况,因此保证了线程安全性。也正因为如此,在多线程运行环境下效率表现非常低下。因为当一个线程访问HashTable的同步方法时,其他线程也访问同步方法就会进入阻塞状态。比如当一个线程在添加数据时候,另外一个线程即使执行获取其他数据的操作也必须被阻塞,大大降低了程序的运行效率,在新版本中已被废弃,不推荐使用。 + - HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步(1)可以用Collections的synchronizedMap方法;(2)使用ConcurrentHashMap类,相较于HashTable锁住的是对象整体,ConcurrentHashMap基于lock实现锁分段技术。首先将Map存放的数据分成一段一段的存储方式,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。ConcurrentHashMap不仅保证了多线程运行环境下的数据访问安全性,而且性能上有长足的提升。 + + + + +### 03.hashCode和equal +- get和put的中的equals()和hashCode()的都有什么作用? + - 通过对key的hashCode()进行hashing,并计算下标( `(n-1) & hash`),从而获得buckets的位置。 + - 如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点 + + + + +### 04.Key为何需要不可变 +- 为什么HashMap中String、Integer这样的包装类适合作为K? + - String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率 + - 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 + - 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况 + - 如果对象在创建后它的哈希值发生了变化,则Map对象很可能就定位不到映射的位置。 +- 想要让自己的Object作为K应该怎么办呢?[博客](https://github.com/yangchong211/YCBlogs) + - 重写hashCode()和equals()方法 + - 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; + - 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性; +- 总结 + - 采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。 + + + + + +### 05.HashMap为啥要扩容 +- HashMap是为啥要扩容 + - 当链表数组的容量超过初始容量*加载因子(默认0.75)时,再散列将链表数组扩大2倍,把原链表数组的搬移到新的数组中。 + - 为什么需要使用加载因子?为什么需要扩容呢?因为如果填充比很大,说明利用的空间很多,如果一直不进行扩容的话,链表就会越来越长,这样查找的效率很低,扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率。 +- 重新调整HashMap大小存在什么问题吗?[博客](https://github.com/yangchong211/YCBlogs) + - 当多线程的情况下,可能产生条件竞争。当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。 + - 在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。 + + + + +### 06.HashMap的table下标 +- 它是用自己的hash方法确定下标 + - HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均; + - 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题; +- 不直接使用hashCode()处理后的哈希值 + - hashCode()方法返回的是int整数类型,其范围为-(2^31)~(2^31-1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置; +- 为什么数组长度要保证为2的幂次方呢? + - 只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,即实现了key的定位,2的幂次方也可以减少冲突次数,提高HashMap的查询效率;[博客](https://github.com/yangchong211/YCBlogs) + - 如果 length 为 2 的次幂 则 length-1 转化为二进制必定是 11111……的形式,在于 h 的二进制与操作效率会非常的快,而且空间不浪费;如果 length 不是 2 的次幂,比如 length 为 15,则 length - 1 为 14,对应的二进制为 1110,在于 h 与操作,最后一位都为 0 ,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。 + + + ### 其他介绍 #### 01.关于博客汇总链接 @@ -385,3 +469,4 @@ - 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e + diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/15.TreeMap1.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/09.TreeMap\345\216\237\347\220\206\346\267\261\345\272\246\345\210\206\346\236\220.md" similarity index 53% rename from "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/15.TreeMap1.md" rename to "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/09.TreeMap\345\216\237\347\220\206\346\267\261\345\272\246\345\210\206\346\236\220.md" index 3bd92122a..cf33dac97 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/15.TreeMap1.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/09.TreeMap\345\216\237\347\220\206\346\267\261\345\272\246\345\210\206\346\236\220.md" @@ -161,6 +161,167 @@ } ``` +- 01.构造函数和成员变量 +- 02.put插入函数源码 +- 03.get获取函数源码 +- 04.如何保证有序性 + + + +### 好消息 +- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! +- **链接地址:https://github.com/yangchong211/YCBlogs** +- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! + + + +### 02.put函数源码 +- 如果存在的话,old value被替换;如果不存在的话,则新添一个节点,然后对做红黑树的平衡操作。 + ``` + public V put(K key, V value) { + Entry t = root; + if (t == null) { + compare(key, key); // type (and possibly null) check + root = new Entry<>(key, value, null); + size = 1; + modCount++; + return null; + } + int cmp; + Entry parent; + // split comparator and comparable paths + Comparator cpr = comparator; + // 如果该节点存在,则替换值直接返回 + if (cpr != null) { + do { + parent = t; + cmp = cpr.compare(key, t.key); + if (cmp < 0) + t = t.left; + else if (cmp > 0) + t = t.right; + else + return t.setValue(value); + } while (t != null); + } + else { + if (key == null) + throw new NullPointerException(); + @SuppressWarnings("unchecked") + Comparable k = (Comparable) key; + do { + parent = t; + cmp = k.compareTo(t.key); + if (cmp < 0) + t = t.left; + else if (cmp > 0) + t = t.right; + else + return t.setValue(value); + } while (t != null); + } + // 如果该节点未存在,则新建 + Entry e = new Entry<>(key, value, parent); + if (cmp < 0) + parent.left = e; + else + parent.right = e; + + // 红黑树平衡调整 + fixAfterInsertion(e); + size++; + modCount++; + return null; + } + ``` + +### 03.get获取函数源码 +- get函数则相对来说比较简单,以log\(n\)的复杂度进行get。[博客](https://github.com/yangchong211/YCBlogs) + ``` + final Entry getEntry(Object key) { + // Offload comparator-based version for sake of performance + if (comparator != null) + return getEntryUsingComparator(key); + if (key == null) + throw new NullPointerException(); + @SuppressWarnings("unchecked") + Comparable k = (Comparable) key; + Entry p = root; + // 按照二叉树搜索的方式进行搜索,搜到返回 + while (p != null) { + int cmp = k.compareTo(p.key); + if (cmp < 0) + p = p.left; + else if (cmp > 0) + p = p.right; + else + return p; + } + return null; + } + public V get(Object key) { + Entry p = getEntry(key); + return (p==null ? null : p.value); + } + ``` + + +### 04.如何保证有序性 +- TreeMap是如何保证其迭代输出是有序的呢? + - 其实从宏观上来讲,就相当于树的中序遍历\(LDR\)。我们先看一下迭代输出的步骤 + ``` + for(Entry entry : tmap.entrySet()) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } + ``` + - for语句会做如下转换为: + ``` java + for(Iterator> it = tmap.entrySet().iterator() ; tmap.hasNext(); ) { + Entry entry = it.next(); + System.out.println(entry.getKey() + ": " + entry.getValue()); + } + ``` + - 在**it.next\(\)**的调用中会使用**nextEntry**调用`successor`这个是过的后继的重点。 +- 然后看一下successor函数 + ``` java + static TreeMap.Entry successor(Entry t) { + if (t == null) + return null; + else if (t.right != null) { + // 有右子树的节点,后继节点就是右子树的“最左节点” + // 因为“最左子树”是右子树的最小节点 + Entry p = t.right; + while (p.left != null) + p = p.left; + return p; + } else { + // 如果右子树为空,则寻找当前节点所在左子树的第一个祖先节点 + // 因为左子树找完了,根据LDR该D了 + Entry p = t.parent; + Entry ch = t; + // 保证左子树 + while (p != null && ch == p.right) { + ch = p; + p = p.parent; + } + return p; + } + } + ``` + - 怎么理解这个successor呢?只要记住,这个是中序遍历就好了,L-D-R。具体细节如下:[博客](https://github.com/yangchong211/YCBlogs) + - **a. 空节点,没有后继** + - **b. 有右子树的节点,后继就是右子树的“最左节点”** + - **c. 无右子树的节点,后继就是该节点所在左子树的第一个祖先节点** + - a.好理解,不过b, c,有点像绕口令啊,没关系,上图举个例子就懂了! + - **有右子树的节点**,节点的下一个节点,肯定在右子树中,而右子树中“最左”的那个节点则是右树中最小的一个,那么当然是**右子树的“最左节点”**,就好像下图所示: + - ![image](https://upload-images.jianshu.io/upload_images/4432347-db8ab85faac10262.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + - **无右子树的节点**,先找到这个节点所在的左子树\(右图\),那么这个节点所在的左子树的父节点\(绿色节点\),就是下一个节点。 + - ![image](https://upload-images.jianshu.io/upload_images/4432347-db115296284072e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + + + diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/13.HashMap3.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/13.HashMap3.md" deleted file mode 100644 index bd0b5339d..000000000 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/13.HashMap3.md" +++ /dev/null @@ -1,142 +0,0 @@ -#### 目录介绍 -- 01.容量和装载因子 -- 02.HashTable和HashMap -- 03.hashCode和equal -- 04.Key为何需要不可变 -- 05.HashMap为啥要扩容 -- 06.HashMap的table下标 - - -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - - -### 01.容量和装载因子 -- 在HashMap中有两个很重要的参数,容量\(Capacity\)和负载因子\(Load factor\) - - 简单的说,Capacity就是bucket的大小,Loadfactor就是bucket填满程度的最大比例。 - - 如果对迭代性能要求很高的话,不要把`capacity`设置过大,也不要把`load factor`设置过小。 - - 当bucket中的entries的数目大于`capacity*load factor`时就需要调整bucket的大小为当前的2倍。 -- 什么是装载因子 - - 装载因子用于规定数组在自动扩容之前可以数据占有其容量的最高比例,即当数据量占有数组的容量达到这个比例后,数组将自动扩容。 - - 装载因子衡量的是一个散列表的空间的使用程度,装载因子越大表示散列表的装填程度越高,反之愈小。因此如果装载因子越大,则对空间的利用程度更高,相对应的是查找效率的降低。 - - 如果装载因子太小,那么数组的数据将过于稀疏,对空间的利用率低,官方默认的装载因子为0.75,是平衡空间利用率和运行效率两者之后的结果。 - - 如果在实际情况中,内存空间较多而对时间效率要求很高,可以选择降低装载因子的值;如果内存空间紧张而对时间效率要求不高,则可以选择提高装载因子的值。[博客](https://github.com/yangchong211/YCBlogs) - - 此外,即使装载因子和哈希算法设计得再合理,也不免会出现由于哈希冲突导致链表长度过长的情况,这将严重影响 HashMap 的性能。为了优化性能,从 JDK1.8 开始引入了红黑树,当链表长度超出 TREEIFY_THRESHOLD 规定的值时,链表就会被转换为红黑树,利用红黑树快速增删改查的特点以提高 HashMap 的性能。 - ``` - //序列化ID - private static final long serialVersionUID = 362498820763181265L; - - //哈希桶数组的默认容量 - static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; - - //网上很多文章都说这个值是哈希桶数组能够达到的最大容量,其实这样说并不准确 - //从 resize() 方法的扩容机制可以看出来,HashMap 每次扩容都是将数组的现有容量增大一倍 - //如果现有容量已大于或等于 MAXIMUM_CAPACITY ,则不允许再次扩容 - //否则即使此次扩容会导致容量超出 MAXIMUM_CAPACITY ,那也是允许的 - static final int MAXIMUM_CAPACITY = 1 << 30; - - //装载因子的默认值 - //装载因子用于规定数组在自动扩容之前可以数据占有其容量的最高比例,即当数据量占有数组的容量达到这个比例后,数组将自动扩容 - //装载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小 - //对于使用链表的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,则对空间的利用程度更高,相对应的是查找效率的降低 - //如果负载因子太小,那么数组的数据将过于稀疏,对空间的利用率低 - //官方默认的负载因子为0.75,是平衡空间利用率和运行效率两者之后的结果 - static final float DEFAULT_LOAD_FACTOR = 0.75f; - - //为了提高效率,当链表的长度超出这个值时,就将链表转换为红黑树 - static final int TREEIFY_THRESHOLD = 8; - ``` -- HashMap的大小超过了负载因子\(`load factor`\)定义的容量,怎么办? - - 如果超过了负载因子\(默认**0.75**\),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。 - - - - -### 02.HashTable和HashMap -- HashTable和HashMap初始化与增长方式 - - 初始化时: - - HashTable在不指定容量的情况下的默认容量为11,且不要求底层数组的容量一定要为2的整数次幂;HashMap默认容量为16,且要求容量一定为2的整数次幂。 - - 扩容时:[博客](https://github.com/yangchong211/YCBlogs) - - Hashtable将容量变为原来的2倍加1;HashMap扩容将容量变为原来的2倍。 -- HashTable和HashMap线程安全性 - - HashTable其方法函数都是同步的(采用synchronized修饰),不会出现两个线程同时对数据进行操作的情况,因此保证了线程安全性。也正因为如此,在多线程运行环境下效率表现非常低下。因为当一个线程访问HashTable的同步方法时,其他线程也访问同步方法就会进入阻塞状态。比如当一个线程在添加数据时候,另外一个线程即使执行获取其他数据的操作也必须被阻塞,大大降低了程序的运行效率,在新版本中已被废弃,不推荐使用。 - - HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步(1)可以用Collections的synchronizedMap方法;(2)使用ConcurrentHashMap类,相较于HashTable锁住的是对象整体,ConcurrentHashMap基于lock实现锁分段技术。首先将Map存放的数据分成一段一段的存储方式,然后给每一段数据分配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。ConcurrentHashMap不仅保证了多线程运行环境下的数据访问安全性,而且性能上有长足的提升。 - - - - -### 03.hashCode和equal -- get和put的中的equals()和hashCode()的都有什么作用? - - 通过对key的hashCode\(\)进行hashing,并计算下标\( `(n-1) & hash`\),从而获得buckets的位置。 - - 如果产生碰撞,则利用key.equals\(\)方法去链表或树中去查找对应的节点 - - - - -### 04.Key为何需要不可变 -- 为什么HashMap中String、Integer这样的包装类适合作为K? - - String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率 - - 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 - - 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况 - - 如果对象在创建后它的哈希值发生了变化,则Map对象很可能就定位不到映射的位置。 -- 想要让自己的Object作为K应该怎么办呢?[博客](https://github.com/yangchong211/YCBlogs) - - 重写hashCode()和equals()方法 - - 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; - - 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性; -- 总结 - - 采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。 - - - - - -### 05.HashMap为啥要扩容 -- HashMap是为啥要扩容 - - 当链表数组的容量超过初始容量*加载因子(默认0.75)时,再散列将链表数组扩大2倍,把原链表数组的搬移到新的数组中。为什么需要使用加载因子?为什么需要扩容呢?因为如果填充比很大,说明利用的空间很多,如果一直不进行扩容的话,链表就会越来越长,这样查找的效率很低,扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率。 -- 重新调整HashMap大小存在什么问题吗?[博客](https://github.com/yangchong211/YCBlogs) - - 当多线程的情况下,可能产生条件竞争。当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。 - - - - -### 06.HashMap的table下标 -- 它是用自己的hash方法确定下标 - - HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均; - - 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题; -- 不直接使用hashCode()处理后的哈希值 - - hashCode()方法返回的是int整数类型,其范围为-(2^31)~(2^31-1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置; -- 为什么数组长度要保证为2的幂次方呢? - - 只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,即实现了key的定位,2的幂次方也可以减少冲突次数,提高HashMap的查询效率;[博客](https://github.com/yangchong211/YCBlogs) - - 如果 length 为 2 的次幂 则 length-1 转化为二进制必定是 11111……的形式,在于 h 的二进制与操作效率会非常的快,而且空间不浪费;如果 length 不是 2 的次幂,比如 length 为 15,则 length - 1 为 14,对应的二进制为 1110,在于 h 与操作,最后一位都为 0 ,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。 - - - - -### 其他介绍 -#### 01.关于博客汇总链接 -- 1.[技术博客汇总](https://www.jianshu.com/p/614cb839182c) -- 2.[开源项目汇总](https://blog.csdn.net/m0_37700275/article/details/80863574) -- 3.[生活博客汇总](https://blog.csdn.net/m0_37700275/article/details/79832978) -- 4.[喜马拉雅音频汇总](https://www.jianshu.com/p/f665de16d1eb) -- 5.[其他汇总](https://www.jianshu.com/p/53017c3fc75d) - - - -#### 02.关于我的博客 -- github:https://github.com/yangchong211 -- 知乎:https://www.zhihu.com/people/yczbj/activities -- 简书:http://www.jianshu.com/u/b7b2c6ed9284 -- csdn:http://my.csdn.net/m0_37700275 -- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/ -- 开源中国:https://my.oschina.net/zbj1618/blog -- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1 -- 邮箱:yangchong211@163.com -- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV -- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles -- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e - - - diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/16.TreeMap2.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/16.TreeMap2.md" deleted file mode 100644 index 82641b2d3..000000000 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/16.TreeMap2.md" +++ /dev/null @@ -1,197 +0,0 @@ -#### 目录介绍 -- 01.构造函数和成员变量 -- 02.put插入函数源码 -- 03.get获取函数源码 -- 04.如何保证有序性 - - - -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - - -### 02.put函数源码 -- 如果存在的话,old value被替换;如果不存在的话,则新添一个节点,然后对做红黑树的平衡操作。 - ``` - public V put(K key, V value) { - Entry t = root; - if (t == null) { - compare(key, key); // type (and possibly null) check - root = new Entry<>(key, value, null); - size = 1; - modCount++; - return null; - } - int cmp; - Entry parent; - // split comparator and comparable paths - Comparator cpr = comparator; - // 如果该节点存在,则替换值直接返回 - if (cpr != null) { - do { - parent = t; - cmp = cpr.compare(key, t.key); - if (cmp < 0) - t = t.left; - else if (cmp > 0) - t = t.right; - else - return t.setValue(value); - } while (t != null); - } - else { - if (key == null) - throw new NullPointerException(); - @SuppressWarnings("unchecked") - Comparable k = (Comparable) key; - do { - parent = t; - cmp = k.compareTo(t.key); - if (cmp < 0) - t = t.left; - else if (cmp > 0) - t = t.right; - else - return t.setValue(value); - } while (t != null); - } - // 如果该节点未存在,则新建 - Entry e = new Entry<>(key, value, parent); - if (cmp < 0) - parent.left = e; - else - parent.right = e; - - // 红黑树平衡调整 - fixAfterInsertion(e); - size++; - modCount++; - return null; - } - ``` - -### 03.get获取函数源码 -- get函数则相对来说比较简单,以log\(n\)的复杂度进行get。[博客](https://github.com/yangchong211/YCBlogs) - ``` - final Entry getEntry(Object key) { - // Offload comparator-based version for sake of performance - if (comparator != null) - return getEntryUsingComparator(key); - if (key == null) - throw new NullPointerException(); - @SuppressWarnings("unchecked") - Comparable k = (Comparable) key; - Entry p = root; - // 按照二叉树搜索的方式进行搜索,搜到返回 - while (p != null) { - int cmp = k.compareTo(p.key); - if (cmp < 0) - p = p.left; - else if (cmp > 0) - p = p.right; - else - return p; - } - return null; - } - public V get(Object key) { - Entry p = getEntry(key); - return (p==null ? null : p.value); - } - ``` - - -### 04.如何保证有序性 -- TreeMap是如何保证其迭代输出是有序的呢? - - 其实从宏观上来讲,就相当于树的中序遍历\(LDR\)。我们先看一下迭代输出的步骤 - ``` - for(Entry entry : tmap.entrySet()) { - System.out.println(entry.getKey() + ": " + entry.getValue()); - } - ``` - - for语句会做如下转换为: - ```java - for(Iterator> it = tmap.entrySet().iterator() ; tmap.hasNext(); ) { - Entry entry = it.next(); - System.out.println(entry.getKey() + ": " + entry.getValue()); - } - ``` - - 在**it.next\(\)**的调用中会使用**nextEntry**调用`successor`这个是过的后继的重点。 -- 然后看一下successor函数 - ``` - static TreeMap.Entry successor(Entry t) { - if (t == null) - return null; - else if (t.right != null) { - // 有右子树的节点,后继节点就是右子树的“最左节点” - // 因为“最左子树”是右子树的最小节点 - Entry p = t.right; - while (p.left != null) - p = p.left; - return p; - } else { - // 如果右子树为空,则寻找当前节点所在左子树的第一个祖先节点 - // 因为左子树找完了,根据LDR该D了 - Entry p = t.parent; - Entry ch = t; - // 保证左子树 - while (p != null && ch == p.right) { - ch = p; - p = p.parent; - } - return p; - } - } - ``` - - 怎么理解这个successor呢?只要记住,这个是中序遍历就好了,L-D-R。具体细节如下:[博客](https://github.com/yangchong211/YCBlogs) - - **a. 空节点,没有后继** - - **b. 有右子树的节点,后继就是右子树的“最左节点”** - - **c. 无右子树的节点,后继就是该节点所在左子树的第一个祖先节点** - - a.好理解,不过b, c,有点像绕口令啊,没关系,上图举个例子就懂了! - - **有右子树的节点**,节点的下一个节点,肯定在右子树中,而右子树中“最左”的那个节点则是右树中最小的一个,那么当然是**右子树的“最左节点”**,就好像下图所示: - - ![image](https://upload-images.jianshu.io/upload_images/4432347-db8ab85faac10262.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - **无右子树的节点**,先找到这个节点所在的左子树\(右图\),那么这个节点所在的左子树的父节点\(绿色节点\),就是下一个节点。 - - ![image](https://upload-images.jianshu.io/upload_images/4432347-db115296284072e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - - - - - - - -### 其他介绍 -#### 01.关于博客汇总链接 -- 1.[技术博客汇总](https://www.jianshu.com/p/614cb839182c) -- 2.[开源项目汇总](https://blog.csdn.net/m0_37700275/article/details/80863574) -- 3.[生活博客汇总](https://blog.csdn.net/m0_37700275/article/details/79832978) -- 4.[喜马拉雅音频汇总](https://www.jianshu.com/p/f665de16d1eb) -- 5.[其他汇总](https://www.jianshu.com/p/53017c3fc75d) - - - -#### 02.关于我的博客 -- github:https://github.com/yangchong211 -- 知乎:https://www.zhihu.com/people/yczbj/activities -- 简书:http://www.jianshu.com/u/b7b2c6ed9284 -- csdn:http://my.csdn.net/m0_37700275 -- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/ -- 开源中国:https://my.oschina.net/zbj1618/blog -- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1 -- 邮箱:yangchong211@163.com -- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV -- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles -- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e - - - - - - - - - - - diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/20.LinkedHashMap1.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/20.LinkedHashMap1.md" index a30194dd5..a648da6f9 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/20.LinkedHashMap1.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/20.LinkedHashMap1.md" @@ -6,10 +6,6 @@ - 05.LinkedHashMap案例 -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! ### 01.LinkedHashMap使用 diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/21.LinkedHashMap2.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/21.LinkedHashMap2.md" index 984134d58..8e6c9ebd3 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/21.LinkedHashMap2.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/21.LinkedHashMap2.md" @@ -65,7 +65,7 @@ #### 1.2 afterNodeInsertion函数 - 代码如下所示 - ``` + ``` java void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry first; // 如果定义了溢出规则,则执行相应的溢出 @@ -79,7 +79,7 @@ #### 1.3 afterNodeRemoval函数 - 代码如下所示 - ``` + ``` java void afterNodeRemoval(Node e) { // unlink // 从链表中移除节点 LinkedHashMap.Entry p = @@ -124,7 +124,7 @@ ### 03.构造函数分析 - 构造函数如下所示,一般用无参构造方法。 - ```java + ``` java //自定义初始容量与装载因子 //内部元素按照插入顺序进行排序 public LinkedHashMap(int initialCapacity, float loadFactor) { @@ -287,7 +287,9 @@ ### 07.LRUCache拓展训练 -- 在 Android 的实际应用开发中,LRUCache 算法是很常见的,一种典型的用途就是用来在内存中缓存 Bitmap,因为从 IO 流中读取 Bitmap 的代价很大,为了防止多次从磁盘中读取某张图片,所以可以选择在内存中缓存 Bitmap。但内存空间也是有限的,所以也不能每张图片都进行缓存,需要有选择性地缓存一定数量的图片,而最近最少使用算法(LRUCache)是一个可行的选择 +- 在 Android 的实际应用开发中 + - LRUCache 算法是很常见的,一种典型的用途就是用来在内存中缓存 Bitmap,因为从 IO 流中读取 Bitmap 的代价很大,为了防止多次从磁盘中读取某张图片,所以可以选择在内存中缓存 Bitmap。 + - 但内存空间也是有限的,所以也不能每张图片都进行缓存,需要有选择性地缓存一定数量的图片,而最近最少使用算法(LRUCache)是一个可行的选择。 - 这里利用 LinkedHashMap 可以按照元素使用顺序进行排列的特点,来实现一个 LRUCache 策略的缓存 ``` public class LRUCache { diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/22.HashSet.md" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/22.HashSet\345\216\237\347\220\206\346\267\261\345\272\246\345\210\206\346\236\220.md" similarity index 93% rename from "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/22.HashSet.md" rename to "Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/22.HashSet\345\216\237\347\220\206\346\267\261\345\272\246\345\210\206\346\236\220.md" index 6269e5dca..ac859fa59 100644 --- "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/22.HashSet.md" +++ "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/22.HashSet\345\216\237\347\220\206\346\267\261\345\272\246\345\210\206\346\236\220.md" @@ -7,13 +7,6 @@ -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - - ### 01.HashSet特点 - HashSet特点说明 - HashSet 实现了 Set 接口,不允许插入重复的元素,允许包含 null 元素,且不保证元素迭代顺序,特别是不保证该顺序恒久不变 diff --git "a/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/image/HashMap\347\273\223\346\236\204\345\233\276.png" "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/image/HashMap\347\273\223\346\236\204\345\233\276.png" new file mode 100644 index 000000000..da143bfbf Binary files /dev/null and "b/Read/ReadJavaWiki/04.\346\225\260\346\215\256\347\273\223\346\236\204/image/HashMap\347\273\223\346\236\204\345\233\276.png" differ diff --git "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/18.\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\2052.md" "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/11.\347\224\237\344\272\247\350\200\205\344\270\216\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" similarity index 71% rename from "Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/18.\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\2052.md" rename to "Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/11.\347\224\237\344\272\247\350\200\205\344\270\216\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" index 2cd8828c6..2b1dfdac7 100644 --- "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/18.\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\2052.md" +++ "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/11.\347\224\237\344\272\247\350\200\205\344\270\216\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" @@ -1,3 +1,272 @@ +#### 目录介绍 +- 01.生产者消费者模型发生场景 +- 02.什么是生产者消费者模型 +- 03.一生产与一消费案例 +- 04.多生产与多消费案例 +- 05.会遇到哪些关键问题 +- 06.如何解决关键问题 + + + +### 好消息 +- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! +- **链接地址:https://github.com/yangchong211/YCBlogs** +- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! + + + + +### 01.生产者消费者模型发生场景 +- 生产者消费者模型发生场景 + - 多线程-并发协作(生产者消费者模型)。多线程同步的经典问题! + + +### 02.什么是生产者消费者模型 +- 什么是生产者消费者模型,举例式说明 + - 准确说应该是“生产者-消费者-仓储”模型 + - 1、生产者仅仅在仓储未满时候生产,仓满则停止生产。 + - 2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。 + - 3、当消费者发现仓储没产品可消费时候会通知生产者生产。 + - 4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。 +- 专业术语说明什么是生产者消费者模型 + - 生产者消费者模型通过一个缓存队列,既解决了生产者和消费者之间强耦合的问题,又平衡了生产者和消费者的处理能力。 + - 具体规则:生产者只在缓存区未满时进行生产,缓存区满时生产者进程被阻塞;消费者只在缓存区非空时进行消费,缓存区为空时消费者进程被阻塞;当消费者发现缓存区为空时会通知生产者生产;当生产者发现缓存区满时会通知消费者消费。 + - 实现关键:synchronized保证对象只能被一个线程占用;wait()让当前线程进入等待状态,并释放它所持有的锁;notify()¬ifyAll()唤醒一个(所有)正处于等待状态的线程 + + +### 03.一生产与一消费案例 +- 下面代码案例是一个生产者,一个消费者的模式。 + - 假设场景:一个String对象,其中生产者为其设置值,消费者拿走其中的值,不断的循环往复,实现生产者/消费者的情形。 + - 实现方式:**wait\(\)/notify\(\)实现** +- 生产者 + ``` + public class Product { + private String lock; + + public Product(String lock) { + super(); + this.lock = lock; + } + public void setValue(){ + try { + synchronized (lock) { + if(!StringObject.value.equals("")){ + //有值,不生产 + lock.wait(); + } + String value = System.currentTimeMillis()+""+System.nanoTime(); + System.out.println("set的值是:"+value); + StringObject.value = value; + lock.notify(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + ``` +- 消费者 + ``` + public class Consumer { + private String lock; + + public Consumer(String lock) { + super(); + this.lock = lock; + } + public void getValue(){ + try { + synchronized (lock) { + if(StringObject.value.equals("")){ + //没值,不进行消费 + lock.wait(); + } + System.out.println("get的值是:"+StringObject.value); + StringObject.value = ""; + lock.notify(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + ``` +- 生产者线程 + ``` + public class ThreadProduct extends Thread{ + private Product product; + + public ThreadProduct(Product product) { + super(); + this.product = product; + } + @Override + public void run() { + //死循环,不断的生产 + while(true){ + product.setValue(); + } + } + } + ``` +- 消费者线程 + ``` + public class ThreadConsumer extends Thread{ + private Consumer consumer; + + public ThreadConsumer(Consumer consumer) { + super(); + this.consumer = consumer; + } + @Override + public void run() { + //死循环,不断的消费 + while(true){ + consumer.getValue(); + } + } + } + ``` +- 开启生产者/消费者模式 + ``` + public class Test { + public static void main(String[] args) throws InterruptedException { + String lock = new String(""); + Product product = new Product(lock); + Consumer consumer = new Consumer(lock); + ThreadProduct pThread = new ThreadProduct(product); + ThreadConsumer cThread = new ThreadConsumer(consumer); + pThread.start(); + cThread.start(); + } + } + ``` +- 输出结果: + > set的值是:148827033184127168687409691 + > get的值是:148827033184127168687409691 + > set的值是:148827033184127168687449887 + > get的值是:148827033184127168687449887 + > set的值是:148827033184127168687475117 + > get的值是:148827033184127168687475117 + + + +### 04.多生产与多消费案例 +- **特殊情况:** 按照上述一生产与一消费的情况,通过创建多个生产者和消费者线程,实现多生产与多消费的情况,将会出现“假死”。 +- **具体原因:** 多个生产者和消费者线程。当全部运行后,生产者线程生产数据后,可能唤醒的同类即生产者线程。此时可能会出现如下情况:所有生产者线程进入等待状态,然后消费者线程消费完数据后,再次唤醒的还是消费者线程,直至所有消费者线程都进入等待状态,此时将进入“假死”。 +- **解决方法:** 将notify\(\)或signal\(\)方法改为notifyAll\(\)或signalAll\(\)方法,这样就不怕因为唤醒同类而进入“假死”状态了。 +- **Condition方式实现** +- 生产者 + ``` + public class Product { + private ReentrantLock lock; + private Condition condition; + + public Product(ReentrantLock lock, Condition condition) { + super(); + this.lock = lock; + this.condition = condition; + } + + public void setValue() { + try { + lock.lock(); + while (!StringObject.value.equals("")) { + // 有值,不生产 + condition.await(); + } + String value = System.currentTimeMillis() + "" + System.nanoTime(); + System.out.println("set的值是:" + value); + StringObject.value = value; + condition.signalAll(); + } catch (InterruptedException e) { + e.printStackTrace(); + }finally { + lock.unlock(); + } + } + } + ``` +- 消费者 + ``` + public class Consumer { + private ReentrantLock lock; + private Condition condition; + + public Consumer(ReentrantLock lock,Condition condition) { + super(); + this.lock = lock; + this.condition = condition; + } + public void getValue(){ + try { + lock.lock(); + while(StringObject.value.equals("")){ + //没值,不进行消费 + condition.await(); + } + System.out.println("get的值是:"+StringObject.value); + StringObject.value = ""; + condition.signalAll(); + + } catch (InterruptedException e) { + e.printStackTrace(); + }finally { + lock.unlock(); + } + } + } + ``` + - 生产者线程和消费者线程与一生产一消费的模式相同。[博客](https://github.com/yangchong211/YCBlogs) +- 开启多生产/多消费模式 + ``` + public static void main(String[] args) throws InterruptedException { + ReentrantLock lock = new ReentrantLock(); + Condition newCondition = lock.newCondition(); + Product product = new Product(lock,newCondition); + Consumer consumer = new Consumer(lock,newCondition); + for(int i=0;i<3;i++){ + ThreadProduct pThread = new ThreadProduct(product); + ThreadConsumer cThread = new ThreadConsumer(consumer); + pThread.start(); + cThread.start(); + } + } + ``` +- 输出结果: + > set的值是:148827212374628960540784817 + > get的值是:148827212374628960540784817 + > set的值是:148827212374628960540810047 + > get的值是:148827212374628960540810047 +- 可见交替地进行get/set实现多生产/多消费模式。**注意:相比一生产一消费的模式,改动了两处。①signal\(\)-->signalAll\(\)避免进入“假死”状态。②if\(\)判断-->while\(\)循环,重新判断条件,避免逻辑混乱。** + + + + + + +### 05.会遇到哪些关键问题 +- 如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。 +- 如何保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据? +- 实际开发时案例 + - 这种并发情况下,一般服务端程序用的比较多,Android端的应用程序较少有什么并发情况。虽然事实如此,但是构建生产者-消费者模型,是线程间协作的思想,工作线程的协助是为了让UI线程更好的完成工作,提高用户体验。比如,图片选择查看器案例! + + + + +### 06.如何解决关键问题 +- 如何保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据,思考一下? +- 解决思路可以简单概括为: + - 生产者持续生产,直到缓冲区满,满时阻塞;缓冲区不满后,继续生产; + - 消费者持续消费,直到缓冲区空,空时阻塞;缓冲区不空后,继续消费; + - 生产者和消费者都可以有多个; +- 能够让消费者和生产者在各自满足条件需要阻塞时能够起到正确的作用 + - wait()/notify()方式; + - await()/signal()方式; + - BlockingQueue阻塞队列方式; + - PipedInputStream/PipedOutputStream方式; +- 一般可以使用第一种和第三种方式实现逻辑。[博客](https://github.com/yangchong211/YCBlogs) + 生产者-消费者模式是一个十分经典的多线程并发协作的模式,弄懂生产者-消费者问题能够让我们对并发编程的理解加深。所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。但是,这个共享数据区域中应该具备这样的线程间并发协作的功能: @@ -723,4 +992,42 @@ void signalAll():与1的区别在于能够唤醒所有等待在condition上的 可以看出,使用BlockingQueue来实现生产者-消费者很简洁,这正是利用了BlockingQueue插入和获取数据附加阻塞操作的特性。 -关于生产者-消费者实现的三中方式,到这里就全部总结出来,如果觉得不错的话,请点赞,也算是给我的鼓励,在此表示感谢! \ No newline at end of file + + +### 其他介绍 +#### 01.关于博客汇总链接 +- 1.[技术博客汇总](https://www.jianshu.com/p/614cb839182c) +- 2.[开源项目汇总](https://blog.csdn.net/m0_37700275/article/details/80863574) +- 3.[生活博客汇总](https://blog.csdn.net/m0_37700275/article/details/79832978) +- 4.[喜马拉雅音频汇总](https://www.jianshu.com/p/f665de16d1eb) +- 5.[其他汇总](https://www.jianshu.com/p/53017c3fc75d) + + + +#### 02.关于我的博客 +- github:https://github.com/yangchong211 +- 知乎:https://www.zhihu.com/people/yczbj/activities +- 简书:http://www.jianshu.com/u/b7b2c6ed9284 +- csdn:http://my.csdn.net/m0_37700275 +- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/ +- 开源中国:https://my.oschina.net/zbj1618/blog +- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1 +- 邮箱:yangchong211@163.com +- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV +- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles +- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e + + + + + + + + + + + + + + + diff --git "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/11.\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/11.\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" deleted file mode 100644 index fc63775bf..000000000 --- "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/11.\347\224\237\344\272\247\350\200\205\346\266\210\350\264\271\350\200\205\346\250\241\345\236\213.md" +++ /dev/null @@ -1,309 +0,0 @@ -#### 目录介绍 -- 01.生产者消费者模型发生场景 -- 02.什么是生产者消费者模型 -- 03.一生产与一消费案例 -- 04.多生产与多消费案例 -- 05.会遇到哪些关键问题 -- 06.如何解决关键问题 - - - -### 好消息 -- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢! -- **链接地址:https://github.com/yangchong211/YCBlogs** -- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! - - - - -### 01.生产者消费者模型发生场景 -- 生产者消费者模型发生场景 - - 多线程-并发协作(生产者消费者模型)。多线程同步的经典问题! - - -### 02.什么是生产者消费者模型 -- 什么是生产者消费者模型,举例式说明 - - 准确说应该是“生产者-消费者-仓储”模型 - - 1、生产者仅仅在仓储未满时候生产,仓满则停止生产。 - - 2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。 - - 3、当消费者发现仓储没产品可消费时候会通知生产者生产。 - - 4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。 -- 专业术语说明什么是生产者消费者模型 - - 生产者消费者模型通过一个缓存队列,既解决了生产者和消费者之间强耦合的问题,又平衡了生产者和消费者的处理能力。 - - 具体规则:生产者只在缓存区未满时进行生产,缓存区满时生产者进程被阻塞;消费者只在缓存区非空时进行消费,缓存区为空时消费者进程被阻塞;当消费者发现缓存区为空时会通知生产者生产;当生产者发现缓存区满时会通知消费者消费。 - - 实现关键:synchronized保证对象只能被一个线程占用;wait()让当前线程进入等待状态,并释放它所持有的锁;notify()¬ifyAll()唤醒一个(所有)正处于等待状态的线程 - - -### 03.一生产与一消费案例 -- 下面代码案例是一个生产者,一个消费者的模式。 - - 假设场景:一个String对象,其中生产者为其设置值,消费者拿走其中的值,不断的循环往复,实现生产者/消费者的情形。 - - 实现方式:**wait\(\)/notify\(\)实现** -- 生产者 - ``` - public class Product { - private String lock; - - public Product(String lock) { - super(); - this.lock = lock; - } - public void setValue(){ - try { - synchronized (lock) { - if(!StringObject.value.equals("")){ - //有值,不生产 - lock.wait(); - } - String value = System.currentTimeMillis()+""+System.nanoTime(); - System.out.println("set的值是:"+value); - StringObject.value = value; - lock.notify(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - ``` -- 消费者 - ``` - public class Consumer { - private String lock; - - public Consumer(String lock) { - super(); - this.lock = lock; - } - public void getValue(){ - try { - synchronized (lock) { - if(StringObject.value.equals("")){ - //没值,不进行消费 - lock.wait(); - } - System.out.println("get的值是:"+StringObject.value); - StringObject.value = ""; - lock.notify(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - ``` -- 生产者线程 - ``` - public class ThreadProduct extends Thread{ - private Product product; - - public ThreadProduct(Product product) { - super(); - this.product = product; - } - @Override - public void run() { - //死循环,不断的生产 - while(true){ - product.setValue(); - } - } - } - ``` -- 消费者线程 - ``` - public class ThreadConsumer extends Thread{ - private Consumer consumer; - - public ThreadConsumer(Consumer consumer) { - super(); - this.consumer = consumer; - } - @Override - public void run() { - //死循环,不断的消费 - while(true){ - consumer.getValue(); - } - } - } - ``` -- 开启生产者/消费者模式 - ``` - public class Test { - public static void main(String[] args) throws InterruptedException { - String lock = new String(""); - Product product = new Product(lock); - Consumer consumer = new Consumer(lock); - ThreadProduct pThread = new ThreadProduct(product); - ThreadConsumer cThread = new ThreadConsumer(consumer); - pThread.start(); - cThread.start(); - } - } - ``` -- 输出结果: - > set的值是:148827033184127168687409691 - > get的值是:148827033184127168687409691 - > set的值是:148827033184127168687449887 - > get的值是:148827033184127168687449887 - > set的值是:148827033184127168687475117 - > get的值是:148827033184127168687475117 - - - -### 04.多生产与多消费案例 -- **特殊情况:** 按照上述一生产与一消费的情况,通过创建多个生产者和消费者线程,实现多生产与多消费的情况,将会出现“假死”。 -- **具体原因:** 多个生产者和消费者线程。当全部运行后,生产者线程生产数据后,可能唤醒的同类即生产者线程。此时可能会出现如下情况:所有生产者线程进入等待状态,然后消费者线程消费完数据后,再次唤醒的还是消费者线程,直至所有消费者线程都进入等待状态,此时将进入“假死”。 -- **解决方法:** 将notify\(\)或signal\(\)方法改为notifyAll\(\)或signalAll\(\)方法,这样就不怕因为唤醒同类而进入“假死”状态了。 -- **Condition方式实现** -- 生产者 - ``` - public class Product { - private ReentrantLock lock; - private Condition condition; - - public Product(ReentrantLock lock, Condition condition) { - super(); - this.lock = lock; - this.condition = condition; - } - - public void setValue() { - try { - lock.lock(); - while (!StringObject.value.equals("")) { - // 有值,不生产 - condition.await(); - } - String value = System.currentTimeMillis() + "" + System.nanoTime(); - System.out.println("set的值是:" + value); - StringObject.value = value; - condition.signalAll(); - } catch (InterruptedException e) { - e.printStackTrace(); - }finally { - lock.unlock(); - } - } - } - ``` -- 消费者 - ``` - public class Consumer { - private ReentrantLock lock; - private Condition condition; - - public Consumer(ReentrantLock lock,Condition condition) { - super(); - this.lock = lock; - this.condition = condition; - } - public void getValue(){ - try { - lock.lock(); - while(StringObject.value.equals("")){ - //没值,不进行消费 - condition.await(); - } - System.out.println("get的值是:"+StringObject.value); - StringObject.value = ""; - condition.signalAll(); - - } catch (InterruptedException e) { - e.printStackTrace(); - }finally { - lock.unlock(); - } - } - } - ``` - - 生产者线程和消费者线程与一生产一消费的模式相同。[博客](https://github.com/yangchong211/YCBlogs) -- 开启多生产/多消费模式 - ``` - public static void main(String[] args) throws InterruptedException { - ReentrantLock lock = new ReentrantLock(); - Condition newCondition = lock.newCondition(); - Product product = new Product(lock,newCondition); - Consumer consumer = new Consumer(lock,newCondition); - for(int i=0;i<3;i++){ - ThreadProduct pThread = new ThreadProduct(product); - ThreadConsumer cThread = new ThreadConsumer(consumer); - pThread.start(); - cThread.start(); - } - } - ``` -- 输出结果: - > set的值是:148827212374628960540784817 - > get的值是:148827212374628960540784817 - > set的值是:148827212374628960540810047 - > get的值是:148827212374628960540810047 -- 可见交替地进行get/set实现多生产/多消费模式。**注意:相比一生产一消费的模式,改动了两处。①signal\(\)-->signalAll\(\)避免进入“假死”状态。②if\(\)判断-->while\(\)循环,重新判断条件,避免逻辑混乱。** - - - - - - -### 05.会遇到哪些关键问题 -- 如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。 -- 如何保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据? -- 实际开发时案例 - - 这种并发情况下,一般服务端程序用的比较多,Android端的应用程序较少有什么并发情况。虽然事实如此,但是构建生产者-消费者模型,是线程间协作的思想,工作线程的协助是为了让UI线程更好的完成工作,提高用户体验。比如,图片选择查看器案例! - - - - -### 06.如何解决关键问题 -- 如何保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据,思考一下? -- 解决思路可以简单概括为: - - 生产者持续生产,直到缓冲区满,满时阻塞;缓冲区不满后,继续生产; - - 消费者持续消费,直到缓冲区空,空时阻塞;缓冲区不空后,继续消费; - - 生产者和消费者都可以有多个; -- 能够让消费者和生产者在各自满足条件需要阻塞时能够起到正确的作用 - - wait()/notify()方式; - - await()/signal()方式; - - BlockingQueue阻塞队列方式; - - PipedInputStream/PipedOutputStream方式; -- 一般可以使用第一种和第三种方式实现逻辑。[博客](https://github.com/yangchong211/YCBlogs) - - - - -### 其他介绍 -#### 01.关于博客汇总链接 -- 1.[技术博客汇总](https://www.jianshu.com/p/614cb839182c) -- 2.[开源项目汇总](https://blog.csdn.net/m0_37700275/article/details/80863574) -- 3.[生活博客汇总](https://blog.csdn.net/m0_37700275/article/details/79832978) -- 4.[喜马拉雅音频汇总](https://www.jianshu.com/p/f665de16d1eb) -- 5.[其他汇总](https://www.jianshu.com/p/53017c3fc75d) - - - -#### 02.关于我的博客 -- github:https://github.com/yangchong211 -- 知乎:https://www.zhihu.com/people/yczbj/activities -- 简书:http://www.jianshu.com/u/b7b2c6ed9284 -- csdn:http://my.csdn.net/m0_37700275 -- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/ -- 开源中国:https://my.oschina.net/zbj1618/blog -- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1 -- 邮箱:yangchong211@163.com -- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV -- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles -- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e - - - - - - - - - - - - - - - diff --git "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/12.\347\272\277\347\250\213\346\215\225\350\216\267\345\274\202\345\270\270\347\232\204\345\210\206\346\236\220.md" "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/12.\347\272\277\347\250\213\346\215\225\350\216\267\345\274\202\345\270\270\347\232\204\345\210\206\346\236\220.md" index f25533522..3e6c2d98a 100644 --- "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/12.\347\272\277\347\250\213\346\215\225\350\216\267\345\274\202\345\270\270\347\232\204\345\210\206\346\236\220.md" +++ "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/12.\347\272\277\347\250\213\346\215\225\350\216\267\345\274\202\345\270\270\347\232\204\345\210\206\346\236\220.md" @@ -12,8 +12,10 @@ ### 01.为什么不能抛出到外部线程捕获 - 在Java中,线程中的异常是不能抛出到调用该线程的外部方法中捕获的。 - 为什么不能抛出到外部线程捕获? - - 因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。 + - JVM的这种设计源自于这样一种理念:因为线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。 - ”基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行try catch并处理掉。换句话说,我们不能捕获从线程中逃逸的异常。 + - 基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。 +- 换句话说,我们不能捕获从线程中逃逸的异常。 ### 02.怎么进行的限制 @@ -23,11 +25,6 @@ - 而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常)。 -### 03.JVM中这种设计理念 -- JVM的这种设计源自于这样一种理念:“线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。 -- 换句话说,我们不能捕获从线程中逃逸的异常。 - - ### 04.测试run方法中抛运行时异常 - 下面是一个在run方法中定义了一个运行时的异常 ``` @@ -76,7 +73,7 @@ ### 05.Thread.UncaughtExceptionHandler - 其实jdk5之前的解决办法是线程组,在jdk5之后新引进了一个接口Thread.UncaughtExceptionHandler这个接口的作用就是为每个创建的线程都附着一个异常处理器,然后我们为了使用到这个组件去创建线程,又引进了一个新的对象 ThreadFactory对象,这个也是一个接口,我们重写的方法newThread就是它返回新线程的方法,在这个方法中为每个新创建的线程都设置上面的Thread.UncaughtExceptionHandler,然后将带有异常处理器的线程返回看下面的代码 -- 第一步:创建Thread.UncaughtExceprionhandler的实例 +- 第一步:创建Thread.UncaughtExceptionHandler 的实例 ``` class MyUncaughtExceptionhandler implements Thread.UncaughtExceptionHandler { diff --git "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/13.ThreadLocal\345\210\206\346\236\220.md" "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/13.ThreadLocal\345\210\206\346\236\220.md" index c5aa71f88..4e21f64e2 100644 --- "a/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/13.ThreadLocal\345\210\206\346\236\220.md" +++ "b/Read/ReadJavaWiki/06.\347\272\277\347\250\213\347\237\245\350\257\206/13.ThreadLocal\345\210\206\346\236\220.md" @@ -7,11 +7,18 @@ - 06.ThreadLocal使用场景 + +### 00.思考一些问题分析 +- ThreadLocal是干什么的?为何需要它? + + ### 01.ThreadLocal作用介绍 - ThreadLocal的作用主要是用来做什么的? - - 这个类提供线程本地变量。这个变量不同于线程中普通的副本变量,因为每个线程都持有一个属于它自己的变量,并且可以通过 get、set 方法来对其进行访问和修改,并且初始时会独立创建变量的副本保存在每个线程中。ThreadLocal 对象一般是一个线程的私有字段,用于和线程中的某个信息相关联(比如:用户 ID、交易 ID)。 + - 这个类提供线程本地变量。这个变量不同于线程中普通的副本变量,因为每个线程都持有一个属于它自己的变量,并且可以通过 get、set 方法来对其进行访问和修改,并且初始时会独立创建变量的副本保存在每个线程中。 + - ThreadLocal 对象一般是一个线程的私有字段,用于和线程中的某个信息相关联(比如:用户 ID、交易 ID)。 - 每个线程都拥有对其保存的线程局部变量副本的隐式引用,只要线程处于活动状态并且其对应的 ThreadLocal 对象字段可用时就可以访问。线程结束后,所有的线程保存的 ThreadLocal 对象将会被垃圾回收器回收,除非还有其他的引用指向这些存在的对象。 -- 从官方说明中,我们可以知道利用这个类我们可以在每一个线程中都保存一个变量,并且不同线程中的这个变量互不冲突,我们还可以通过 ThreadLocal 对象的 get、set 方法来读取、修改这个变量的值。 +- 官方说明是怎么样的? + - 我们可以知道利用这个类我们可以在每一个线程中都保存一个变量,并且不同线程中的这个变量互不冲突,我们还可以通过 ThreadLocal 对象的 get、set 方法来读取、修改这个变量的值。 @@ -25,7 +32,7 @@ - 还有很多文章在对比 ThreadLocal 与 synchronize 的异同。既然是作比较,那应该是认为这两者解决相同或类似的问题。 - 上面的描述,问题在于,ThreadLocal并不解决多线程共享变量的问题。既然变量不共享,那就更谈不上同步的问题。 - 合理的理解 - - ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例,后文会详细阐述)。这里有几点需要注意 + - ThreadLocal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例,后文会详细阐述)。这里有几点需要注意 - 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是ThreadLocal 命名的由来 - 既然每个 Thread 有自己的实例副本,且其它Thread不可访问,那就不存在多线程间共享的问题。既无共享,何来同步问题,又何来解决同步问题一说? - 那 ThreadLocal 到底解决了什么问题,又适用于什么样的场景? @@ -38,7 +45,7 @@ ### 03.ThreadLocal用法 - 下面通过如下代码说明 ThreadLocal 的使用方式 - ```java + ``` java private void test(){ int threads = 3; final CountDownLatch countDownLatch = new CountDownLatch(threads); @@ -97,7 +104,7 @@ } ``` - 实例分析,ThreadLocal本身支持范型。该例使用了 StringBuilder 类型的 ThreadLocal 变量。可通过 ThreadLocal 的 get() 方法读取 StringBuidler 实例,也可通过 set(T t)方法设置StringBuilder。上述代码执行结果如下 - ```java + ``` java 2019-09-02 18:28:37.393 20999-21043/com.ycbjie.ycscrollpager I/System.out: Thread name:thread - 1 , ThreadLocal hashcode:117278486, Instance hashcode:218610308, Value:0 2019-09-02 18:28:37.394 20999-21043/com.ycbjie.ycscrollpager I/System.out: Thread name:thread - 1 , ThreadLocal hashcode:117278486, Instance hashcode:218610308, Value:01 2019-09-02 18:28:37.395 20999-21044/com.ycbjie.ycscrollpager I/System.out: Thread name:thread - 2 , ThreadLocal hashcode:117278486, Instance hashcode:221696621, Value:0 diff --git "a/Read/ReadLeetcode/04.\346\240\210/01.\351\230\237\345\210\227\345\237\272\347\241\200\344\273\213\347\273\215.md" "b/Read/ReadLeetcode/05.\351\230\237\345\210\227/01.\351\230\237\345\210\227\345\237\272\347\241\200\344\273\213\347\273\215.md" similarity index 100% rename from "Read/ReadLeetcode/04.\346\240\210/01.\351\230\237\345\210\227\345\237\272\347\241\200\344\273\213\347\273\215.md" rename to "Read/ReadLeetcode/05.\351\230\237\345\210\227/01.\351\230\237\345\210\227\345\237\272\347\241\200\344\273\213\347\273\215.md" diff --git "a/Read/ReadLeetcode/04.\346\240\210/02.\351\230\237\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206.md" "b/Read/ReadLeetcode/05.\351\230\237\345\210\227/02.\351\230\237\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206.md" similarity index 100% rename from "Read/ReadLeetcode/04.\346\240\210/02.\351\230\237\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206.md" rename to "Read/ReadLeetcode/05.\351\230\237\345\210\227/02.\351\230\237\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206.md" diff --git "a/Read/ReadLeetcode/04.\346\240\210/03.\351\241\272\345\272\217\351\230\237\345\210\227.md" "b/Read/ReadLeetcode/05.\351\230\237\345\210\227/03.\351\241\272\345\272\217\351\230\237\345\210\227.md" similarity index 100% rename from "Read/ReadLeetcode/04.\346\240\210/03.\351\241\272\345\272\217\351\230\237\345\210\227.md" rename to "Read/ReadLeetcode/05.\351\230\237\345\210\227/03.\351\241\272\345\272\217\351\230\237\345\210\227.md" diff --git "a/Read/ReadLeetcode/04.\346\240\210/04.\351\223\276\345\274\217\351\230\237\345\210\227.md" "b/Read/ReadLeetcode/05.\351\230\237\345\210\227/04.\351\223\276\345\274\217\351\230\237\345\210\227.md" similarity index 100% rename from "Read/ReadLeetcode/04.\346\240\210/04.\351\223\276\345\274\217\351\230\237\345\210\227.md" rename to "Read/ReadLeetcode/05.\351\230\237\345\210\227/04.\351\223\276\345\274\217\351\230\237\345\210\227.md" diff --git "a/Read/ReadLeetcode/04.\346\240\210/05.\351\230\273\345\241\236\351\230\237\345\210\227.md" "b/Read/ReadLeetcode/05.\351\230\237\345\210\227/05.\351\230\273\345\241\236\351\230\237\345\210\227.md" similarity index 100% rename from "Read/ReadLeetcode/04.\346\240\210/05.\351\230\273\345\241\236\351\230\237\345\210\227.md" rename to "Read/ReadLeetcode/05.\351\230\237\345\210\227/05.\351\230\273\345\241\236\351\230\237\345\210\227.md" diff --git "a/Read/ReadLeetcode/04.\346\240\210/06.\345\271\266\345\217\221\351\230\237\345\210\227.md" "b/Read/ReadLeetcode/05.\351\230\237\345\210\227/06.\345\271\266\345\217\221\351\230\237\345\210\227.md" similarity index 100% rename from "Read/ReadLeetcode/04.\346\240\210/06.\345\271\266\345\217\221\351\230\237\345\210\227.md" rename to "Read/ReadLeetcode/05.\351\230\237\345\210\227/06.\345\271\266\345\217\221\351\230\237\345\210\227.md" diff --git "a/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/26.ServiceLoader\345\256\236\350\267\265.md" "b/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/26.ServiceLoader\345\256\236\350\267\265.md" new file mode 100644 index 000000000..f544dbe48 --- /dev/null +++ "b/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/26.ServiceLoader\345\256\236\350\267\265.md" @@ -0,0 +1,61 @@ +#### 目录介绍 +- 概述 + - 项目背景 + - 设计目标 +- 方案设计 + - 整体架构 + - 架构设计图 + - UML设计图 + - 功能设计 + - 关键流程图 + - 接口设计图 + - 模块间依赖关系 +- 其他设计(Optional) + - 性能设计 + - 稳定性设计 + - 灰度设计 + - 降级设计 + - 异常设计 +- 排期与计划 + - 排期节点 + - 落实反馈 + + + +思考一下问题 +1.项目业务组件依赖sdk组件,如何使用组件中功能且减少代码耦合性? +2.使用注解表记类,编译期扫描class文件如何生成SPI配置⽂件,又如何生成ServiceRegistry类? +举一个案例 +比如想实现一个推送的功能,不管是在那个组件中能快速使用,减少耦合性。核心还是用到接口+实现类+反射创建对象的形式。 +注解类:ServiceProvider,ServiceProviderInterface。注意注解标注是运行时…… +定义推送的接口:PushServiceProvider,使用注解ServiceProviderInterface标记 +定义推送接口实现类:PushServiceImpl,接口具体实现类,使用注解ServiceProvider标记 +apt生成类:PushService接口委托类,ServiceRegistry +SPI机制是什么 +SPI ,全称为 Service Provider Interface,是一种服务发现机制。 +它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类,一般用来启用框架扩展和替换组件。 +作用是什么 +解耦合:把接口的具体实现类的全名写成配置文件,然后进行读取,生成具体的实现类对象,进行服务的调用,从而实现解耦。 +SPI机制原理 +1.使用@ServiceProvider来标注该类提供哪些服务接口,标识服务的优先级与别名(加载时可通过别名指定加载某个服务) +2.在构建过程中,java代码编译完成之后,执行自定义的gradle任务,扫描所有class,生成SPI配置文件,存放接口、接口实现类(服务)与实现类优先级信息 +3.解析SPI配置文件,通过Javapoet生成ServiceRegistry.java,将配置文件中接口与实现类的映射关系转译成代码,生成静态代码块可在类加载时自动完成 服务的注册 +4.把ServiceRegistry.java编译成class文件,被ClassLoader加载后可在ServiceLoader中正常引用完成服务的创建 +遇到的问题 +1.ServiceRegistry是如何解析生成该编译文件的,这块还要在熟悉下apt,找到项目中具体的实现代码分析下。 +2.比如在PushService代码中,成员位置调用ServiceLoader.laod创建接口实现类对象,如果不是单利创建对象,那么会不会多次调用load创建多次对象?是否可以改成在静态final形式或者在静态代码块中调用? +SPI原理图 + +问题解答说明 +1.如何扫描所有class? +写一个plugin插件,开启一个task任务,然后去加载代码文件目录或者jar,遍历取后缀名为.class的字节码并存储到list集合中。 +2.委派代理的作用? +接口+接口实现类+接口委派类,调用的时候使用委派类,而具体的实现逻辑细节在实现类中。主要是起到代码隔离和解耦合的操作。 +3.如何生成apt代码? +使用javaopet库,然后通过拼接类的路径,类的修饰符,返回值,参数等多个元素,最后用jarvFile写到指定的目录文件。 +4.ServiceLoader.laod多次会怎样? +会创建多个对象。所以这个一般都是保持单利的形式 +5.如何实现priority优先级? +首先获取ServiceProvider注解,然后通过注解拿到里面的参数。然后遍历所有被注解标记的类,最后把优先级写到file文件中。 + + diff --git "a/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/7.3FileProvider\351\200\232\344\277\241\346\265\201\347\250\213\345\233\276.png" "b/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/07.3FileProvider\351\200\232\344\277\241\346\265\201\347\250\213\345\233\276.png" similarity index 100% rename from "Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/7.3FileProvider\351\200\232\344\277\241\346\265\201\347\250\213\345\233\276.png" rename to "Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/07.3FileProvider\351\200\232\344\277\241\346\265\201\347\250\213\345\233\276.png" diff --git "a/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/26.1spi\346\265\201\347\250\213\345\233\276.png" "b/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/26.1spi\346\265\201\347\250\213\345\233\276.png" new file mode 100644 index 000000000..dc3cd6b06 Binary files /dev/null and "b/Read/ReadMeWiki/00.\346\226\271\346\241\210\345\256\236\350\267\265/image/26.1spi\346\265\201\347\250\213\345\233\276.png" differ diff --git "a/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/00.\346\235\250\345\205\205\344\270\252\344\272\272\347\256\200\345\216\206.md" "b/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/00.\346\235\250\345\205\205\344\270\252\344\272\272\347\256\200\345\216\206.md" index 6f1b5e202..5e2730f86 100644 --- "a/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/00.\346\235\250\345\205\205\344\270\252\344\272\272\347\256\200\345\216\206.md" +++ "b/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/00.\346\235\250\345\205\205\344\270\252\344\272\272\347\256\200\345\216\206.md" @@ -40,6 +40,7 @@ - 在滴滴开发经历 - 按时按量完成需求开发,bug<3,线上工单<3/年,优化地图&导航异常降级,完善核心链路技术埋点,完善监控体系。 - 快速适应flutter混合开发,承担地图&导航&定位&其他等模块大改版,不断拉齐Android和iOS两端差异化,承接基础业务开发。 + - 负责改版中flutter容器开发,地图业务channel通信,改造方案设计,地图&Flutter容器多点触摸事件分发处理。 - 开发磁盘缓存查看工具极大提高测试效率,开发地图mock各种场景工具,地图模拟行走工具,导航模拟行走工具,主要是辅助测试童鞋模拟国外业务场景测试,提高测试效率,有些提到了DoKit中。 - 在立思辰开发经历 - 负责项目业务拆分,功能组件抽取,服务组件,以及基础组件的App代码开发。将多个app不断完善抽取分离组件化module,为后期开发定制app快速高效打下基础。 @@ -60,8 +61,8 @@ #### 4.2 2020年4月到2021年3月份 - 负责立思辰教育旗下诸葛书/豆神学习机/豆神直播课开发迭代 - - 1.组件化开发项目,负责项目业务沟通,项目排期,进度把控等。 - - **2.负责项目基础搭建工作,拆分业务组件,服务组件,功能组件,基础组件,完善项目组件化,分离和复用**。 + - 1.负责项目基础搭建工作,拆分业务组件,服务组件,功能组件,基础组件,完善项目组件化,分离和复用。 + - 2.负责后期多个定制项目App,组件化快速开发项目,负责项目业务沟通,项目排期,进度把控等。 - 负责中间组件开发【公共库】 - **1.针对多个项目,搭建一套可以切换内核,业务解耦合,高拓展性通用视频播放器,用于4个项目**。 - 2.针对kindle或者平板学习项目开发通用崩溃重启和崩溃日志记录库,用于2个项目。 diff --git "a/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/01.\344\270\252\344\272\272\347\256\200\345\216\206\345\244\215\344\271\240.md" "b/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/01.\344\270\252\344\272\272\347\256\200\345\216\206\345\244\215\344\271\240.md" index 100670d99..67dfbc6ba 100644 --- "a/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/01.\344\270\252\344\272\272\347\256\200\345\216\206\345\244\215\344\271\240.md" +++ "b/Read/ReadShareWiki/00.\347\273\274\345\220\210\344\270\223\351\241\271/01.\344\270\252\344\272\272\347\256\200\345\216\206\345\244\215\344\271\240.md" @@ -21,12 +21,13 @@ - 6年的Android开发经验,能独立地完成开发的工作。有github封装库长期维护的库!有组件化项目经验! - 理解基本数据类型和包装类原理,并且开发中避免常识性bug,对泛型,线程,反射,异常等有较深理解,具有扎实的Java语言基础。 - 常识性bug,比如针对id使用long或者String,避免返回过长导致字段截取,出现不可预知的bug;比如之前对地图经纬度数据对比,修改直接使用double判断(精度丢失造成不准) - - 范型,主要是约束,编译器检查代码规范,封装base接口,需要用到范型 - - 线程,生命周期,线程排查,优先级,name等等 + - 范型,主要是约束,编译器检查代码规范,封装base接口,需要用到范型。有哪些错误的范型使用,举例说明 + - 线程,生命周期,线程排查,优先级,name等等。线程的异常捕获问题,线程runnable 和callback问题 - 对数据结构比如常见List,Map,Set,Deque,Stack等熟悉,能够结合实际场景选择合适的集合。 - 比如说,针对activity任务栈管理,使用Stack管理; - 比如说,针对针对webView库,读取内存缓存资源,因为读远大于写,保证数据安全,选择CopyOnWriteArrayList; - 比如说,针对磁盘缓存file查看工具,采用Deque队列管理页面回退 + - 主要是复习HashMap,LinkedHashMap(淘汰算法) - 熟悉面向对象编程思想,熟练运用设计模式原则,写出高质量可复用,拓展性强,方便维护和阅读的代码,具体可见技术产出库。 - 熟悉Http协议,熟悉常用状态码,知道https请求传输数据过程,较为熟悉网络协议知识点,开发过网络请求拦截日志信息工具,有接口健壮性自测能力。 - 熟悉Android中四大组件,Handler通信机制,IPC机制,Window,AMS,WMS,等基础原理,并实际开发灵活运用。 @@ -103,10 +104,9 @@ - 极度自律,喜欢编程,能够克服开发过程中绝大部分的技术问题,对于GitHub开源项目持续更新和维护。 - 虽然工作上不是很聪明,但是做事靠谱和认真,一步一个脚印,有始有终,能够持续不断完善自我,不算会表达。 - - -缺点 -1.比较欠缺管理能力 +- 缺点 + - 1.比较欠缺管理能力 + - 2. ### 6.开源项目 diff --git a/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/PoolThread.java b/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/PoolThread.java index 12663b28a..46e90aba9 100644 --- a/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/PoolThread.java +++ b/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/PoolThread.java @@ -67,7 +67,6 @@ public final class PoolThread implements Executor { * 默认线程传递 */ private final Executor defDeliver; - /** * 确保多线程配置没有冲突 */ diff --git a/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/builder/AbsThreadPoolBuilder.java b/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/builder/AbsThreadPoolBuilder.java index 2f4ba7583..572e28cac 100644 --- a/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/builder/AbsThreadPoolBuilder.java +++ b/ToolLib/ThreadPoolLib/src/main/java/cn/ycbjie/ycthreadpoollib/builder/AbsThreadPoolBuilder.java @@ -19,6 +19,9 @@ */ public abstract class AbsThreadPoolBuilder implements IThreadPoolBuilder{ + /** + * ConcurrentHashMap 多线程下数据安全 + */ protected static Map mThreadPoolMap = new ConcurrentHashMap<>(); protected ExecutorService mExecutorService = null; protected String mPoolName = "default";