1. Java集合类框架的基本接口有哪些?
Java中的集合主要由Collection(单列集合)和Map(双列集合)这两个接口派生而来
Collection下有子接口List,Queue,Set(无序)
List接口的实现类有ArrayList,vector(线程安全),stack,LinkedList(双向链表)
Set接口的实现类有hashset(底层是hashMap),sortedSet(接口)、treeSet(底层是treeMap,遍历有序),LinkedHashSet
线程安全的集合:Vector、HashTable、ConcurrentHashMap、Stack(继承Vector)、ArrayBlockingQueue、ConcurrentLinkedQueue
2.HashMap工作原理、插入、扩容
前置知识:
二叉树:每个节点至多只有两个子节点的树
搜索二叉树:满足当前节点的左子树上的节点不能大于当前节点,右子树上的节点不能小于当前节点的二叉树
红黑树:一种自平衡的搜索二叉树,能保证遍历,插入,删除的时间复杂度永远是O(logn)
红黑树规则:
红黑:节点只有红黑两种颜色,根节点和叶节点一定是黑色,红节点的子节点一定是黑色
树:叶节点一定是null,任一节点到叶节点的所有路径包含相同数量的黑节点
散列表:又称Hash表,是一种kv存储结构,存储时先计算k经过哈希函数运算后的值,然后根据这个值将v存放在对应的位置
HashMap底层
一、哈希函数
HashMap 的核心思想是哈希映射,即将任意长度的输入(即键)通过哈希函数变换成固定长度的输出(即该键在数组中的索引位置),并将该键和值存储到找到的索引位置处。具体来说,哈希函数需要满足以下两个条件:
【1】散列均匀。这意味着不同的键应该有不同的哈希值,并且此哈希值在数组中分布均匀,也就是不要让大量哈希值都集中到一个区域。
【2】计算速度快。为了避免计算哈希值过于缓慢,需要使用高效的哈希函数。
在 Java 中,Java 8 以前使用的是传统的拉链法解决哈希冲突(即多个键映射到同一个数组下标的情况),而 Java 8 之后为了进一步提升性能,采用了链表和红黑树相结合的方式来处理哈希冲突。
二、数组+链表的实现
HashMap 的内部结构是一个数组,数组中每个元素称为桶。桶里放的元素类型是 Entry 类型的对象,该类包含了键和值。当 HashMap 中加入一个键值对时,它会首先根据键获取其哈希码(即通过根据 hash() 方法计算得到),然后通过这个哈希码在数组中找到相应桶,并把键值对存储在桶中。
如果多个键映射到同一索引位置上,就需要通过链表将它们串联起来,而 JDK 1.8 之前版本中采用的则是最基础的链式结构。但随着链表长度的增加,查询效率会逐渐变低,甚至还可能造成链表成环并导致死循环等情况,因此,Java 8 引入了一种新的机制:当链表长度超过阈值(默认为 8)时,会将链表转化为红黑树,以提高查询效率。在使用红黑树优化后,HashMap 的查询性能会更进一步的提升。插入:
1、判断数组是否为空,如果为空调用resize方法,初始化一个容量16的数组
2、对key进行hash运算,得到数组中的索引,table下标i=(table.length- 1) & ((h = key.hashCode()) ^ (h >>> 16)),如果索引指向的位置为空,则直接插入一个新的node
3、如果不为空,则判断当前key是否存在,如果存在直接进行替换value操作
4、如果不存在,判断当前Node节点是否是红黑树结构,如果是树类型,则按照树的方式去追加节点,否则在链表尾部插入数据
5、最后判断链表长度是否大于8,如果大于8,会先尝试扩容,判断数组长度是否小于64,是就扩容,否则链表转换为红黑树。
6、插入操作结束后,判断如果hashmap的元素个数超过了负载因子*容量的阈值,则需要进行扩容操作
7、扩容操作会将原来数组的元素分配到新的数组中,重新每个元素在数组中的下标
8、扩容操作完成后,再将元素插入到新的数组中
扩容:
1、在添加元素或初始化时扩容,执行resize方法,第一次初始化长度为16,以后每次在元素达到容量*负载因子(0.75)时,进行扩容
2、每次扩容操作后,容量都是之前的2倍
3、扩容之后,会创建一个新的数组,然后把老的数组中的元素移动到新的数组中
4、如果是链表,则计算每个链表元素的下标,然后移动到新的数组
5、如果是红黑树,则走红黑树的添加。
3. 为什么Java集合类没有实现Cloneable和Serializable接口?
1.设计原则与职责分离:
集合类的主要职责是管理和组织对象的存储结构,提供增删查改等操作。而Cloneable接口用于实现对象的复制(克隆),Serializable接口用于实现对象的序列化(将对象状态转换为字节流以便持久化或网络传输)。这两种功能并非集合类的核心职责,而是与具体存储的数据对象及其使用场景有关。
实现这些接口意味着对所有使用该集合类的实例强制引入克隆或序列化行为,这可能并不适用于所有情况。例如,某些集合可能包含不应该被克隆或不需要序列化的对象,或者集合的使用者可能有自定义的克隆和序列化逻辑。因此,将克隆和序列化的责任交给集合的具体实现类或用户代码更符合面向对象设计原则中的“单一职责”原则。
2.灵活性与可扩展性:
不直接实现Cloneable和Serializable接口,使得集合类更具有灵活性和可扩展性。具体实现类(如ArrayList、LinkedList等)可以根据自身特点和需求选择是否实现这两个接口,或者提供更符合其数据结构特性的克隆和序列化方法。
对于用户而言,可以根据实际应用场景选择合适的集合实现,并自行决定是否需要克隆或序列化集合实例,以及如何进行克隆和序列化。例如,可以通过实现Serializable接口的集合实现类来创建可序列化的集合,或者使用Collections.unmodifiableList(List<T> list)等方法创建不可变集合,这些集合往往不需要实现Cloneable接口。
3.深拷贝与浅拷贝的复杂性:
Cloneable接口默认实现的是浅拷贝,即只复制对象本身,而不复制其包含的引用对象。对于集合类而言,如果直接实现Cloneable接口,可能导致集合中的元素只是被浅拷贝,这在包含复杂对象结构的集合中可能导致意外的结果。因此,如果需要深拷贝(复制元素及其引用的对象),应该由集合的使用者或具体实现类根据实际数据类型来定制深拷贝逻辑。
4.元素的克隆与序列化能力:
集合类能否成功克隆或序列化不仅取决于集合本身的实现,还依赖于其包含的元素是否也支持相应的操作。如果集合类直接实现了Cloneable和Serializable接口,但其中某个元素不支持克隆或序列化,那么克隆或序列化操作可能会失败。将克隆和序列化的责任交给元素本身,可以确保只有支持相应操作的元素才能被正确处理。
4.简述Hashmap和Hashtable的不同 ?
1:HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
2:HashMap允许键和值是null,而Hashtable不允许键或者值是null。
3:Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
4:HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
5:一般认为Hashtable是一个遗留的类。
5.Java如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。
有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。"
6.Java集合类框架有哪些最佳实践?
1.根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用 Array 而不是ArrayList。
2.有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算 hash 值或者是扩容。3.为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。
4.使用 JDK 提供的不变类(immutable class)作为 Map 的键可以避免为我们自己的类实现 hashCode()和 equals()方法。
5.编程的时候接口优于实现。
6.底层的集合实际上是空的情况下,返回长度是 0 的集合或者是数组,不要返回null。
7.HashSet 和 TreeSet 的区别
1.内部实现:
HashSet 使用哈希表实现,通过散列函数将元素存储在数组中,这样可以快速地查找元素。
TreeSet 使用红黑树实现,保证元素有序存储,并且支持高效的插入、删除和查找操作。2.元素顺序:
HashSet 不保证元素的顺序,元素存储位置取决于哈希值。
TreeSet 会根据元素的自然排序或者自定义排序规则来维护元素的顺序。
3.插入和查询性能:HashSet 的插入和查询操作都是 O(1) 的平均时间复杂度,最坏情况下可能是 O(n)。
TreeSet 的插入、删除和查询操作都是 O(log n) 的时间复杂度,因为它需要维护元素的顺序。
4.排序功能:HashSet 没有排序功能,只能保证元素不重复。
LinkedHashSet本身也不支持直接排序操作,但可以通过转换为List来实现排序。
TreeSet 可以按照自然排序或者自定义排序规则对元素进行排序,并支持一些与排序相关的方法。
需要注意的是,在使用 TreeSet 时,存储的元素必须实现 Comparable 接口或者通过构造函数传入一个 Comparator 对象,以便确定元素的排序方式。
8.Enumeration接口和Iterator接口的区别?
Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。
Enumeration 只有2个函数接口。 通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
Iterator 只有3个函数接口。 Iterator除了能读取集合的数据之外,也能数据进行删除操作。Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。
而Iterator 是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
9.Collection 和 Collections的区别
Collection是集合类的上级接口,继承与他的接口主要有Set 和List.
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
10. 简述 WeakHashMap 的工作原理 ?
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。这个“弱键”的原理大致上就是,通过WeakReference和ReferenceQueue实现的。
WeakHashMap特点是当除了自身有对Key的引用外,如果此Key没有其他引用,那么此Map会自动丢弃该值。如清单1所示代码声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入 A、B 两个对象,当HashMap删除 A,并且 A、B 都指向Null时,WeakHashMap中的 A 将自动被回收掉。出现这个状况的原因是,对于 A 对象而言,当HashMap删除并且将 A 指向Null后,除了WeakHashMap中还保存 A 外已经没有指向 A 的指针了,所以WeakHashMap会自动舍弃掉 a,而对于 B 对象虽然指向了null,但HashMap中还有指向 B 的指针,所以WeakHashMap将会保留 B 对象。