JDK部分源码阅读与理解

Posted 小明猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK部分源码阅读与理解相关的知识,希望对你有一定的参考价值。

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/

 

   不喜欢重复造轮子,不喜欢贴各种东西、JDK代码什么的,让整篇文章很乱。。。JDK源码谁都有,没什么好贴的。。。如果你没看过JDK源码,建议打开Eclipse边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录。。。

   JDK容器类中有大量的空指针、数组越界、状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现,这篇文章就是我看完这部分源码之后的觉得有用的知识点总结,渣渣一枚,大神勿喷。。。


ArrayList
1、 容器默认大小为10,位置不够了自动扩增,每次增加当前长度的一半;(扩增时用Arrays.copyOf进行扩增)
2、 数组容量扩增到Integer.MAX_VALUE-8的时候,就会开始限制数组扩充,超过Integer.MAX_VALUE的时候,抛内存溢出异常;
3、 clone是浅拷贝,List中的引用指向的还是相同数据;
4、 线程不安全的;
5、 迭代器中的expectedModCount和modCount:迭代器初始化时,会用modCount去初始化expectedModCount,在迭代器的操作中都要检查这两个值的等值性,不想等抛出ConcurrentModificationException异常表示当前List正在被修改,为什么呢?如下:
List本身的add和remove操作时都会修改modCount的值,如果在迭代器循环迭代过程中调用了List本身的add和remove方法就会导致两个值不想等而抛出异常,而迭代器本身的remove则不会有这个问题,它会在remove完成后重新更新一遍expectedModCount保持两个值的等值性。
6、 ListIterator和普通迭代器类似,只是多了双向迭代的功能;
7、 ArrayList本身的sort其实是调用Arrays.sort传入比较器实现的;


LinkedList
1、 没ArrayList那么麻烦了,不需要扩容什么的,处理好指针引用的指向关系就行。
2、 内部维护size,first,last,支持双向遍历,链表增删操作时,注意维护好这三个值;
3、 线程不安全;
4、 链表支持链表头增删,链表尾增删,指定节点前或后增删;
5、 List中的Node为private内部类,无法手动创建,这是为了保证用户拿到的所有Node都是LinkedList生成的,即其成员next和pre为正确指定的值;
6、 LinkedList中的Element元素支持插入空值,和HashMap一样;
7、 LinkedList的迭代器中也有expectedModCount检测链表的修改状态,迭代器循环迭代时只能用迭代器的remove方法;
8、 clone为浅拷贝;
9、 LinkedList可以当Deque用,因为有实现相应接口;


HashMap
1、 HashMap支持null的key和value;
2、 Equals会比较两个Map地址,若地址不等,则迭代比较每个元素,都相同才返回true;
3、 Hashcode为每个元素Entry的hashcode的和;
4、 Entry是一个键值对封装体,都复写了Object的很多相应方法,建立了K-V映射关系;
5、 Clone浅拷贝;
6、 HashMap默认大小16,最大大小2的30次方;
7、 结构:一开始使用数据结构书中的以链表为元素的数组存储元素,元素得到的哈希值相同的放在数组中哈希值对应位置的链表的末尾,操作很简单,但是有弊端,当同一个哈希值存在的元素过多的话,查询速度很差,这不是是同哈希表想看到的结果,这时候采取的是转换成红黑树的办法。
8、 填充比:其中设置一个填充比,当链表中的元素数量超过填充比对应的值的时候,链表会转换成红黑树,提高查询效率,填充比在HashMap的构造函数可以设置,默认0.75。
9、 临界值:数组中的位置使用率达到临界值的时候,数组会扩容;
10、 线程不安全;
11、 Put方法:hashCode()计算Key的哈希值,从而找到数组中的元素位置,
(1)该位置中没元素,直接放入;
(2)该位置中有元素调用equals,对比Key是否真的相等,若相等,用新的Value替代旧的Value;
(3)若equals不相等(冲突),则把这个元素放到原来的元素后面(链表)
注:因此作为HashMap的Key的数据类型,有必要好好考虑Key的hashCode和equals重写,尽量避免冲突,提升效率;
12、 resize():重新调整数组大小,新旧数组复制,较耗时;
13、 get()方法:计算key的hashCode,从而找到数组位置,对比数组中元素第一个节点和key是否一致,一致返回该节点,否则判断节点是否是红黑树节点来分类讨论(红黑树按照红黑树的方式查找该key,链表则循环下去接着找),如果找到key一致的则返回,否则返回null,表示该key暂时还没有值;
14、 put()方法步骤:
(1)检查表是否需要扩容;
(2)检查hashCode指定位置是否需要newNode;
(3)数组中指定位置已经有节点了,先检查和第一个节点是否一致,一致则找到节点;
(4)不一致,检查是否为红黑树,通过红黑树查找节点;
(5)不是红黑树的,接着链表查找,找到元素节点或者找不到新建节点,并检查是否需要转换成红黑树;
(6)若找到的节点不为空,赋值Value;
(7)检查数组是否需要扩容;
(8)返回oldValue;
15、 Remove()方法:通过hashCode找到数组位置,对比第一个节点,再对比红黑树或链表,找到节点之后删除节点并返回;
16、 HashMap的增删方法也和List一样有ConcurrentModificationException异常,使用迭代器需注意;
17、 


LinkedHashMap
1、 HashMap的子类;
2、 存储方式和HashMap都一样,只是在put元素的时候顺带存储了元素的存入先后顺序,在迭代器遍历的时候,输出的结果顺序是和插入元素时的插入先后顺序一致的;
3、 有ConcurrentModificationException;
4、 可以设置插入元素时自动删除最旧的元素;


TreeMap
1、 实现了NavigableMap接口(是SortedMap的子接口);
2、 使用红黑树作为底层实现,排序平衡树,有序,提供一系列有序集合才有的方法(获取第一个,获取某一范围内的),排序按照Key为标准进行比较;
3、 默认为升序排序,descendingMap()返回降序排序的实例;
4、 维护了比较器comparator、树根节点root、size属性;
5、 putAll方法时,先判断传入的集合是否是SortedMap,是的话利用比较器直接通过红黑树批量插入再调整红黑树,较为高效,若不是,则遍历整个集合把每个元素一一插入;
6、 元素间比较:若内部比较器不为空,则用内部比较器进行key的比较,否则元素本身要实现Comparable接口以提供比较;
7、 不允许key为空;
8、 Entry就是红黑树的节点;
9、 Put:按照排序树查找key,找到直接修改value,没找到就插入一个节点,并进行红黑树调整;
10、 Remove:按照key查找相应的Entry,再把这个节点从红黑树中删除,并进行红黑树调整;
11、 浅拷贝;
12、 Replace要求提供的oldValue要和key对应的Entry的value一致才能成功替换;
13、 非迭代器的增删方法也有ConcurrentModificationException;


HashTable
1、 Dictionary的子类;
2、 浅拷贝;
3、 Keyset,ValueSet,EntrySet三种值的获取方式使用了标记位的方式,代码重用,不像之前的,每个都各写一个;
4、 Map操作是方法粒度的线程安全的;
5、 不允许key为空或value为空;
6、 遍历迭代器使用Enumeration方式;
7、 HashMap的get方法返回null表示没有该key或者该key对应的value为空;而HashTable的get返回空则一定是没有该key;


IdentityHashMap
1、 和WeakHashMap一样,用一个特殊值表示null的key;
2、 线程不安全;
3、 浅拷贝;
4、 底层用一个数组实现存储,下标0,2,4,6…存储key,下标1,3,5,7…存储value,
remove时,将相应位置的值赋空,并把后面的元素往前移;
put时一次递增两个位置下标来查找;
hashCode采用native底层实现,根据内存地址计算哈希值,两个key只有==时才算相同,在字符串常量池和堆区的两个相同字符串不算相等;


HashSet
   底层由HashMap提供支持,实现容器中元素单一出现的目的。


TreeSet
1、 有序的Set,元素需要实现Comparable接口,要求compareTo和equals的返回结果一致;
2、 Comparator负责提供给TreeMap比较器;
3、 具有有序集合的一些性质:比如提取比某个值大的子集合,提取在某一范围内的子集合等有序才具有的操作;
4、 实现了NavigableSet接口,NavigableSet是SortedSet的子接口;
5、 底层为TreeMap提供支持,Set中的元素就是Map的Key;
6、 浅拷贝;
7、 PRESENT:Set提供给底层Map的Value,所有Key共用通过一个Value;


Queue
   单向队列,队头取出,队尾加入。


Deque
   双向队列,双向添加删除,分别有两个方向的迭代器。


Vector
   和ArrayList都一样,但是提供了方法粒度级别的同步机制。


Stack
1、 继承于Vector,线程安全,底层数组实现,自动扩张;
2、 栈顶增删查操作,并提供整栈元素搜索功能。


WeakHashMap
1、 允许Key为空,但是在存储底层是使用了内部一个特殊Object对象来表示Null的Key;
2、 维持了一个弱引用队列,Map的Entry就是一个弱引用对象;
3、 底层实现和HashMap基本一致,只是tables中的引用对象都是弱引用;
4、 弱引用:不能保证引用还存在时避免引用指向的内存不被垃圾回收机制回收;
5、 Entry在new的时候创建了一个弱引用对象并加入弱引用队列中,该弱引用指向的内存空间随时可能被回收,被回收后再使用该弱引用返回null,并且内部的多数Map操作都附带了弱引用回收操作,把那些已经被回收的弱引用从弱引用队列中移除,不影响该弱引用在hashTable中链表指向的下一个节点。


Collections
1、 封装了集合的很多常用工具静态方法,二分查找、排序、子集合、替换、比较等;
2、 synchronizedCollection等一系列的方法提供了将普通集合转换成代码块粒度的同步,使用mutex进行同步;
3、 singleton提供了集合中单元素的集合静态方法;
4、 unmodifiableCollection提供了将所给集合变成不可写的集合的方法;
5、 等等。。。。。


Arrays
1、 数组的常用工具静态方法,排序,二分查找,数组填充,子数组提取,深equals,深hashCode,数组复制等;
2、 基本数据类型使用插入排序(长度小于7)和快速排序(长度大于7);
3、 Object的数组使用归并排序(稳定排序);


Array
   反射包中的Array,表示所有的数组,由native本地方法实现,一个数组就相当于是一个Object。原生数组中的相关调用就相当于是调用这个类中的相关方法。


String
1、 对char数组的封装,相当于是一个元素为char的ArrayList,没有长度自动伸缩功能;
2、 封装了字符串操作的常用方法,底层实现都是在反复操纵char[];
3、 Equals重写成了char[]的各个元素;
4、 hashCode重写成char[]计算的哈希值;
5、 split通过不断查找切割子串的位置来substring字符串得到字符串分割结果数组;
6、 String长度创建后不变,改变的话就是整个char[]变了,不能对char[]数组中的指定下标进行随机修改;


StringBuilder
1、 底层char[]实现,相当于ArrayList的char元素的实现,提供元素增删改查;
2、 线程不安全;
3、 可设定初始长度,防止扩充长度的资源消耗;
4、 可修改指定下标的char值;
5、 动态修改动态添加,长度可变;


StringBuffer
   与StringBuilder继承相同抽象类,底层相同实现,但是StringBuffer是线程安全的。


ThreadLocal
1、 多线程同时访问统一共享属性的时候产生的线程安全问题的一种解决方案,适用于每个线程对这个共享属性的修改都不会影响到其他线程,即不需要线程间通信的这种情况;这样做是为了避免使用同步锁导致的效率问题(每个线程各创建一个共享数据的副本分别服务于各自的线程);
2、 每个Thread内部有两个类似Map的属性ThreadLocalMap,通过Thread.currentThread();获取当前的线程实例,再获取本线程中的Map实例,通过对这个Map中进行数据的增删,实现共享数据的副本和线程绑定的目的,从而在一定程度上避免线程安全问题。

 


额外收获:
1. transient:关键字,修饰的变量不参与序列化操作;
2. volatile:关键字,修饰的变量在每次使用时都会去查询其最新值,防止编译器优化使用缓存错过他的最新值;
注:这里的每次使用指的是从堆区复制这个值的瞬间,当一个线程访问时,会从堆区拷贝这个值一份到自己的线程栈中,之后线程就只修改线程栈中的这个值,对于堆区的值没有修改,这是volatile的局限性。详细看:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
3. 集合equals判断:先判断地址是否相同,再判断类型是否相同,再判断size,之后判断集合中每个元素是否相同;
4. 集合hashCode:集合中每个非空元素的hashCode之和;
5. Remove如果删除了集合中没有的元素,集合没有任何改变,返回false;
6. HashMap、LinkedHashMap、TreeMap区别(迭代遍历顺序):
   HashMap存储和查询顺序根据Hash而定,顺序不确定;
   LinkedHashMap:按照元素插入先后顺序;
   TreeMap:按照元素Key比较顺序,或临时设定的比较器决定的顺序输出;
7. Comparable和Comparator:
   Comparable是Entity元素的可实现接口,实现该接口的Entity在和本类型的其他Entity比较时就是通过该接口的compareTo方法进行比较;
   Comparator:提供给集合的比较器接口,该比较器可以通过传入相同类型的两个对象进行compare比较并返回相应值判断出大小关系(通常保证比较结果要和该类型的equals返回结果相一致)。


参考博客:
1. http://zengzhaoshuai.iteye.com/blog/1130376
2. http://mikewang.blog.51cto.com/3826268/880775/

 

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/

以上是关于JDK部分源码阅读与理解的主要内容,如果未能解决你的问题,请参考以下文章

阅读JDK源码正确的打开方式

JDK源码阅读指南

来啊, 一起看遍JDK源码

jdk源码阅读笔记之java集合框架(基础篇)

JDK1.8JDK1.8集合源码阅读——TreeMap

JDK12 concurrenthashmap源码阅读