Java集合

1.集合类主要由collection和Map派生而来的。collection派生出三个子接口:List,Set,Queue。

2.线程安全的:hashtable;concurrenthashmap;vector(比array list多了个同步化机制);stack(继承自vector)

线程不安全的:hashmap;Arraylist;linkedlist;HashSet;TreeSet;TreeMap;

3.arraylist与linkedlist异同

(1)都是不同步,线程不安全;

(2)array list底层适用的是object数组;linked list适用的是双向链表结构;

(3)arrayList插入和删除复杂,但查询方便;

4.array list与vector区别

(1)vector是线程安全的,因为加了syn关键字,arraylist不是

(2)vector是扩展1倍,arraylist是扩展0.5倍;

5.arraylist

(1)arraylist扩容是添加元素后判断是否扩容,调用grow方法扩容,在检查是否溢出,调用arrays.copyof将elementData数组指向新的内存空间,并将数据复制到新的内存空间。

(2)arraylist只能包含对象类型,而array可以包含基本类型和对象类型;array是大小固定的,而arraylist是动态变化的。

6.hashmap的底层原理

数组+链表+红黑树

当链表超过8且数据总量超过64才会转化为红黑树。如果数组长度小于64,会先选择扩容,而不是转换为红黑树。

7.解决哈希冲突

有开放定址法(所需hash长度要大于需要存放的元素,而且存在再次hash所以删除只是做标记,不是真正的删除),再哈希法,拉链法(适用于经常插入和删除的情况),建立公共溢出区;hashmap使用的是拉链法;

8.为什么不直接使用红黑树,因为红黑树需要进行左旋右旋变色等操作保持平衡,而单链表不需要。当元素小于8时,单链表已经能保证查询性能。

负载因子0.75是时间和空间效率的平衡选择,设置初始大小时要考虑尽量减少resize的次数。

9.hashmap的key怎么计算的

根据key计算出hashcode的值(hashcode值是表示在hash表里面的位置),然后根据hashcode计算出hash值,最后通过hash&(length-1)计算存储位置。

10.hashmap的put流程

(1)计算出key的hash值,找到元素在数组中存储的下标;

(2)如果数组为空,调用resize进行初始化;

(3)如果没有哈希冲突,直接放

(4)如果冲突且key已经存在,就覆盖

(5)如果冲突发现是红黑树就挂在树上

(6)如果是链表,看是否大于8,如果大于8且总量小于64就扩容(两倍扩容);如果大于64就转换为红黑树;

11.hashmap的扩容方式

1.8的两处优化:(1)resize,看原来的hash值新增的那个bit是1还是0,0在原处,1就在原+oldlength处,减少重新计算hash的时间;(2)采用尾插法

12.hashmap的key选取

一般用string,integer这种不可变的当key,这样hashcode不需要重新计算;并且获取对象用到的equals和hashcode方法,这些类已经规范重写了。

13.hashmap为啥线程不安全

(1)多线程发生扩容死循环:头插法会出现环形链表的情况,而尾插法会保持原来顺序不会出现;

(2)多线程的put会导致元素丢失:如果计算出位置相同,会造成前一个key被后一个覆盖;

(3)put和get并发时,可能导致get为null:当线程1put时,rehash时线程2get会为空;

14.concurrentHashMap的实现原理

JDK1.7之前, 其中segment有n个hashentry组成,segment继承了reentrantlock,是一种可重入锁。

JDK1.8,抛弃了segment分段锁,选择了和hashmap一样的数据结构;在锁的实现上,采用了CAS+syn关键字实现更低粒度的锁。

(1)为啥用syn,不用reentrantlock,

    (a)因为1.6中对synchronized锁实现引入大量优化,并且synchronized有多种锁状态,从无锁-偏向锁-轻重量-重量级锁一步步转换。

    (b)减少内存开销,如果用可重入锁,那么每个节点都需要通过继承AQS来获得同步。但并不是每个都需要,只有链表的头节点需要同步。

(2)1.8中concurrenthashmap的put操作:根据key计算hash值;然后判断是否要初始化;定位到node,判断首节点,如果null就通过CAS尝试添加;如果=-1说明其他线程在扩容就参与一起扩容;如果都不满足就锁住头节点,判断是链表还是红黑树遍历插入。

(3)concurrenthashmap的get操作:根据key计算hash值,判断是否为空,如果是首节点就返回;如果是链表就遍历,是红黑树就查询

(4)GET是否要加锁:不需要,因为node元素都是用volatile修饰的,修改和新增对其他线程可见。

(5)concurrentHashMap不支持key或者value为null:value在多线程下为null就无法判断是null还是不存在,产生歧义;而单线程的hashmap却可以判断是否包含了null。

 还有一个collections.synchronizeMap对方法加锁,和hashtable一样也是全表锁。不推荐。而concurrentHashMap是对元素加锁,粒度更细。

 15.HashSet和HashMap区别

 hashset底层其实就是hashmap,只不过hashset是实现了Set接口并且把数据作为K值,而V值一直使用一个相同的虚值来保存。由于HashMap的K值不允许重复,并且KV相同时,会用新的V覆盖掉旧的V,然后返回旧V,那么在HashSet中执行这一句话一定返回false,导致插入失败,保证不可重复性。

16.collection框架中实现比较要怎么做

(1)实体类实现Comparable接口,并实现compareTo<T t>方法,称为内部比较器。

(2)外部比较强,实现compare接口的compare<T t1,T t2>方法。

17.Iterator和ListIterator区别

(1)iterator可以遍历所有集合,但只能向前;而listIterator只能遍历List实现的对象,可以向前向后遍历。

(2)添加,修改和索引,iterator无法操作,但是ListIteror可以。

18.快速失败和安全失败

快速失败:当用迭代器遍历一个对象时,如果修改会抛出ConCurrent Modification Expection。因为在遍历过程中使用一个modcount变量。集合在遍历过程中如果发生变化就会修改modcount值。在hashNext()/next()遍历下一个元素之前,都会检测modcount变量是否为expectmodcount值,是的话就返回遍历,不然抛出异常终止遍历。如果发生ABA问题不会抛出异常。

 Javautil包下的集合都是快速失败的,不能再多线程下发生并发修改(迭代过程中被修改)

安全失败:采用类似CAS,先复制一份集合内容,在复制的集合上进行遍历。由于迭代是在原集合的拷贝进行遍历,所以原集合所作的修改并不能被迭代器检测到,不会抛出异常。

但是这样迭代器不能拿到修改后的数据。java.util.concurrent下的容器都是安全的。

如concurrentHashMap,concurrentLinkedQueue,ConcurrentLinkedDeque,

copyOnwriteArrayList:适用于读操作>>写操作的情况。在写时拷贝,也就是如果需要对CopyOnWriteArrayList的内容进行改变,首先会拷贝一份新的List并且在新的List上进行修改,最后将原List的引用指向新的List。线程安全地遍历,因为如果另外一个线程在遍历的时候修改List的话,实际上会拷贝出一个新的List上修改,而不影响当前正在被遍历的List。ArrayList非线程安全。

CopyOnWriteArraySet:HashSet非线程安全。是一种读写分离的思想,读在原容器,写在新容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值