1、java 容器都有哪些?
Java中的容器主要可以分为两大类:Collection和Map。
Collection:
-
List:有序的集合,允许重复元素,可以通过索引访问元素。常用的实现类包括:
- ArrayList:实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
- LinkedList:链表实现,提供了在列表的开头和结尾进行操作的便利方法。
- Vector:和ArrayList类似,但是是同步的,因此是线程安全的。但同步需要花费机器时间,所以Vector的执行效率要低于ArrayList。
- Stack:栈是Vector的一个子类,它实现了一个后进先出(LIFO)的堆栈。
-
Set:不允许重复元素的集合,不保证元素的顺序。常用的实现类包括:
- HashSet:基于哈希表的实现,提供了高效的添加、删除和查找操作。
- TreeSet:基于红黑树的实现,提供了自然排序或自定义排序的功能。
- LinkedHashSet:哈希表和链表实现的结合,可以维护元素的插入顺序。
Map:
Map提供key到value的映射,一个Map中不能包含相同的key,每个key只能映射一个value。常用的实现类包括:
- HashMap:基于哈希表的实现,提供了高效的添加、删除和查找操作。
- Hashtable:和HashMap类似,但是是同步的,因此是线程安全的。但同步需要花费机器时间,所以Hashtable的执行效率要低于HashMap。
- TreeMap:基于红黑树的实现,提供了自然排序或自定义排序的功能。
- LinkedHashMap:哈希表和链表实现的结合,可以维护元素的插入顺序。
- ConcurrentHashMap:适用于多线程环境的哈希表实现,提供了高效的并发性能。
此外,Java还提供了其他的容器类,如ArrayDeque(双端队列)、PriorityQueue(优先队列)等,它们分别提供了不同的功能和性能特点。
2、Collection 和 Collections 有什么区别?
Collection是集合的接口,其实现类有List和Set;
Collections是工具类,包含许多有关集合操作的静态多态方法,可以直接使用
3、List、Set、Map 之间的区别是什么?
List、Set和Map在Java中都是集合框架(Collections Framework)的重要部分,但它们之间有明显的区别:
-
List(列表):
- 有序性:List中的元素按照它们被添加的顺序进行存储,并且可以通过索引访问每个元素。
- 可重复性:List允许存储相同的元素多次。也就是说,可以在List中添加重复的元素,并且它们可以保持各自的位置和顺序。
- 常见的实现类:ArrayList(动态数组实现,适用于查找和随机访问操作)和LinkedList(链表实现,提供了在列表的开头和结尾进行操作的便利方法)。
-
Set(集合):
- 无序性:Set集合中的元素是无序的,不能通过下标或者位置来访问元素。遍历Set集合时获取元素的顺序是不确定的。
- 元素唯一性:Set集合中的元素是唯一的,不会存在重复的元素。当试图添加重复元素时,新元素不会被添加进集合中。
- 可变性:Set集合的元素可以被增加、删除或者修改,因此是可变的。
- 哈希表实现:Java中的HashSet是通过哈希表来实现的,因此Set集合的插入、查询和删除操作都具有较快的速度。
- 线程不安全:Set集合不是线程安全的,如果需要在多线程环境下使用,需要进行同步处理。
- 支持数学中的集合运算:Set集合支持并集、交集、差集等运算,方便我们进行集合的操作。
-
Map(映射):
- 键值对存储:Map中存储的元素是以键值对的形式保存的,每个键值对包含一个键对象和一个值对象,可以根据键对象获取对应的值对象。
- 键的唯一性:在Map中,每个键对象是唯一的,不能存在相同的键对象,如果向Map中添加一个已经存在的键对象,则会替换掉原有的值对象。
- 支持null键和null值:HashMap和Hashtable支持null键和null值,TreeMap和ConcurrentHashMap不允许有null键。
- 无序性:HashMap和Hashtable等散列表实现的Map在存储键值对时并不是以顺序方式存储的,因此不能保证元素的顺序。但如果使用LinkedHashMap,则可以按照插入顺序或访问顺序进行遍历。
- 高效性:访问和修改Map集合中的元素都非常高效,可以通过哈希表实现,时间复杂度为O(1)。
- 可以存储不同类型的键值对:Map的键和值可以是任何类型的对象,只要它们能够被正确地哈希和比较。
总的来说,List、Set和Map在Java集合框架中各有其特定的用途和特点。List用于存储有序且可重复的元素,Set用于存储无序且唯一的元素,而Map则用于存储键值对形式的元素,其中键是唯一的。
4、HashMap 和 Hashtable 有什么区别?
HashMap不是线程安全的,HashTable是线程安全的
HashMap允许Null Key和Null Value,HashTable不允许
5、如何决定使用 HashMap 还是 TreeMap?
- 性能:
- HashMap通常具有更高的性能,因为它的查找、插入和删除操作的时间复杂度都是O(1)。它通过哈希函数直接定位到数组的某个位置。
- TreeMap的查找、插入和删除操作的时间复杂度是O(log n),因为它基于红黑树实现。在数据量较大时,HashMap的性能优势会更加明显。
- 排序:
- HashMap是无序的,它的存储和遍历顺序与元素的插入顺序无关。它只保证在单线程环境下遍历结果的一致性。
- TreeMap则是有序的,它可以根据键的自然顺序或自定义的Comparator顺序进行排序和遍历。这使得TreeMap在需要按照一定顺序处理数据的情况下更加适用。
- 内存占用:
- TreeMap由于内部采用红黑树数据结构,因此它的内存占用相对较高,比HashMap稍微大一些。
- 线程安全性:
- HashMap和TreeMap都是非线程安全的。如果你在多线程环境下使用,需要额外的同步处理。但Java也提供了线程安全的ConcurrentHashMap作为HashMap的替代品。
- 具体需求:
- 如果你的应用需要高性能的键值对存储,且不关心元素的顺序,那么HashMap是更好的选择。
- 如果你的应用需要按照键的排序顺序存储和遍历键值对,那么TreeMap是更好的选择。
- 其他特性:
- TreeMap还提供了诸如
floorKey()
,ceilingKey()
,lowerKey()
,higherKey()
等方法,这些方法可以根据给定的键找到最接近的键,这在某些场景下可能很有用。
- TreeMap还提供了诸如
6、说一下 HashMap 的实现原理?
HashMap 的实现原理主要基于数组、链表和红黑树。以下是其具体的实现原理:
- 初始化:HashMap 创建一个初始容量为 16 的数组,称为“哈希桶”。
- 哈希函数:当添加一个键值对时,HashMap 使用哈希函数来计算该键的哈希码。这个哈希码用来决定该键值对在数组中的索引位置。
- 存储键值对:
- 如果该位置上没有其它键值对,则直接将该键值对存储在该位置上。
- 如果该位置上已经存在一个或多个键值对(发生了哈希冲突),则遍历链表或树,查找是否已经存在相同的键。
- 如果存在,则更新对应的值。
- 如果不存在,则将该键值对添加到链表或树的末尾。
- 数据结构调整:
- 如果链表长度超过了 8,则将链表转化为红黑树,以提高查找性能。
- 如果树的节点数量少于 6,则将树转换回链表,以节省内存。
- 扩容:当数组的使用量达到了负载因子(默认为 0.75)时,HashMap 会自动进行扩容。扩容操作会将数组长度加倍,并重新计算所有键值对的哈希码,然后将它们放入新的哈希桶中。
在 Java 的不同版本中,HashMap 的扩容机制可能有所不同。例如,在 JDK 1.8 中,HashMap 在第一次调用 put 方法时才会初始化数组,并且在扩容过程中会正序遍历原来的数组,并保持链表中节点的相对顺序不变。
总的来说,HashMap 通过哈希函数和哈希桶实现了快速定位元素位置的功能,并通过链表和红黑树解决了哈希冲突的问题。同时,它还具有较好的扩展性和灵活性,可以根据需要自动进行扩容和调整数据结构。
7、说一下 HashSet 的实现原理?
HashSet是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet的操作相对比较简单,相关HashSet的操作,基本上都是直接调用底层的HashMap的相关方法来完成,HashSet不允许有重复的值,并且元素是无序的
8、ArrayList 和 LinkedList 的区别是什么?
ArrayList的数据结构是动态数组;LinkedList的数据结构是双向链表。
ArrayList比LinkedList在随机访问的时候效率要高,因为LinkedList是线性的数据结构,需要依次往后查找
在非首尾的增删操作,LinkedList要比ArrayList的效率要高,因为ArrayList在操作增删时要影响其他元素的下标
总结:需要频繁读取集合中的元素时,推荐使用ArrayList;插入和删除操作较多时,推荐使用LinkedList
9、如何实现数组和 List 之间的转换?
在Java中,数组(Array)和List之间的转换是常见的操作。
数组转List
你可以使用Arrays.asList()
方法将数组转换为List。但是要注意,Arrays.asList()
返回的是一个固定大小的列表,不支持add
或remove
操作(会抛出UnsupportedOperationException
),因为它返回的实际上是一个Arrays的内部类,这个内部类实现了List接口但没有实现可变大小的方法。
如果你需要一个可以修改的List,你需要将返回的List转换为一个新的ArrayList或其他可修改的List实现。
int[] intArray = {1, 2, 3, 4, 5};
List<Integer> intList = Arrays.stream(intArray).boxed().collect(Collectors.toList());
// 或者对于对象数组
String[] strArray = {"a", "b", "c"};
List<String> strList = Arrays.asList(strArray);
// 如果需要修改List,可以转换为ArrayList
List<String> modifiableStrList = new ArrayList<>(Arrays.asList(strArray));
List转数组
对于List转数组,你可以使用List.toArray()
方法,但这个方法默认返回的是Object数组,所以你可能需要指定一个类型参数或者使用toArray(T[] a)
方法。
List<String> strList = Arrays.asList("a", "b", "c");
// 使用无参toArray方法,需要强制类型转换
String[] strArray1 = (String[]) strList.toArray();
// 使用带类型参数的toArray方法
String[] strArray2 = strList.toArray(new String[0]);
// 或者指定数组大小(如果知道大小的话),但通常不需要
// String[] strArray3 = strList.toArray(new String[strList.size()]);
10、ArrayList 和 Vector 的区别是什么?
相同点:都实现了List接口,都是有序集合
区别:Vector是线程安全的,ArrayList不是线程安全的;
当Vector或ArrayList中的元素超过它的初始大小时,Vector会将容量翻倍,而ArrayList只会将容量扩大50%
11、Array 和 ArrayList 有何区别?
Array类型的变量在声明时必须实例化;ArrayList可以只是先声明;
Array大小是固定的,而ArrayList的大小是动态变化的;
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型
12、怎么确保一个集合不能被修改?
可以使用Collections.unmodifiableCollection(Collection c) 方法创建一个只读集合
这样改变集合的任何操作都会抛出Java. lang. UnsupportedOperationException异常。
13、Iterator 和 ListIterator 有什么区别?
ListIterator有add()方法,可以向List中添加对象,而Iterator不能;
ListIterator有hasPrevious()和previous()方法,可以向前遍历,Iterator不能;
ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现;
ListIterator可以实现对对象的修改,使用set()实现;而Iterator只能遍历,不能修改