Java集合整理(全面)

1 集合和数组的区别

数组是固定长度的;集合可变长度的。

数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

2、 List、Map、Set 三个接口各有什么特点?

Java 容器分为 Collection 和 Map 两大类, Collection 集合的子接口有 Set 、 List 、 Queue 三种子接口。我们比较常用的是 Set 、 List , Map 接口不是 collection 的子接口。

Collection 集合主要有 List 和 Set 两大接口

List :一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个 null 元素,元素都有索引。常用的实现类有 ArrayList 、 LinkedList 和 Vector 。

Set :一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个 null 元素,必须保证元素唯一性。 Set 接口常用实现类是 HashSet 、 LinkedHashSet 以及TreeSet 。

Map 是一个键值对集合,存储键、值和之间的映射。 Key 无序,唯一; value 不要求有序,允许重复。 Map 没有继承于 Collection 接口,从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。Map 的常用实现类: HashMap 、 TreeMap 、 HashTable 、 LinkedHashMap 、ConcurrentHashMap

3、 ArrayList 和 LinkedList 的区别是什么?

数据结构实现: ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。

随机访问效率: ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

增加和删除效率:在非首尾的增加和删除操作, LinkedList 要比 ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据的下标。

内存空间占用: LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList ,而在插入和删除操作较多时,更推荐使用 LinkedList 。

LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

4、 ArrayList 和 Vector 的区别是什么?

这两个类都实现了 List 接口( List 接口继承了 Collection 接口),他们都是有序集合

线程安全: Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。

性能: ArrayList 在性能方面要优于 Vector 。

扩容: ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50% 。

Vector 类的所有方法都是同步的。可以由两个线程安全地访问一个 Vector 对象、但是一个线程访问Vector 的话代码要在同步操作上耗费大量的时间。

Arraylist 不是同步的,所以在不需要保证线程安全时时建议使用 Arraylist 。

5、 HashSet 如何检查重复?如何保证数据不可重复的?

向 HashSet 中 add () 元素时,判断元素是否存在的依据,不仅要比较 hash 值,同时还要结合equles 方法比较。

HashSet 中的 add () 方法会使用 HashMap 的 put() 方法。

HashMap 使用键 (Key )计算 Hashcode

HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为 HashMap 的 key ,

并且在 HashMap 中如果 K/V 相同时,会用新的 V 覆盖掉旧的 V ,然后返回旧的 V 。所以不会重复(HashMap 比较 key 是否相等是先比较 hashcode 再比较 equals )。

hashCode ()与 equals ()的相关规定 :

1. 如果两个对象相等,则 hashcode 一定也是相同的

2. 两个对象相等 , 对两个 equals 方法返回 true

3. 两个对象有相同的 hashcode 值,它们也不一定是相等的

4. 综上, equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode() ,则该

class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

== 与 equals 的区别

1. == 是判断两个变量或实例是不是指向同一个内存空间 equals 是判断两个变量或实例所指向的内存空间的值是不是相同

2. == 是指对内存地址进行比较 equals() 是对字符串的内容进行比较

6 、Hashcode的作用?

java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就会比较满。于是有人发明了哈希算法来提高集合中查找元素的效率。 这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。


7 、HashMap和HashTable的区别

1. 出生的版本不一样,Hashtable 出生于 Java 发布的第一版本 JDK 1.0,HashMap 出生于 JDK1.2。

2. 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8)。

3. HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。Hashtable 继承Dictionary。

4. Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的,HashMap 是非线程安全的。

5. Hashtable 的 key 不能为 null,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。

6. HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果key==null ,则其 hash=0 ;至于 value 是否为 null,根本没有判断过。

7. Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时间的,效率很低。HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。

8. Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了Enumeration 的方式。9. 默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的2n+1(n 为上一次的长度)而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。

8 、HashMap 与 ConcurrentHashMap 的异同

1. 都是 key-value 形式的存储数据;

2. HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;

3. HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;

4. HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容;

5. ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,Segment 数组大小默认是 16。

9 、HashMap 的长度为什么是 2 的 N 次方呢?

第一个原因是为了方便哈希取余:

将元素放在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中去,HashMap中的元素在超过负载因子*HashMap大小时就会产生扩容。

10、 HashMap扩容机制

Java 中的 HashMap 在插入元素过程中,如果现有桶的数量不足以容纳更多的键值对时,会触发扩容操作。HashMap 的扩容机制主要包含以下几个要点:

1、初始容量与负载因子:

初始容量:HashMap 初始化时可以指定容量大小,默认容量为 16。

负载因子(load factor):它是衡量 HashMap 容量是否需要扩容的一个阈值,默认值为 0.75。当 HashMap 中的元素数量超过容量与负载因子的乘积时(即 size > capacity * loadFactor),就会触发扩容。

2、扩容过程:

扩容并不是简单地增加桶的数量,而是将当前桶数组的容量翻倍,例如从 16 扩容到 32,再从 32 扩容到 64 等。

扩容后,所有的键值对都会重新进行哈希计算,并根据新的容量分布到新的桶位置上。

这个过程中涉及到了大量元素的迁移,因此是比较耗时的操作。为了减少扩容频率和性能损失,通常在初始化时合理预估未来可能存储的数据量,以设置合适的初始容量。

3、Rehashing:

当HashMap进行扩容时,每个键值对都需要重新计算其在新表中的索引位置,这个过程叫做rehashing。

HashMap使用了链地址法来处理哈希冲突,即同一个桶中可能会有多个元素通过链表或树形结构链接在一起。在扩容时,这些链接的元素也会随着桶的位置变化而移动。

4、扩容后的索引计算:

在扩容前,键值对存储的位置是通过哈希函数计算并取模得到的(index = hash(key) & (table.length - 1))。

扩容后,新的容量是原来容量的两倍,所以对于已存在的键值对,它们在新表中的位置可能是原索引或者原索引 + 原容量,这取决于扩容后哈希值高位的变化。

5、扩容优化:

自JDK1.8开始,为了进一步优化性能,当桶内元素过多时(默认超过8个元素),该桶会被转换为红黑树结构(TreeMap实现),这样能有效降低搜索、插入和删除的时间复杂度。而在扩容后,如果桶内的元素数量小于6,则又会变回链表结构。

综上所述,HashMap的扩容机制保证了容器能够自动调整自身大小以应对数据增长,同时通过rehashing和树化等手段尽量保持高效的操作性能。

11 、HashMap原理,java8做了什么改变

1、HashMap是以键值对存储数据的集合容器

2、HashMap是非线性安全的。

3、HashMap底层数据结构:数组+(链表、红黑树),jdk8之前是用数组+链表的方式实现,jdk8引进了红黑树

4、Hashmap数组的默认初始长度是16,key和value都允许null的存在

5、HashMap的内部实现数组是Node[]数组,上面存放的是key-value键值对的节点。HashMap通过put和get方法存储和获取。

6、HashMap的put方法,首先计算key的hashcode值,定位到对应的数组索引,然后再在该索引的单向链表上进行循环遍历,用equals比较key是否存在,如果存在则用新的value覆盖原值,如果没有则向后追加。

7、jdk8中put方法:先判断Hashmap是否为空,为空就扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束。

8、Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。

9、HashMap的get方法就是计算出要获取元素的hash值,去对应位置获取即可。

10、HashMap的扩容机制,Hashmap的扩容中主要进行两步,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。

11、HashMap大小为什么是2的幂次方?效率高+空间分布均匀

12、 HashMap什么情况下转红黑树,什么情况下又转链表?

1、当链表的长度达到 8 ,并且数组长度是否达到 64。如果达到 64,则进行链表转红黑树的操作。否则,只是发生一次 resize,散列表扩容,还有种情况,当数组很小,但是链表过长,首先扩容数组,而不是转换树。

2、当树中的元素经过删除或者其他原因调整了大小,当小于等于 6 后,将会导致树退化成链表,中间有个过渡值 7,可以防止频繁的树化与退化。

13 、HashMap为什么不直接用红黑树呢?

最开始使用链表的时候,空间占用是比较少的,而且由于链表短,所以查询时间也没有太大的问题。可是当链表越来越长,需要用红黑树的形式来保证查询的效率。对于何时应该从链表转化为红黑树,需要确定一个阈值,这个阈值默认为 8.

如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,当长度为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。

事实上,链表长度超过 8 就转为红黑树的设计,更多的是为了防止用户自己实现了不好的哈希算法时导致链表过长,从而导致查询效率低, 而此时转为红黑树更多的是一种保底策略,用来保证极端情况下查询的效率。

14 、红黑树的特点?

1、节点颜色。红黑树的每个节点不是红色就是黑色。

2、根节点颜色。根节点是黑色的。

3、叶节点颜色。叶节点(即空节点)是黑色的。

4、红色节点的子节点。如果一个节点是红色的,则它的两个子节点都是黑色的。

5、黑色节点数量。从任一节点出发到其所有叶子节点的路径上,必须包含相同数目的黑色节点。

15 、TreeSet 有什么特点?

(1)TreeSet 是 Java 中的一种集合类型,它实现了 Set 接口,并使用红黑树作为底层数据结构。TreeSet 具有以下特点:

有序性:TreeSet 中的元素是按照自然顺序或指定的比较器顺序排序的,因此可以在集合中快速进行有序访问。

唯一性:TreeSet 中不允许存储重复元素,保证了集合中的所有元素都是唯一的。

可排序性:TreeSet 中的元素必须实现 Comparable 接口或通过构造函数提供 Comparator 对象,以便进行元素的比较和排序。

高效性:TreeSet 的内部实现采用了红黑树,使得元素的查找、插入和删除操作具有较高的效率。

(2)需要注意的是,由于 TreeSet 是有序的,因此其性能通常比 HashSet 稍慢,特别是在添加和删除元素时。但是,对于需要有序性的场景,TreeSet 是一个非常有用的集合类型。

16 、Comparable 和 Comparator 有什么区别?

Comparable 接口和 Comparator 接口都用于比较对象。它们之间的主要区别是

(1)Comparable 是在对象自身内部实现的比较方法,而 Comparator 则是在外部单独实现比较方法。

(2)实现 Comparable 接口的类可以直接进行排序,而不需要使用其他的比较器。这是因为 Comparable 接口定义了一个 compareTo 方法,该方法指定了如何比较两个对象。该方法的返回值为负数、零或正数,表示当前对象小于、等于或大于另一个对象。

(3)与此相反,Comparator 接口定义了一个 compare 方法,该方法指定了如何比较两个对象。实现 Comparator 接口的类可以用于在不改变对象自身的情况下对对象进行排序。

17 、Collection 和 Collections 有什么区别?

Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。

Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法: Collections. sort(list)。

  • 38
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心对元&鑫鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值