1.集合的接口
2.List、Set、Map
Collection 是集合 List、Set 的父接口,它主要有两个子接口:
List:存储的元素有序,可重复。
Set:存储的元素无序,不可重复。
Map是另外的接口,是键值对映射结构的集合。
3.ArrayList和LinkedList的区别
(1)数据结构不同
ArrayList基于数组实现
LinkedList基于双向链表实现
(2)多数情况下,ArrayList更利于查找,LinkedList更利于增删
ArrayList基于数组实现,get(int index)可以直接通过数组下标获取,时间复杂度是O(1);
LinkedList基于链表实现,get(int index)需要遍历链表,时间复杂度是O(n);当然,get(E element)这种查找,两种集合都需要遍历,时间复杂度都是O(n)。
ArrayList增删如果是数组末尾的位置,直接插入或者删除就可以了,但是如果插入中间的位置,就需要把插入位置后的元素都向前或者向后移动,甚至还有可能触发扩容;
双向链表的插入和删除只需要改变前驱节点、后继节点和插入节点的指向就行了,不需要移动元素。
(3)是否支持随机访问
ArrayList基于数组,所以它可以根据下标查找,支持随机访问,当然,它也实现了RandmoAccess接口,这个接口只是用来标识是否支持随机访问。
LinkedList基于链表,所以它没法根据序号直接获取元素,它没有实现RandmoAccess接口,标记不支持随机访问。
(4)内存占用
ArrayList基于数组,是一块连续的内存空间,LinkedList基于链表,内存空间不连续,它们在空间占用上都有一些额外的消耗:
ArrayList是预先定义好的数组,可能会有空的内存空间,存在一定空间浪费
LinkedList每个节点,需要存储前驱和后继,所以每个节点会占用更多的空间
4.如何遍历HashMap、LinkedList
HashMap:
第一种:遍历HashMap的entrySet键值对集合
1.通过HashMap.entrySet()得到键值对集合;
2.通过迭代器Iterator遍历键值对集合得到key值和value值;
第二种:遍历HashMap键的Set集合获取值;
1.通过HashMap.keySet()获得键的Set集合;
遍历键的Set集合获取值;
2.通过HashMap.keySet()获得键的Set集合;
遍历键的Set集合获取值;
第三种:遍历HashMap“值”的集合;
1.通过HashMap.values()得到“值”的集合
2.遍历“值”的集合;
LinkedList
【a】 使用迭代器遍历
使用迭代器遍历
Iterator iterator=students.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());}
【b】 使用size()和get()方法遍历
使用size()和get()方法遍历
for(int i=0;i<students.size();i++){
System.out.println(students.get(i));
}
【c】 使用增强for遍历
使用增强for遍历
for(Student str:students){
System.out.println(str.getName()+"-->"+str.getAge());
}
5.new HashMao(33)实际长度到底是多少,为什么?
36,HashMap的容量是2的倍数。
第一个原因是为了方便哈希取余:
将元素放在table数组上面,是用hash值%数组大小定位位置,而HashMap是用hash值&(数组大小-1),却能和前面达到一样的效果,这就得益于HashMap的大小是2的倍数,2的倍数意味着该数的二进制位只有一位为1,而该数-1就可以得到二进制位上1变成0,后面的0变成1,再通过&运算,就可以得到和%一样的效果,并且位运算比%的效率高得多
HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。
第二个方面:
利用扩容后的大小也是2的倍数,将已经产生hash碰撞的元素完美的转移到新的table中去
6.常见的实现类
List 实现类
ArrayList:基于数组的实现,支持快速随机访问和遍历。适用于需要频繁访问元素和对列表进行大量随机访问的场景。
LinkedList:基于链表的实现,支持高效的插入和删除操作。适用于需要频繁进行插入和删除操作的场景。
Vector:与 ArrayList 类似,但是是线程安全的。适用于多线程环境下需要安全操作的场景,但相比 ArrayList 会有一定的性能开销。
Map 实现类
HashMap:基于哈希表的实现,提供快速的插入、删除和查找操作。不保证元素的顺序,在大多数场景下具有良好的性能。
TreeMap:基于红黑树的实现,按照键的自然顺序或自定义顺序进行排序。适用于需要按照顺序遍历键值对的场景。
LinkedHashMap:在 HashMap 的基础上维护了一个双向链表,保持元素的插入顺序或者访问顺序。适用于需要保持插入顺序或访问顺序的场景。
Hashtable:与 HashMap 类似,但是是线程安全的。适用于多线程环境下需要安全操作的场景,但相比 HashMap 会有一定的性能开销。
Set 实现类:
HashSet:基于哈希表的实现,提供快速的插入、删除和查找操作。不保证元素的顺序。
TreeSet:基于红黑树的实现,按照元素的自然顺序或自定义顺序进行排序。适用于需要按照顺序遍历元素的场景。
LinkedHashSet:在 HashSet 的基础上维护了一个双向链表,保持元素的插入顺序或者访问顺序。适用于需要保持插入顺序或者访问顺序的场景。
7.如何获取一个线程安全的List
使用Vector 代替ArrayList。(不推荐,Vector是一个历史遗留类)
使用Collections.synchronizedList包装ArrayList,然后操作包装后的list。
使用 CopyOnWriteArrayList 代替 ArrayList。
在使用 ArrayList 时,应用程序通过同步机制去控制 ArrayList 的读写
8.Collection和Collections的区别
Collection和Collections是Java集合框架中的两个不同的类,它们的主要区别如下:
-
Collection是Java集合框架中集合类的一个基本接口,用于存储和操作一组对象,包括List、Set、Queue等集合类的父接口。Collection接口提供了许多方法,如添加、删除、查询元素等,可以用于操作集合中的元素。
-
Collections是Java集合框架提供的一个辅助类,包含了各种对集合类进行操作的静态方法。这些方法包括排序、查找、替换、复制、打乱顺序等。Collections类的静态方法可以对各种Collection集合进行操作,使用起来非常方便。
9.线程安全的map
Hashtable
底层数据结构和JDK1.8之前的HashMap一样,是数组+链表。确保线程安全就是在每个方法前加synchronized关键字,即使只是读取数据也会用锁锁住整个对象,并发性很差。
Collections.SynchronizedMap
SynchronizedMap 是 Collections 集合类的私有静态内部类,主要用于将不安全的map包装成安全的map。SynchronizedMap 成员变量包含一个Map类型m用来接收传入的Map对象,一个Object类型的mutex用来充当锁。实现线程安全的方法就是先对mutex上锁,然后执行m的相关方法。和Hashtable一样,同一时刻只能有一个线程调用其方法,并发性能差。
ConcurrentHashMap
JDK1.8之前
底层数据结构:ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment扮演锁的角色,而且其继承了ReentrantLock,所以是一种可重入锁。HashEntry用于存储键值对数据,每个HashEntry数组属于链表结构。
线程安全实现方式:ConcurrentHashMap采用分段锁,就是对每个Segment数组元素加锁,Segment的个数一旦初始化就不能改变,默认大小是16,也就是说默认可以同时支持16个线程并发写。
JDK1.8及以后
底层数据结构:跟HashMap JDK1.8的HashMap结构类似,采用数组+链表/红黑二叉树,链表长度大于8时转换成红黑树。
线程安全实现方式:采用Node+CAS+synchronized+volatile来保证并发安全。value用volatile关键字修饰,读取靠volatile保证线程安全。添加或修改时用synchronized关键字锁定当前链表或红黑二叉树的首节点,保证线程安全,此外还用到大量的CAS操作。JDK.8中对Node元素加锁,锁粒度更细,并发度更高,而且只要不发生哈希冲突,就不会影响其他Node的读写。
10.HashMap的下标是如何计算的
汇总为:table下标i=(table.length- 1) & ((h = key.hashCode()) ^ (h >>> 16))
当我们put的时候,会根据key获取对应的hash值,然后无符号右移16位(>>> 16),在与原本的hash值进行^计算,然后再与table.length-1进行&计算,最终得出需要放入的位置
比如:key = “165156”,HashMap中table数组的大小为16,套入公式,那么我们就会计算出存放位置的下标为13
11.什么是hash冲突
根据key(键)即经过一个函数f(key)得到的结果的作为地址去存放当前的key value键值对(这个是hashmap的存值方式),但是却发现算出来的地址上已经被占用了。这就是所谓的hash冲突。
2.解决Hash冲突
2.1开放定址法
该方法也叫做再散列法,其基本原理是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi 。
2.2再Hash法
这种方法就是同时构造多个不同的哈希函数: Hi=RH1(key) i=1,2,…,k。当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
2.3链地址法(Java就是采用这种方法)
其基本思想: 将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
2.4建立公共溢出区
这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
12.集合数组的区别
一、数组声明了它容纳的元素的类型,而集合不声明。
二、数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
三、数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
四、数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
13.数组的特点
1.数组是一种变量的集合,在这个集合中,所有变量的数据类型都是相同的;
2.每一个数组元素的作用相当于简单变量;
3.同一数组中的数组元素在内存中占据的内存空间是连续的;
4.数组的大小(数组元素的个数)必须在定义时确定,在程序中不可改变;
5.数组名代表的是数组在内存中的首地址。