1、Collection接口
常用方法:
add() 添加元素、size() 获取集合中元素的个数、addAll() 把一个集合的元素添加到一个新的集合
isEmpty() 判断当前集合是否为空,判断的是集合中是否有元素、contains() 判断是否包含
containsAll() 判断集合coll1中是否包含coll集合中的所有元素、remove() 删除指定元素
removeAll() 删除当前集合中包含另一个集合中的所有元素
clear() 清空集合中的元素、retainAll() 获取两个集合的交集、equels() 比较两个集合是否相等
hashCode() 返回当前对象的哈希值、toArray() 集合转成数组、Arrays.asList() 数组转成集合
特殊方法:iterator()迭代器和foreach
Collection coll = new ArrayList();
//add() 添加元素
coll.add("AA");
//iterator() 迭代器
Iterator iterator = coll.iterator();
//hasNext() 判断是否有下一个元素
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//foreach
//for(集合中的每一个元素 : 集合对象)
for (Object o : coll) {
System.out.println(o);
}
2、Collection -> List 接口(ArrayList、LinkedList、Vector)
3、Collection接口 -> Set接口(TreeSet、HashSet)
(1)不包含重复元素的集合接口,list包含重复元素,
(2)最多包含一个null,而list可以包含多个
(3)set保存的元素无序(具体是否有序由实现子类决定),list接口保存的元素有序
HashSet类:
底层是用hashmap实现,并实现了set接口,用key值来存放数据,value值存放虚值。
当hashset用来保存自定义对象时,如果要保证hashset保存的元素无序且不重复,需要对象类实现equals方法和hashcode方法。
【解释:使用HashSet来保存自定义对象时,HashSet会使用对象的hashCode()方法来计算元素的hash值,并根据该值来将元素存储在内部的哈希表中。在判断元素是否已经存在时,HashSet会使用equals()方法来比较元素是否相等。
如果我们没有在自定义对象中实现equals()和hashCode()方法,HashSet会默认使用Object类中的equals()和hashCode()方法,这样就会导致HashSet无法正确地判断元素是否相等。因为Object类中的equals()和hashCode()方法只是简单地比较对象的引用地址和hash值,而不是比较对象的内容。】
TreeSet类:
1、保存字符串的时候是有序的,调用了String类的比较接口实现的CompareTo方法定义的规则
2、保存的数据不能为空、保存的数据不能重复
3、有序的话需要实现Comparable接口,或者定义一个实现Comoarator接口的比较器类
存储String类字符串:有序是需要一个比较器,这个比较器需要自己根据排序需求构造,定义比较器类实现Comoarator接口,当然在存储String类型的时候,String类实现了Comparable接口,重写的CompareTo方法是遵循字典排序的。
【这里说一下Comparable接口和Comparator接口的区别】
迭代器:正向迭代器(Iterator)、正反向迭代器(ListIterator)
数据容器很多,有数组、链表、树等等,对数据容器中的元素进行遍历也对应的有各自的方法,对不同数据遍历时核心思想是寻找下一个元素,但要实现两套不同的代码,显然这是不优秀的。
有了迭代器,我们可以将算法和特定的容器分离开来。它可以遍历并选择序列中的对象,而我们不需要了解该序列的底层结构。也就是说功能代码我们只需要实现一套就行。
迭代器只能用在Collection类
正反向迭代器只能用在Collection下的List类
1、正向迭代器:通过iterator() 方法返回得到一个迭代器,注意:iterator() 方法java.lang.Iterable 接口,被 Collection 继承。
(1) 使用next() 获得序列中的下一个元素。
(2) 使用hasNext() 检查序列中是否还有元素。
(3) 使用remove() 将迭代器新返回的元素删除。
2、正反向迭代器是为List类设计的,set接口和map接口都没有实现此接口,支持反向迭代,但反向迭代之前需执行正向迭代,相当于指针要移动到最后一个,才可以反向迭代。
(1)hasPrevious()方法 (2)previous()方法组合
Map<K,V>接口
Map类没有迭代器,所以对Map类的遍历是一个问题,主要通过两种方式,利用set类的迭代器来实现遍历。
(1)将Map中所有的key值存到set集合中,然后通过set的迭代器器加上getValue方法实现遍历。
Set keySet():
(2)将Map集合中的映射关系存入到了Set集合中,而这个映射关系的数据类型就是 Map.Entry。Map.Entry:其实Entry也是一个接口,它是Map接口中的一个内部接口。
Set<Map.Entry<K,V>> entrySet():
/*// 方法一:通过keySet()获取Map集合元素
// 先获取Map集合的所有键的Set集合,通过keySet()方法获取到
Set<String> keySet=map.keySet();
// 有了键的Set集合,就可以获取其迭代器
Iterator<String> it=keySet.iterator();
// 方法二:通过entrySet()获取Map集合元素
// 先获取Map集合中的映射关系的Set集合,通过entrySet()方法获取到
Set<Map.Entry<String,String>> entrySet=map.entrySet();
// 有了键的Set集合,就可以获取其迭代器
Iterator<Map.Entry<String,String>> it=entrySet.iterator();
Map<K,V>的实现类主要有HashMap,HashTable两类
HashMap、HashTable、ConCurrentHashMap区别对比:
HashMap详解:
- 1、get()和put()的具体执行过程和原理?
- 2、equals()和hashnode()都有什么作用?
- 3、hash()【取key的hashcode(),高位运算,取模运算】的实现是什么样的?为什么要这样实现?
- 4、数组扩容的时机和具体流程是什么样的?
- 5、数组长度为什么要是2的幂次方?
- 6、链表树化的时机?
- 7、什么时候会进行红黑树拆分?
- 8、为什么一定要用红黑树,而不是其他树,比如AVL树
HashMap会导致什么样的并发问题?(为什么线程不安全)
(1)put的时候导致多线程数据不一致。
线程A通过hashcode计算得到最终的存储位置时,时间片用完,线程B刚好得到了同样的位置索引并成功存放,线程A重新执行时对此一无所知,会覆盖掉线程B存储的内容。
(2)HashMap的get操作可能因为resize引起死循环。
线程1和线程2在resize过程中形成了循环链表,而刚好get方法获取时造成了死循环。
HashMap:JDK1.8之后,内部实现是数组+链表(红黑树),结构图如下:
HashMap的插入原理:put()方法流程
HashMap初始化怎么设定初始容量的大小:
答:一半初始化不传值默认为16,负载因子是0.75,如果穿值,则会初始化为大于K的2的整数次方,例如穿传的是10,大小则会初始化为16。
HashMap中如何确定数组索引位置:
答:对key取哈希值,高位运算,取模运算,得到数组索引位置,HashMap的扩展为什么一定要是2的次方?目的是为了减少哈希冲突,使table中的数据分布更均匀,取模运算设计为(n-1)&hash
HashMap的hash函数是怎么设计的: 答:HashMap使用的是自定义的算法,当我们进入put方法查看时,看见put方法中return了一个叫putVal的方法,此方法前面把这个key传进去又调用了一个叫hash的方法,这个是它自己的方法,点进去之后会发现,他又将这个key获取了一个hashcode,然后把它右移了16位,跟当前的hashcode做了一个异或。
HashMap和Hashtable的共同点:
- HashMap和Hashtable都是java.util包下的类
- HashMap和Hashtable都实现了Map接口,存储方式都是key-value形式
- HashMap和Hashtable同时也都实现了Serializable和Cloneable接口
- HashMap和Hashtable的负载因子都是0.75
HashMap和Hashtable的区别:
(1)HashMap是非线程安全的,Hashtable是线程安全的。HashTable的方法都是synchronized关键字修饰,所以导致并发效率较低,而HashMap虽然线程不安全,但并发效率较高。
(2)HashMap允许null作为键或值,Hashtable不允许,运行时会报NullPointerException
(3)HashMap添加元素使用的是自定义的hash算法,HashTable使用的是key的hashcode
(4)HashMap在数组+链表中引入了红黑树,HashTable没有
(5)HashMap的初始容量为16,而HashTable为11
问题:Hashmap的初始化容量如何确定
(6)HsahMap扩容是当前容量翻倍,Hashtable是当前容量翻倍+1
问题:HashMap的扩展为什么一定要是2的次方?
(7)HashMap只支持Iterator遍历,Hashtable支持Iterator和Enumeration
遍历HashMap只能借助Collection的Iterator迭代器,用keyset还有一个叫Entryset的两个方法得到
(8)HashMap和HashTable的部分方法不同,HashTable有contains方法,而HashMap有containsKeys和containsValue
ConcurrentHashMap
为什么需要ConcurrentHashMap?
上面的比较可以看出,HashMap和HashTable都不能很好地实现多线程下的高效操作,HashMap本身线程不安全,而HashTable由于同步代码块的设置导致效率较低。所以ConcurrentHashMap的优点就是HashMap和HashTabel的缺点。
实现多线程的方式:(jdk1.7)
1、分段锁,在ConcurrentHashMap中进行了分段(Segment),在并发操作时,只需要对数据所在的Segment加锁即可,不像HashTable是对整个HashTable加锁。里面则是HashEntry的数组和HashMap类似。
2、并行度,决定并行度的其实就是分段的多少,分段越多,理论上同时支持的并发数越高,concurrencyLevel默认为16,如果输入的类似15这种非2的整次幂,会自动调整为大于此数的整次幂。默认16,则在ConcurrentHashMap内部分了16个Segment,最多同时支持16个线程并发写。