Java集合面试(看这一篇就够了)
该集合中的数据均为引用数据类型,在Java程序中基本数据类型的值无需手动包装即可使用,并且该集合共有增删查改等基本操作方法。此外,在Java语言中,ArrayList类继承自AbstractList抽象类,并实现了List接口这一功能。而List接口本身又继承自Collection接口这一设计基础。在编程领域中,默认情况下,默认情况下,默认情况下,默认情况下,默认情况下,默认情况下,默认情况下,默认情况下

Collection接口包括列表、集合和队列接口.\color{#FF0000}{集合主要关注效率而非安全性}
List接口下有ArrayList、Vector和LinkedList实现类
Java中的List接口通常采用哪些常见实现类?这些不同的实现类不仅各自具有鲜明的特性,在实际应用中也展现出各自的适用范围。
ArrayList:
-
底层采用了数组实现,并具备动态扩容功能。
-
该系统特别适合需要快速支持随机访问与遍历操作的技术场景。
-
插入与删除操作的效率相对较弱,在实际应用中涉及数据元素位置的调整。
LinkedList:
-
底层采用双链表结构实现。
-
插入与删除操作具有较高的效率,在节点调整的数量上较为有限。
-
随机查找与遍历操作的速度较低,在实际应用中需逐一进行查找。
Vector:
类似于一种实现了 thread-safe 特性的数据结构类似于 ArrayList
CopyOnWriteArrayList:
- 类似于ArrayList,
但具有线程安全性。 - 为了实现并发访问而复制整个数组,
主要适用于读多于写的情形。 - 因为每次写操作都需要复制整个数组,
所以该方法在处理大量 writes时效率相对较低。
各类List实现类在不同应用场景中展现出各自的独特优势与适用范围。通常情况下,在无需多线程操作且要求频繁更新元素时,则建议选用ArrayList。若需频繁插入删除元素或在多线程环境中操作,则推荐选择LinkedList、Vector或CopyOnWriteArrayList。根据具体需求与性能考量的不同情况来决定选用何种List实现类。
ArrayList arrayList = new ArrayList(100);
arrayList.ensureCapacity(200);
Vector vector = new Vector(100);
vector.ensureCapacity(200);
ArrayList集合为什么查找快?
在搜索过程中利用索引结构精准识别每个元素的位置;在添加操作中使用equals方法来判断是否存在重复项,并确保后续所有插入的元素将依次向前移动一位位置
list集合为什么不用Iterator迭代,要使用增强for循环?
当对list集合使用Iterator进行遍历操作时
list集合的去重:
List<Integer> list = mew ArrayList<Integer>();
HashSet<Integer> set = new HashSet<Integer>(list);
list.clear();
list.addAll(set);
在集合接口(Set)的基础上包含了两个实现了特定功能的类:HashSet 和 TreeSet。其中,在 HashSet 中拥有 LinkedHashSet 作为子类
Set集合不支持快速查找无序,并且没有内置的快速查找机制;允许存储不允许存在重复项;由于不提供快速获取键的方法而无法直接遍历所有键值对;可以通过比较对象相等性来检查是否存在相同的实例;其内部实现基于映射数据结构;键。
两者的区别主要体现在以下几个方面:
- HashSet基于哈希表实现,并由数组和链表组成;而TreeSet则是基于二叉树的数据结构实现。
- HashSet允许表中存储null值;但TreeSet中的数据节点不允许插入null值。
- 当使用HashSet存储数据时,所涉及对象必须实现hashCode()方法;并且在存储过程中会先计算出该对象对应的hashcode值来比较集合中的hashcode列表是否存在该值;如果不存在,则将该对象存入列表中;如果存在,则需通过equals方法判断其内容是否与列表中已存在的对象相同;如果不相同则继续存入。
- 前两个集合(HashSet和TreeSet)在添加元素时均为无序不可重复存储;而后者(TreeSet)则为有序不可重复存储;因此常用于处理列表中的去重问题
请问list集合如何去除重复元素?
Map接口下有HashMap、Hashtable和SortedMap实现类
该map集合是由set集合演变而来的。
在Java语言中,默认提供了四种主要实现方式:
- HashMap(无序不重复-无序),其子类LinkedHashMap(有序不重复-自然有序)、Hashtable(无序不重复-无序)及TreeMap(无序不重复-大小有序)。
它们的主要特点如下:
- HashMap具有线程不安全特性但效率较高,在键值对允许null的情况下可实现对value值的去重功能。
- Hashtable虽然具有线程安全特性但效率较低且要求键值对均非null会引发空指针异常其线程安全性系通过sychronized机制实现。
- 两者的键值对均无法重复若插入相同的键值对后续的value会覆盖前者的value但不会导致错误信息产生。
- 在开发过程中建议优先采用HashMap但在多线程环境下需配合使用Java提供的Collections工具类以确保整体系统的线程安全性其中HashMap的线程同步功能至关重要
HashMap<Integer, String> map = new HashMap<Integer, String>();
map = (HashMap<Integer, String>) Collections.synchronizedMap(map);
面试关联:
不得不聊的是map集合的几种遍历方式:
Java中的Map集合的多种遍历途径及其性能表现对比(涉及lambda表达式的应用)
HashMap中value值去重:
方法论:通过将HashMap中的键值对进行交换并赋值给一个新的HashMap实例,在该操作过程中因键值具有唯一性特性而在后续步骤中可去除重复的键值;接着将键值对转移至另一个新的HashMap实例中
LinkedHashSet<Integer> set2 = new LinkedHashSet<Integer>(list);
list.clear();
list.addAll(set2);
HashMap的按值排序:
- 将该数据集合转换为 LinkedList 类型的数据集合。
- 通过 Collections 排序算法对 list 数据集进行排序。
- 将排序后的 list 数据集赋值给 LinkedHashMap 对象。
具体代码实现:
// 1.将map集合转成一个LinkedList集合
Set<Entry<Integer, String>> sets = map.entrySet();
LinkedList<Entry<Integer, String>> linkedList = new LinkedList<Entry<Integer, String>>(
sets);
// 2.用Collections的sort方法排序list集合
Collections.sort(linkedList, new Comparator<Entry<Integer, String>>() {
@Override
public int compare(Entry<Integer, String> o1,
Entry<Integer, String> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
// 3.将排序后的list赋值给LinkedHashMap
Map<Integer, String> map1 = new LinkedHashMap();
for (Entry<Integer, String> entry : linkedList) {
map1.put(entry.getKey(), entry.getValue());
}
for (Entry<Integer, String> entry : map1.entrySet()) {
System.out.println(
"key:" + entry.getKey() + " value:" + entry.getValue());
}
如何实现数组和 List 之间的转换?
将 List 转换为 数组:可以通过 调用 ArrayList 类 的 toArray() 方法 实现;
使用 Arrays.asList() 方法 来 实现 将 数组 转 成 List 的 过程。
说一下 HashSet 的实现原理?
该集合基于Map数据结构实现
该集合中的元素均存储在Map键位置上
该Map所有键对应的值统一设置为当前赋值
说一下 HashMap 的实现原理?
HashMap采用数组加链表结构 在1.8版本之前这一设计主要基于数组存储加上单向链表实现动态扩展功能
数据存储节点为entry类型 插入操作中使用头插法进行初始节点放置
然而 在扩容过程中存在resize操作 随后调用transfer方法对当前容器中的所有entry执行rehash操作
其中entry元素执行了rehash操作 可能导致后续出现链表循环 结构 这种情况会导致后续获取操作出现死循环 也是导致其线程不安全的主要原因之一
当我们向HashMap中添加元素时 首先根据键值key计算其哈希码得到索引值
如果目标数组在该索引位置尚未占用 则直接将该键值存入该位置 如果已有键值存在 则使用equals方法判断是否相同
如何决定使用 HashMap 还是 TreeMap?
对于在Map数据结构中进行插入、删除和定位元素这些操作而言,HashMap提供的是最高效的实现方案。当需要处理一个有序的key集合时,在完成遍历过程的基础上TreeMap更适合作为选择
JDK1.8中HashMap为什么当链表长度大于等于8时会转成红黑树?
链表个数多了转换为红黑树的话可以提高查找速度。游戏开发中数组用途广泛,在应用中追求高效快速。哈希碰撞的问题在于不同键经过哈希算法计算可能得到相同的值。
list!=null、list.size()>0和list.isEmpty的区别?
当list变量指向内存中的对象为空时(即list == null),它实际上并不在堆内存空间中存在也没有对应的内存地址。然而,在这种情况下(即list.size() == 0),虽然对象已经被创建并分配了内存空间(即有对应的内存地址),但此时对象中没有任何数据存储其中。当对象的数据量增加时(即数据元素数量增大),大小属性也会随之变化(即size()属性会更新)。如果未对变量进行初始化操作,则会导致调用任何方法都会抛出空指针异常;而当list.size() == 0时,则表示该列表刚刚被创建出来,并且还没有存入任何数据内容。这里需要注意的是:调用isEmpty()方法会先获取当前列表大小后再进行判断并返回结果;而直接访问列表大小属性的方法则会立即返回当前大小数值信息,并且运行速度更快捷一些。查看源码:
public boolean isEmpty() {
return size == 0;
}
从源码可以看出isEmpty相较于List.size多出一个查询步骤。
区别在于是否存在;如果存在,则判断其中是否有元素。
①是否有瓶子?list != null
②瓶子里有水吗?list.size() != 0
判断时必须先确定是否有瓶子。
如果没有瓶子直接去判断是否有水会导致nullException。
③另外:
list.add(null)会导致list.isEmpty()返回false,
但list.size()返回1,
因此代码中必须避免出现这种陷阱。
举个形象的例子:
比如我的list是一个空着的水杯(list),而你没有(arrs),
那你是null,
我的size为0。
你想装水需要去买个水杯(new ArrayList();),
我就可以直接装水(list.add(水))。
你要是没有杯子直接倒水,
那水就流出去啦(导致null指针异常)。
所以用做判断的时候经常连用list!=null && list.size()!=0,
或者arrs!=null && arrs.length>0
