大家好,又见面了,我是你们的朋友全栈君。
? 为何 Collection 不从 Cloneable 和 Serializable 接口继承?
Collection 接口指定一组对象,对象即为它的元素。
当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制,特定的实现应该决定它是否可以被克隆和序列化。
为何 Map 接口不继承 Collection 接口?
尽管 Map 接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map。因此,Map 继承 Collection 毫无意义,反之亦然。
如果 Map 继承 Collection 接口,那么元素去哪儿?Map 包含 key-value 对,它提供抽取 key 或 value 列表集合( Collection )的方法,但是它不适合“一组对象”规范。
? 为何 Map 接口不继承 Collection 接口?
尽管 Map 接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map。因此,Map 继承 Collection 毫无意义,反之亦然。
如果 Map 继承 Collection 接口,那么元素去哪儿?Map 包含 key-value 对,它提供抽取 key 或 value 列表集合( Collection )的方法,但是它不适合“一组对象”规范。
? Collection 和 Collections 的区别?
? 集合框架里实现的通用算法有哪些?
Java 集合框架提供常用的算法实现,比如排序和搜索。
Collections类包含这些方法实现。大部分算法是操作 List 的,但一部分对所有类型的集合都是可用的。部分算法有排序、搜索、混编、最大最小值。
? 集合框架底层数据结构总结
1)List
2)Map
3)Set
Iterator 接口,提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的 #remove(Object Obj)
方法删除,可以通过迭代器的 #remove()
方法删除。
? Iterator 和 ListIterator 的区别是什么?
? 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
差别在于 ConcurrentModification 异常:
java.util
包下的都是快速失败。java.util.concurrent
包下的全是安全失败的。? 如何删除 List 中的某个元素?
有两种方式,分别如下:
? Enumeration 和 Iterator 接口有什么不同?
对于很多胖友,可能并未使用过 Enumeration 类,所以可以看看 《Java Enumeration 接口》 文章。
? 为何 Iterator 接口没有具体的实现?
Iterator 接口,定义了遍历集合的方法,但它的实现则是集合实现类的责任。每个能够返回用于遍历的 Iterator 的集合类都有它自己的 Iterator 实现内部类。
这就允许集合类去选择迭代器是 fail-fast 还是 fail-safe 的。比如,ArrayList 迭代器是 fail-fast 的,而 CopyOnWriteArrayList 迭代器是 fail-safe 的。
java.lang
包下,用于当前对象和其它对象的比较,所以它有一个 #compareTo(Object obj)
方法用来排序,该方法只有一个参数。java.util
包下,用于传入的两个对象的比较,所以它有一个 #compare(Object obj1, Object obj2)
方法用来排序,该方法有两个参数。详细的,可以看看 《Java 自定义比较器》 文章,重点是如何自己实现 Comparable 和 Comparator 的方法。
? compareTo 方法的返回值表示的意思?
? 如何对 Object 的 List 排序?
Object[]
数组进行排序时,我们可以用 Arrays#sort(...)
方法。List<Object>
数组进行排序时,我们可以用 Collections#sort(...)
方法。null
,这样可以防止底层集合是空的。List,Set 都是继承自 Collection 接口。
注意:元素虽然无放入顺序,但是元素在 Set 中的位置是有该元素的 hashcode 决定的,其位置其实是固定的。 另外 List 支持
for
循环,也就是通过下标来遍历,也可以用迭代器,但是 Set 只能用迭代,因为他无序,无法用下标来取得想要的值。
Set 和 List 对比:
尽管 ArrayList 明显是更好的选择,但也有些时候 Array 比较好用,比如下面的三种情况。
[][]
比 List 会方便。? ArrayList
? LinkedList
? 适用场景分析:
? ArrayList 是如何扩容的?
直接看 《ArrayList 动态扩容详解》 文章,很详细。主要结论如下:
重点是 1.5 倍扩容,这是和 HashMap 2 倍扩容不同的地方。
ArrayList 集合加入 1 万条数据,应该怎么提高效率?
ArrayList 的默认初始容量为 10 ,要插入大量数据的时候需要不断扩容,而扩容是非常影响性能的。因此,现在明确了 10 万条数据了,我们可以直接在初始化的时候就设置 ArrayList 的容量!
这样就可以提高效率了~
ArrayList 和 Vector 都是用数组实现的,主要有这么三个区别:
synchronized
进行修饰,这样就导致了 Vector 在效率上无法与 ArrayList 相比。
Vector 是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。
适用场景分析:
Hashtable 是在 Java 1.0 的时候创建的,而集合的统一规范命名是在后来的 Java2.0 开始约定的,而当时其他一部分集合类的发布构成了新的集合框架。
old * 2 + 1
,HashMap 默认大小是 16 ,扩容每次为 2 的指数大小。一般现在不建议用 HashTable 。主要原因是两点:
? Hashtable 的 #size() 方法中明明只有一条语句 “return count;” ,为什么还要做同步?
同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程 A 在执行 Hashtable 的 put 方法添加数据,线程 B 则可以正常调用 #size()
方法读取 Hashtable 中当前元素的个数,那读取到的值可能不是最新的,可能线程 A 添加了完了数据,但是没有对 count++
,线程 B 就已经读取 count
了,那么对于线程 B 来说读取到的 count
一定是不准确的。
而给 #size() 方法加了同步之后,意味着线程 B 调用 #size() 方法只有在线程 A 调用 put 方法完毕之后才可以调用,这样就保证了线程安全性。
他们的共同点都是 hash 算法实现的唯一性,他们都不能持有基本类型,只能持有对象。
为了更好的性能,Netty 自己实现了 key 为基本类型的 HashMap ,例如 IntObjectHashMap 。
O(1)
。O(logn)
。? 如何决定选用 HashMap 还是 TreeMap?
基于你的 collection 的大小,也许向 HashMap 中添加元素会更快,再将 HashMap 换为 TreeMap 进行有序 key 的遍历。
ConcurrentHashMap 是线程安全的 HashMap 的实现。主要区别如下:
null
,但是 ConCurrentHashMap 都不允许。
栈和队列两者都被用来预存储数据。
java.util.Queue
是一个接口,它的实现类在Java并发包中。队列允许先进先出(FIFO)检索元素,但并非总是这样。Deque 接口允许从两端检索元素。我们知道在 Java 中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap 也是如此。实际上 HashMap 是一个**“链表散列”**。
HashMap 是基于 hashing 的原理。
#put(key, value)
方法来存储对象到 HashMap 中,使用 get(key)
方法从 HashMap 中获取对象。#put(key, value)
方法传递键和值时,我们先对键调用 #hashCode()
方法,返回的 hashCode 用于找到 bucket 位置来储存 Entry 对象。? 当两个对象的 hashCode 相同会发生什么?
因为 hashcode 相同,所以它们的 bucket 位置相同,“碰撞”会发生。
因为 HashMap 使用链表存储对象,这个 Entry(包含有键值对的 Map.Entry 对象)会存储在链表中。
? hashCode 和 equals 方法有何重要性?
HashMap 使用 key 对象的 #hashCode()
和 #equals(Object obj)
方法去决定 key-value 对的索引。当我们试着从 HashMap 中获取值的时候,这些方法也会被用到。
#hashCode()
和 #equals(Object obj)
输出,HashMap 将会认为它们是相同的,然后覆盖它们,而非把它们存储到不同的地方。同样的,所有不允许存储重复数据的集合类都使用 #hashCode()
和 #equals(Object obj)
去查找重复,所以正确实现它们非常重要。#hashCode()
和 #equals(Object obj)
方法的实现,应该遵循以下规则:
o1.equals(o2)
,那么 o1.hashCode() == o2.hashCode()
总是为 true
的。o1.hashCode() == o2.hashCode()
,并不意味 o1.equals(o2)
会为 true
。? HashMap 默认容量是多少?
默认容量都是 16 ,负载因子是 0.75 。就是当 HashMap 填充了 75% 的 busket 是就会扩容,最小的可能性是(16 * 0.75 = 12
),一般为原内存的 2 倍。
? 有哪些顺序的 HashMap 实现类?
? 我们能否使用任何类作为 Map 的 key?
我们可以使用任何类作为 Map 的 key ,然而在使用它们之前,需要考虑以下几点:
? HashMap 的长度为什么是 2 的幂次方?
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。
这个算法应该如何设计呢?我们首先可能会想到采用 %
取余的操作来实现。但是,重点来了:
%
)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&
)操作(也就是说 hash % length == hash & (length - 1)
的前提是 length 是 2 的 n 次方;)。&
,相对于 %
能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。
HashSet 是构建在 HashMap 之上的 Set hashing 实现类。让我们直接撸下源码,代码如下:
// HashSet.java
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
? HashSet 如何检查重复?
艿艿:正如我们上面看到 HashSet 的实现原理,我们自然可以推导出,HashMap 也是如何检查重复滴。
如下摘取自 《Head First Java》 第二版:
当你把对象加入 HashSet 时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较。
【详细可以查看java基础系列】
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/126374.html原文链接:https://javaforall.cn