java集合类

1、在java集合类中最常用的是Collection和Map的接口实现类。

Collection又分为List和Set两类接口,List的实现类有ArrayList、LinkedList、Vector、Stack,

Set接口的实现类有HashSet、TreeSet,而Map的实现类主要有HashMap、ConcurrentHashMap、TreeMap。

2、ArrayList是容量可以改变的非线程安全集合(可以使用Collections.synchronizedList方法实现线程安全),内部使用数组进行存储。

ArrayList支持对元素的快速随机访问,但是插入和删除时速度通常会很慢,因为这个过程需要移动其他元素。ArrayList的默认大小为10,在不传入指定的列表大小时,默认使用空列表。

ArrayList在每次添加数据时都会检查空间是否足够,若不足就会按原数组空间的1.5倍扩容(向下取整),最后会将新的数组空间大小和插入数据后需要的空间大小作比较,取两者之前的更大者。之后就会将旧数组整体拷贝进新的数组空间。


3、HashMap的默认容量值为16,默认负载因子为0.75,默认阈值为12。HashMap的容量不会在new的时候就分配,而是在put的时候才进行分配。

HashMap的实际容量为比传入容量参数大的2的幂,而ConcurrentHashMap实际容量为比(传入容量参数除4/3后)大的2的幂,此后每次扩容都是增加2倍。

HashMap的容量为2的幂的主要是因为HashMap在计算槽位的时候使用的算法是(n-1)& hash,当n为2的幂时元素可以更加均匀的散列。

HashTable是HashMap的线程安全版本,两者主要区别在于HashTable在一些关键的函数中增加了synchronized关键字,由于性能不佳目前已经被ConcurrentHashMap淘汰了。

HashMap是线程不安全的,在java8以前甚至可能出现由于多线程resize导致的循环链表,线程不安全的两个小例子:两个线程同时对同一桶位插入数据可能导致某个线程的数据被覆盖、某个线程在执行resize时另一线程可能读不到数据。


4、HashMap的key和value都可以为null,key为null的元素会被散列到数组的第一个元素,但在使用stream的Collectors.toMap方法时HashMap的value不可以为null,且key不可以重复,否则会抛出异常。另外不同于HashMap,ConcurrentHashMap的key和value都不能为null。

5、树的基础知识:
高度:从某节点出发到叶子节点的简单路径上边的数量被称为该节点的“高度”
重要的二叉树:平衡二叉树、二叉查找树、红黑树
平衡二叉树:树及所有子树的左右高度差不超过1
二叉查找树:对于树的任意节点而言它的左子树的所有节点值都小于它,而他的右子树的所有节点值都大于它。它主要有前序遍历、中序遍历、后序遍历三种遍历方式。随着数据不停的增加或删除,二叉查找树容易失衡
红黑树:红黑树是一种可以自平衡的二叉查找树。红黑树在每个节点上增加了颜色属性,可以为红色或黑色,红黑树通过按规则着色和特定的旋转来保持自身的平衡,它新增、删除、查找的最坏时间复杂度均为O(logN)。相对于其他的自平衡树例如AVL树,红黑树并不严格保证时左右子树的高度差超过1,这使得红黑树在删除时能够更快的恢复平衡,成本比较低,所以面对频繁的插入删除时,红黑树更适合,而面对低频修改,大量查询时AVL树更合适


6、Comparable接口和Comparator接口的区别?
如果要使用Comparable接口,就必须实现该接口并重写compareTo方法。而Comparator接口可以在类外实现,并可以将其实现对象传入到Collections.sort或Arrays.sort方法中以实现排序。Comparator接口的使用体现了基于开闭原则的设计。


7、集合中的hashCode和equals?
hashCode和equals用来标识对象,两者协同工作来判断对象是否相等,当hashCode的值相同时,还需要调用equals进行一次值比较。任何时候在覆写equals时一定要同时覆写hashCode,因为Map、Set等集合都是同时使用两者来判断对象是否相同的。hashCode是根据对象地址进行相关计算得到的int类型数值。在做对象间的比较时,尽量使用Objects.equals方法来避免空指针。


8、集合的fail-fast机制?
fail-fast是一种在集合遍历时的错误检测机制,如果在遍历的过程中出现了意料之外的修改就会抛出ConcurrentModificationException异常,这种机制经常出现在多线程环境下。线程会维护一一个modCount用来记录集合已经修改的次数,在遍历集合时会时时检查modCount的数值是否发生变化,若发生变化则抛出异常。
9、ConcurrentHashMap知识:
在java8以前ConcurrentHashMap通过锁分段的思想将整个hashMap分为16个segment,每个segment负责一部分数组元素,并通过reentrantLock锁来保证每个segment的数据安全。而在java8后ConcurrentHashMap取消了segment,大量的使用了volatile、cas等技术进一步减少了锁竞争造成的性能影响。

java8后ConcurrentHashMap有三点主要的改动:(1)取消分段锁机制,进一步降低冲突概率、(2)引入红黑树、(3)使用了更加优秀的方式统计元素的数量。
get操作逻辑:计算出key的hash值并计算出槽位,然后通过getObjectVolatile获取数该槽位的元素,并比较hash值和key值,若相同则返回,如果槽位内节点的hash值小于0则说明正在进行扩容,则通过ForwardingNode的find函数去新的数组nextTable中进行查找。否则就遍历单链表查找相应节点。
put操作逻辑:首先检查核心的Node<K,V>[] table是否已经初始化,如果没有初始化,则利用CAS将sizeCtl的值置为-1进行初始化。查询key相应的槽位是否为 null,若为null直接通过CAS将键值对放入槽位。如果相应的槽位已经有节点,并且其hash值为-1,则表示正在进行扩容,则当前线程帮忙进行扩容。否则通过synchronized锁住槽位内的节点即链表的头结点,然后遍历链表,寻找是否有hash值及key值相同的节点,若有则将value设置进去,否者创建新的节点加入链表。通过addCount函数更新ConcurrentHashMap键值对的数量,并检查是否需要进行扩容。
扩容操作逻辑:首先新建一个两倍长度的数组nextTable。初始化ForwardingNode节点,其中保存了新数组nextTable的引用,在处理完每个槽位节点后当做占位节点,表示该槽位已经处理过了。通过倒序的方式为线程分配需要处理数组元素个数(默认每个线程16个元素)。每个线程处理自己负责的数组元素,具体逻辑和HashMap基本相应,处理完的元素的位置上会放入ForwardingNode节点。
计数方法:代码里的变量baseCount用于在无竞争环境下记录元素的个数,每当插入元素或删除元素时都会利用CAS更新键值对个数。当有线程竞争时,会使用CounterCell数组来计数,每个ConuterCell都是一个独立的计数单元。线程可以通过ThreadLocalRandom.getProbe() & m找到属于它的CounterCell进行计数。这种方法能够降低线程的竞争,相比所有线程对一个共享变量不停进行CAS操作性能上要好很多。这里的CounterCell数组初始容量为2,最大容量是机器的CPU数。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值