类集知识点总结

类集小结

在这里插入图片描述
1. ArrayList、Vector、LinkedList三者的关系与区别?
(1)关系:以上三个类都是List接口下的常用子类,其中ArrayListVector是基于数组实现的,而LinkedList是基于双向链表实现的;
(2)区别ArrayListArrayList采用懒加载策略,即就是在第一次add时才初始化内部数组,默认大小为10,扩容时,扩容为原数组大小的1.5倍;不加锁,采用异步处理,线程不安全,但是性能比较高;在大部分场合(80%的情况下)都采用ArrayList,如频繁的查找、在集合末端进行插入删除操作(只需要一行代码);
(3)区别VectorVector在产生对象时就初始化内部数组大小为10,扩容时,扩容为原来的2倍;加锁,采用synchronized修饰增删改查方法,线程安全,但是性能较低,因为锁的粒度太粗,直接将整个集合对象锁住,读读都是互斥的;在JDK内置的Stack是Vector的子类;
(4)区别LinkedListLinkedList采用异步处理,线程不安全,性能较高;频繁的在任意位置进行插入与删除考虑使用LinkedList,不管是在中间插入还是尾插,需要7行代码;LinkedList还是Queue接口的常用子类。
2. HashMap、Hashtable、TreeMap三者的关系与区别?
(1)这三者都是Map接口的常用子类,其中HashMap底层使用哈希表+红黑树(JDK1.8之后,在JDK1.8之前使用纯哈希表实现),TreeMap使用红黑树,Hashtable使用哈希表;
(2)Hashtable使用synchronized同步方法,将整个哈希表上锁,类似于Vector,锁的粒度太粗,性能很低,线程安全;HashMap和TreeMap采用异步处理,性能较高,线程不安全;
(3)关于null:HashMap的K和V都允许为null;Hashtable的K和V都不能为null;TreeMap的K不能为null,但是V可以为null。
3. Set接口和Map接口的关系?
Set内部使用Map来存储元素,将元素存储到内部Map的K,即:HashSet->HashMap,TreeSet->TreeMap
4. hashCode()和equals()的关系?
(1)hashCode():将任意一个对象根据指定的算法转为32位的int整型;
(2)equals():比较两个对象是否相等;
(3)equals()相等的两个对象,hashCode()一定相等;
(4)hashCode()相等的两个对象,equals()不一定相等。
5. 哈希冲突与其解决办法?
(1)哈希表:实际上就是<K,V>存储的数组,Node<K,V>[] table,增删改查在没有哈希冲突时,时间复杂度都是O(1),哈希表中多个对象会存储多次;
(2)哈希冲突:传入两个不同的K,通过hash算法运算之后得到的结果相同,就称为哈希冲突;
(3)解决办法再散列法:使用一个不同的哈希算法再次计算K值,该方法浪费空间;链地址法:也叫拉链法,采用数组加链表的方法,将所有冲突的元素以链表的形式存储在同一个数组index下,该方法节省空间,比较推荐,并且HashMap采用的就是这种方法;开放地址法:寻找距离最近的未存储元素的位置将冲突元素存储在此索引位置,该方法比较浪费空间,性能为O(1+n),当没有哈希冲突时,CRUD的时间复杂度为O(1),n可以忽略掉,但是当有哈希冲突时,时间复杂度达到O(n)。
6. Java实现一个类的两个对象的大小比较方式
(1)内部排序(不灵活):Comparable,若一个类实现了Comparable接口,表示该类具备天然的可比较性,就比如年龄,体型等都是天然可比较的,而描述一个人,有很多方面,表示该类不具备天然的可比较性。int compareTo(Object o)方法,返回值等于0,表示两个对象相等,返回值大于0,表示当前对象大于目标对象,返回值小于0,表示当前对象小于目标对象。具有天然可比较特性的对象比较时采用内部排序;
(2)外部排序(灵活):Comparator,类本身不具备天然的可比较特性,如:Person类,专门有一个类比较两个对象的大小关系(Person比较器)。int compare(Object o1, Object o2)方法;
(3)HashSet无序存储,比较时使用hashCode()equals()TreeSet有序存储,此处的序是指是否根据相应的规则(存储该类的大小:内部或外部排序返回的结果)来输出。自定义的类要想存储在TreeSet中,要么实现了内部排序(即该类天然可比较,如String或Integer),要么通过构造方法传入了该类的比较器(实现了外部排序的Comparator接口),并且TreeSet优先使用外部比较器,其内部实现使用的是一个三目运算符。
7. fail-fast机制?
(1)什么是fail-fast?即就是快速失败,这是一种策略,优先考虑出现异常的情况,若异常产生,抛出异常,程序终止;
(2)举例:当出现除0操作时:

public static int div(int a, int b){
	if(b == 0){
		//直接抛出异常,不往下执行,快速失败
		throw new IlegalArgumentException("除数不能为0");
		return a / b;
	}
}

(3)jcl为何会产生ConcurrentModificationException
if(modCount != exceptedModCount),其中modCount是记录当前集合在结构上被修改的次数,exceptedModCount是指迭代器内部希望集合的修改次数(在获取集合迭代器时赋值,等于当前集合修改次数,这里的修改次数包括加加,减减,不管是加还是减都是一次操作,所以modCount + 1);产生ConcurrentModificationException异常的意义在于:避免多线程场景下的脏读问题,防止在读的时候数据被别的线程修改而自己不知道。
(4)如何解决fail-fastConcurrentModificationException异常?
多线程读,单线程写,即在迭代遍历集合时,不要修改集合结构;使用fail-fast(juc包下的线程安全集合,如CopyOnWriteArrayList、ConcurrentHashMap等,不会抛ConcurrentModificationException异常);
(5)java.util包下的直接子类,如:ArrayList, Vector, HashMap等都是fail-fast机制,除了TreeMap
8. ConcurrentHashMap如何高效的实现线程安全?JDK1.7和JDK1.8中ConcurrentHashMap的设计区别?
(1)JDK1.7Segment初始化为16之后无法进行扩容,扩容发生在每个Segment对应的小哈希表;使用Lock体系保证线程安全(SegmentReentrantLock的子类),整张表有16把锁,锁的对象都是Segment,支持的线程并发数是16;
(2)JDK1.8:与JDK1.8的HashMap结构上基本一致,哈希表加红黑树,不再使用Segment;使用CAS加synchronized同步代码块保证线程安全,锁的对象是哈希表的数组元素,锁粒度更细(原来是一个小区域,现在只是一个元素,锁的个数也更多,锁的个数会随着哈希表的扩容而增加),支持的线程并发数更多。
9. HashMap源码解读
(1)负载因子final float loadFactor
默认的负载因子0.75:DEFAULT_LOAD_FACTOR = 0.75f
容量int threshold,等于table.length * loadFactor
默认树化阈值static final int TREEIFY_THRESHOLD = 8
默认解树化阈值static final int UNTREEIFY_THRESHOLD = 6,当红黑树节点个数小于6时,在下一次resize扩容过程中,将红黑树退化为链表,节省空间。
(2)

public V put(K key, V value) { 
       return putVal(hash(key), key, value, false, true);
}

内部的hash(key)算法实现:

    static final int hash(Object key) {  
    	int h;  
    	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

key.hashCode():得到的是一个32位整数值;
h>>>16:只保留了高16位,即保留最有效的位数,一般而言,对于特别大的数字有效位都在前面,后面都是一串0或者都是一样的,区别不大,所以只保留高16位;
(h = key.hashCode()) ^ (h >>> 16):将高低16位都参与异或运算,目的在于减少哈希冲突。
(3)为何HashMap中不直接采用key值的hashCode()方法计算哈希值?
由于Object提供的hashCode()得到的是一个32位的整数值,直接将其作为数据下标会浪费大量的空间。
(4)putVal()方法:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(5)为何HashMap中哈希表的大小始终是2^n
解析:假如HashMap中哈希表的大小不是2^n,那么减去一之后,二进制位数中可能有0,就会存在不能被访问到的下标;反之减一之后二进制所有位数都是1,可以保证哈希中的所有索引下标都有机会被访问到。
(6)为何hash()出来的值仍然不能直接作为数组下标?
解析:即使保留高16位,得到的数仍然可能很大,所以真正的数组下标是(n - 1) & hash,保证得到的数组下标一定在数组的长度范围内。
(7)table.length:当前哈希表的长度(数组元素的个数);Map.size:当前存储的具体元素个数(实际存储的元素个数);两者之间毫无关系,完全不同的两个东西,并且大小关系不确定。
(8)HashMap树化的逻辑:当前数组下标对应的链表的长度(添加完新节点后)>= 8,并且此时的哈希表长度 >= 64,才会将此链表树化,否则只做简单的扩容处理。树化只把自己索引下树化为红黑树,并不会把整个哈希表树化。
树化原因:当链表长度过长时,哈希表的CURD时间复杂度会退化为O(n);将链表变为红黑树,其时间复杂度会变为O(log(n)),大大减小。
(9)负载因子:决定了哈希表达到的容量的百分比大小进行扩容阈值。
若负载因子 > 0.75:增加了哈希表的利用率,哈希冲突概率明显增加;
若负载因子 < 0.75:降低了哈希冲突的概率,空间利用率低。
(10)扩容resize每次哈希表扩容时扩容为原来的二倍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值