这些知识整理都是自己查阅帅丙资料(当然还有其他渠道)加以总结滴~ 每周都会更新知识进去。
如有不全或错误还请大家在评论中指出~
数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry在Java8中叫Node
。
1.8插入数据时判断链表长度是否大于 8并且数组长度大于64, 大于的话链表转换为红黑树;当删除小于六时重新变为链表
根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。
默认初始化长度(1<<4就是16),因为位与运算比算数计算的效率高了很多。因为Length-1的值是所有二进制位全为1,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。2的幂实现均匀分布。
红黑树是一种自平衡的二叉查找树
左旋转: 逆时针旋转红黑树的两个节点,使得父节点被自己的右孩子取代,而自己成为自己的左孩子。
HashMap 中的 Iterator 迭代器是 fail-fast
。
快速失败(fail—fast)
是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。在遍历过程中使用一个 modCount 变量,遍历下一个元素之前都会去监测这个值。安全失败(fail—safe)
遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。(java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。)数组容量是有限的,数据多次插入的,到达一定的数量就会进行扩容(resize)。
扩容的时候1.7需要对原数组中的元素进行重新hash定位在新数组的位置,1.8 不用重新hash就可以直接定位原节点在新数据的位置;(扩容是扩大为原数组大小的2倍,用于计算数组位置的掩码仅仅只是高位多了一个1,重新hash数值比原来大16(旧数组的容量)
)
jdk1.7头插法(新元素总会被放在链表的头部位置)在resize时多线程会形成环形链表
。
1.8使用尾插法避免(使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。)但是它没有加同步锁,多线程情况最容易出现的就是:线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,A会把B的数据覆盖
。
在java中,所有的对象都是继承于Object类。在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样。
对hashCode方法重写,以保证相同的对象返回相同的hash值。不然链表咋找对象??
Java中有HashTable、Collections.synchronizedMap、以及ConcurrentHashMap可以实现线程安全的Map。
HahTable对象的key、value值均不可为null。Hashtable使用的是安全失败机制(fail-safe),如果你使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。HashMap 的键值则都可以为 null
mutex
,如果你传入了mutex参数,则将对象排斥锁赋值为传入的对象。】;LinkedHashMap内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry内部除了继承HashMap的Node属性,还有before 和 after用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。
TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序,内部是通过红黑树来实现。所以要么key所属的类实现Comparable接口,或者自定义一个实现了Comparator接口的比较器,传给TreeMap用于key的比较。
jdk1.7时,由 Segment 数组( 继承于 ReentrantLock)-》里面存放数据使用的是
HashEntry
(HashEntry跟HashMap差不多的,但是不同点是,他使用volatile去修饰了数据Value还有下一个节点next) 组成。分段锁技术:当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。set需要获取Segment 锁;而 get 方法由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,因为不需要加锁。
jdk1.8时,采用了 CAS + synchronized 来保证并发安全性。抛弃了原有的 Segment 分段锁,也把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile
去修饰。采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized。
ConcurrentHashMap在进行put操作的还是比较复杂的,大致可以分为以下步骤:
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。
CAS 带来的ABA问题加个版本号、时间戳就能解决。
ArrayList是实现了基于动态数组(jdk1.8后对arraylist进行优化,默认为0,初次添加元素时扩容为10,
为的是避免无用内存占用。每次扩容1.5倍)的数据结构,随机访问占优,空间浪费主要体现在在list列表的结尾预留一定的容量空间;
不会初始化数组大小
,只是指定了elementData缓冲区数组
的大小, 跟数组的大小size并没有什么关系。(elementData用transient
来修饰,因为elementData里面有一些元素是空的,这种是没有必要序列化
的。)ArrayList遍历最大的优势在于内存的连续性
,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。
Arrays.asList()的坑
Arrays.asList()方法返回的是的Arrays内部的ArrayList,用的时候需要注意。它体现的是适配器模式
,后台的数据仍然是数组,所以不能使用修改集合的方法(add/remove/clear)
Vertor、Collections.synchronizedList、CopyOnWriteArrayList
确保线程安全。CopyOnWrite
并发容器用于读多写少的并发场景( 只是在增删改上加锁,但是读不加锁)。
CopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁,保证同步,避免多线程写的时候会 copy 出多个副本。
虽然同步容器(例如Vertor)的所有方法都加了锁,但是对这些容器的复合操作无法保证其线程安全性。需要客户端通过主动加锁来保证。
由于同步容器存在的并发度低问题,从Java5开始,java.util.concurent包下,提供了大量支持高效并发的访问的集合类–并发容器。但是,作为代替Vector的CopyOnWriteArrayList并没有解决同步容器的复合操作的线程安全性问题。
ConcurrentHashMap中增加了对常用复合操作的支持,比如putIfAbsent()、replace()
String,StringBuild,StringBuff的区别
执行效率: stringbuild>stringbuff>string
String类是不可变类,任何对String的改变都会引发新的String对象的生成;
StringBuffer是可变类,任何对它所指代的字符串的改变都不会产生新的对象,线程安全的。
StringBuilder是可变类,线性不安全的,不支持并发操作,不适合多线程中使用,但其在单线程中的性能比StringBuffer高。