1.Java中集合是什么?
Java中的集合是Java编程语言提供的一种数据结构,用于存储、操作和处理一组数据。它是Java编程语言中非常重要的一部分,广泛应用于各种Java应用程序的开发中。Java集合框架提供了一系列类和接口,用于实现不同类型的集合。
2.集合的关系是什么?
3.每种集合实现的优缺点是什么?
- Collection接口:
- List接口:有序, 可重复
- ArrayList
- 优点: 底层数据结构是数组,查询快,增删慢。
- 缺点: 线程不安全,效率高
- LinkedList
- 优点: 底层数据结构是链表,查询慢,增删快。
- 缺点: 线程不安全,效率高
- Vector
- 优点: 底层数据结构是数组,查询快,增删慢。
- 缺点: 线程安全,效率低, 已给舍弃了
- ArrayList
- Set接口:无序,唯一
- HashSet:无序,唯一
- 底层数据结构是哈希表。
- 如何来保证元素唯一性? 依赖两个方法:hashCode()和 equals()
- LinkedHashSet:FIFO 插入有序,唯一
- 底层数据结构是链表和哈希表。
- 由链表保证元素有序
- 由哈希表保证元素唯一
- TreeSet:唯一,有序
- 底层数据结构是红黑树。
- 如何保证元素排序的呢?自然排序比较器排序
- 如何保证元素唯一性的呢?根据比较的返回值是否是 0 来决定
- HashSet:无序,唯一
- List接口:有序, 可重复
- Map接口:
- HashMap
- 基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和null 键,线程不安全。
- HashTable
- 线程安全,低效,不支持 null 值和 null 键;
- LinkedHashMap
- 线程不安全,是 HashMap 的一个子类,保存了记录的插入顺序;
- TreeMap
- 能够把它保存的记录根据键排序,默认是键值的升序排序,线程不安全。
- HashMap
扩展:问到List 和 Map、Set 的实现类有哪些也可以按照上面的回答,但不需要回答优缺点,问什么就会回答什么。
4.集合和数组的区别?
区别:数组长度固定 集合长度可变
数组中存储的是同一种数据类型的元素,可以存储基本数据类型,也可以存储引用数据类型;
集合存储的都是对象,而且对象的数据类型可以不一致。在开发当中一般当对象较多的时候,使用集合来存储对象。
5.List 和 Map、Set 的区别
- List 和 Set 是存储单列数据的集合,Map 是存储键值对这样的双列数据的集合;
- List 中存储的数据是有顺序的,并且值允许重复;
- Map 中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;
- Set中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的 hashcode 决定,即位置是固定的(Set 集合是根据hashcode 来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set 中的元素还是无序的)。
6.ArrayList和LinkedList有什么区别?
ArrayList 和 LinkedList 是 Java 集合框架(Java Collections
Framework)中两种常用的列表(List)实现,它们之间存在一些关键的区别,主要体现在它们的内部实现、性能特点、以及适用场景上。
- 内部实现
- ArrayList:基于动态数组实现。当元素被添加到 ArrayList
中时,如果数组的大小不足以容纳所有元素,则会自动扩容(通常是当前容量的1.5倍)。这意味着 ArrayList在随机访问(即通过索引访问)时非常快,但在列表的开头或中间插入、删除元素时可能会比较慢,因为可能需要移动大量的元素。 - LinkedList:基于双向链表实现。每个元素都包含了指向前一个元素和后一个元素的引用。因此,LinkedList在插入、删除元素时(特别是在列表的开头或中间)通常比ArrayList 快,因为不需要移动其他元素。然而,由于需要遍历链表来访问特定位置的元素,所以 LinkedList在随机访问时相对较慢。
- ArrayList:基于动态数组实现。当元素被添加到 ArrayList
- 性能特点
- 随机访问:ArrayList 优于 LinkedList。
- 插入和删除操作:在列表的开头或中间位置时,LinkedList 通常优于
ArrayList;但在列表的末尾添加元素时,两者的性能差异不大。 - 内存消耗:LinkedList 的每个节点除了存储数据外,还需要存储两个引用(指向前一个和后一个节点的引用),因此其内存消耗通常比ArrayList 更高。
- 适用场景
- 如果你需要频繁地在列表的任意位置进行插入和删除操作,并且不关心随机访问的性能,那么 LinkedList 可能是更好的选择。
- 如果你需要频繁地通过索引来访问元素,且插入和删除操作主要在列表的末尾进行,那么 ArrayList 将是更高效的选择。
- 线程安全性
- 两者都不是线程安全的。如果你需要在多线程环境中使用它们,那么你需要自己实现同步控制,或者使用Vector、Collections.synchronizedList() 方法来包装它们,或者使用 Java 并发包中的并发集合类(如ConcurrentHashMap 的分割器)。
7.说一下ArrayList的扩容机制?
- 初始容量
- 当创建ArrayList时,可以指定一个初始容量(initialcapacity)。如果没有指定,则默认容量为10(这个值在ArrayList的源码中定义为DEFAULT_CAPACITY)。
- 扩容触发条件
- 当向ArrayList中添加元素,且当前容量不足以容纳新元素时,就会触发扩容操作。具体来说,当调用add()方法时,ArrayList会先检查当前容量是否足够,如果不够,则进行扩容。
- 扩容过程
- 计算新容量:ArrayList的扩容操作通过调用grow()方法实现,该方法会计算出一个新的容量值。在Java8及之后的版本中,默认情况下,新容量是旧容量的1.5倍(即newCapacity = oldCapacity + (oldCapacity>> 1))。这意味着每次扩容后,ArrayList的容量都会以1.5倍的速度增长。
- 创建新数组:根据计算出的新容量,ArrayList会创建一个新的数组,用于存储扩容后的元素。
- 复制元素:将原数组中的所有元素复制到新数组中。这一步是扩容操作中最耗时的部分,因为它需要遍历原数组并将每个元素复制到新数组的相应位置。
- 更新引用:将ArrayList的内部数组引用更新为新创建的数组,以便后续操作都在新数组上进行。
- 扩容操作的性能影响
- 由于扩容操作涉及创建新数组和复制元素,因此它具有一定的性能开销。如果频繁地向ArrayList中添加大量元素,扩容操作可能会成为性能瓶颈。
- 为了减少扩容操作的频率,可以在创建ArrayList时指定一个较大的初始容量,或者在添加大量元素之前使用ensureCapacity()方法显式地增加容量。
- 扩容策略的历史变化
- 值得注意的是,在Java 7及之前的版本中,ArrayList的扩容策略是容量翻倍(即新容量是旧容量的两倍)。但从Java8开始,为了优化内存使用,扩容策略改为了容量增长1.5倍。
- 总结
- ArrayList通过动态扩容机制,实现了其容量的自动增长,从而能够灵活地存储更多的元素。然而,扩容操作会带来一定的性能开销,因此在设计程序时应合理预估所需容量,以减少扩容次数,提高性能。
未完结,待续…