一、随堂复习
1. 数据结构
-
数据结构的研究对象:
-
① 数据间的逻辑关系(集合关系、一对一、一对多、多对多)
-
② 数据的存储结构(或物理结构)
-
角度一:顺序结构、链式结构、索引结构、哈希结构
-
角度二:线性表(一维数组、链表、栈、队列)、树(二叉树、B+树)、图(多对多)、哈希表(HashMap、HashSet)
-
-
③ 相关运算
-
-
树(了解)
-
相关数据结构的核心Node的设计(单向链表、双向链表、二叉树、栈、队列)(理解)
4. 常见存储结构之:链表
链表中的基本单位是:节点(Node)
4.1 单向链表
class Node{
Object data;
Node next;
public Node(Object data){
this.data = data;
}
}
创建对象:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
node1.next = node2;
4.2 双向链表
class Node{
Node prev;
Object data;
Node next;
public Node(Object data){
this.data = data;
}
public Node(Node prev,Object data,Node next){
this.prev = prev;
this.data = data;
this.next = next;
}
}
创建对象:
Node node1 = new Node(null,"AA",null);
Node node2 = new Node(node1,"BB",null);
Node node3 = new Node(node2,"CC",null);
node1.next = node2;
node2.next = node3;
5. 常见存储结构之:二叉树
class TreeNode{
TreeNode left;
Object data;
TreeNode right;
public TreeNode(Object data){
this.data = data;
}
public TreeNode(TreeNode left,Object data,TreeNode right){
this.left = left;
this.data = data;
this.right = right;
}
}
创建对象:
TreeNode node1 = new TreeNode(null,"AA",null);
TreeNode leftNode = new TreeNode(null,"BB",null);
TreeNode rightNode = new TreeNode(null,"CC",null);
node1.left = leftNode;
node1.right = rightNode;
或
class TreeNode{
TreeNode parent;
TreeNode left;
Object data;
TreeNode right;
public TreeNode(Object data){
this.data = data;
}
public TreeNode(TreeNode left,Object data,TreeNode right){
this.left = left;
this.data = data;
this.right = right;
}
public TreeNode(TreeNode parent,TreeNode left,Object data,TreeNode right){
this.parent = parent;
this.left = left;
this.data = data;
this.right = right;
}
}
创建对象:
TreeNode node1 = new TreeNode(null,null,"AA",null);
TreeNode leftNode = new TreeNode(node1,null,"BB",null);
TreeNode rightNode = new TreeNode(node1,null,"CC",null);
node1.left = leftNode;
node1.right = rightNode;
6. 常见存储结构之:栈(stack、先进后出、first in last out、FILO、LIFO)
> 属于抽象数据类型(ADT)
> 可以使用数组或链表来构建
//数组实现栈
class Stack{
Object[] values;
int size;//记录存储的元素的个数
public Stack(int length){
values = new Object[length];
}
//入栈
public void push(Object ele){
if(size >= values.length){
throw new RuntimeException("栈空间已满,入栈失败");
}
values[size] = ele;
size++;
}
//出栈
public Object pop(){
if(size <= 0){
throw new RuntimeException("栈空间已空,出栈失败");
}
Object obj = values[size - 1];
values[size - 1] = null;
size--;
return obj;
}
}
7. 常见存储结构之:队列
> 属于抽象数据类型(ADT)
> 可以使用数组或链表来构建
//数组实现队列
class Queue{
Object[] values;
int size;//记录存储的元素的个数
public Queue(int length){
values = new Object[length];
}
public void add(Object ele){ //添加
if(size >= values.length){
throw new RuntimeException("队列已满,添加失败");
}
values[size] = ele;
size++;
}
public Object get(){ //获取
if(size <= 0){
throw new RuntimeException("队列已空,获取失败");
}
Object obj = values[0];
//数据前移
for(int i = 0;i < size - 1;i++){
values[i] = values[i + 1];
}
//最后一个元素置空
vlaues[size - 1] = null;
size--;
return obj;
}
}
2. List接口下的实现类的源码剖析
【面试题】ArrayList、Vector、LinkedList的三者的对比?
-
层次1:
|-----子接口:List:存储有序的、可重复的数据 ("动态"数组) |---- ArrayList:List的主要实现类;线程不安全的、效率高;底层使用Object[]数组存储 在添加数据、查找数据时,效率较高;在插入、删除数据时,效率较低 |---- LinkedList:底层使用双向链表的方式进行存储;在对集合中的数据进行频繁的删除、插入操作时,建议使用此 类在插入、删除数据时,效率较高;在添加数据、查找数据时,效率较低; |---- Vector:List的古老实现类;线程安全的、效率低;底层使用Object[]数组存储
-
层次2:查看相关api的源码
一、ArrayList
1. ArrayList的特点:
> 实现了List接口,存储有序的、可以重复的数据
> 底层使用Object[]数组存储
> 线程不安全的
2. ArrayList源码解析:
2.1 jdk7版本:(以jdk1.7.0_07为例)
//如下代码的执行:底层会初始化数组,数组的长度为10。Object[] elementData = new Object[10];
ArrayList<String> list = new ArrayList<>();
list.add("AA"); //elementData[0] = "AA";
list.add("BB");//elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementData数组已满,则需要扩容。默认扩容为原来长度的1.5倍。并将原有数组
中的元素复制到新的数组中。
2.2 jdk8版本:(以jdk1.8.0_271为例)
//如下代码的执行:底层会初始化数组,即:Object[] elementData = new Object[]{};
ArrayList<String> list = new ArrayList<>();
list.add("AA"); //首次添加元素时,会初始化数组elementData = new Object[10];elementData[0] = "AA";
list.add("BB");//elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementData数组已满,则需要扩容。默认扩容为原来长度的1.5倍。并将原有数组
中的元素复制到新的数组中。
小结:
jdk1.7.0_07版本中:ArrayList类似于饿汉式
jdk1.8.0_271版本中:ArrayList类似于懒汉式
二、Vector
1. Vector的特点:
> 实现了List接口,存储有序的、可以重复的数据
> 底层使用Object[]数组存储
> 线程安全的
2. Vector源码解析:(以jdk1.8.0_271为例)
Vector v = new Vector(); //底层初始化数组,长度为10.Object[] elementData = new Object[10];
v.add("AA"); //elementData[0] = "AA";
v.add("BB");//elementData[1] = "BB";
...
当添加第11个元素时,需要扩容。默认扩容为原来的2倍。
三、LinkedList
1. LinkedList的特点:
> 实现了List接口,存储有序的、可以重复的数据
> 底层使用双向链表存储
> 线程不安全的
2. LinkedList在jdk8中的源码解析:
LinkedList<String> list = new LinkedList<>(); //底层也没做啥
list.add("AA"); //将"AA"封装到一个Node对象1中,list对象的属性first、last都指向此Node对象1。
list.add("BB"); //将"BB"封装到一个Node对象2中,对象1和对象2构成一个双向链表,同时last指向此Node对象2
...
因为LinkedList使用的是双向链表,不需要考虑扩容问题。
LinkedList内部声明:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
3. LinkedList是否存在扩容问题?No!
四、启示与开发建议
1. Vector基本不使用了。
2. ArrayList底层使用数组结构,查找和添加(尾部添加)操作效率高,时间复杂度为O(1)
删除和插入操作效率低,时间复杂度为O(n)
LinkedList底层使用双向链表结构,删除和插入操作效率高,时间复杂度为O(1)
查找和添加(尾部添加)操作效率高,时间复杂度为O(n) (有可能添加操作是O(1))
3. 在选择了ArrayList的前提下,new ArrayList() : 底层创建长度为10的数组。
new ArrayList(int capacity):底层创建指定capacity长度的数组。
如果开发中,大体确认数组的长度,则推荐使用ArrayList(int capacity)这个构造器,避免了底层的扩容、复制数组的操作。
3. Map接口下的实现类的源码剖析
-
(掌握)HashMap的底层源码的剖析
-
(熟悉)LinkedHashMap的底层源码的剖析
-
(了解)HashSet、LinkedHashSet的底层源码的剖析
二、企业真题
2.1 数据结构相关
1. 链表和数组有什么区别?(腾*)
第14章课件里:《【拓展】尚硅谷_宋红康_数据结构概述-Java版.xmind
》
2. 栈是如何运行的?(西*信息技术)
先进后出。属于ADT(abstract data type),可以使用数组、链表实现栈结构
2.2 List集合源码相关
1. ArrayList的默认大小是多少,以及扩容机制(顺*、凡*科技)
类似问题: > 说说ArrayList的扩容机制吧(国*电网) > 讲一下ArrayList的扩容机制(*实在) > ArrayList的扩容机制,为什么是10,为什么是1.5倍(*软国际)
底层会初始化数组,数组的长度为10。当要添加第11个元素的时候,底层的elementData数组已满,则需要扩容。默认扩容为原来长度的1.5倍。并将原有数组中的元素复制到新的数组中。
2. ArrayList的底层是怎么实现的?(腾*)
类似问题: 集合类的ArrayList底层(安全不安全,扩容,初始大小,添加删除查询是怎么操作的,底层是什么组成的) (湖**利软件、汇*云通、猎*、苏州***动、上海*进天下、北京博*软件、*科软、大连*点科技、中*亿达、德*物流、天*伟业、猫*娱乐)
2.1 jdk7版本:(以jdk1.7.0_07为例)
//如下代码的执行:底层会初始化数组,数组的长度为10。Object[] elementData = new Object[10];
ArrayList<String> list = new ArrayList<>();
list.add("AA"); //elementData[0] = "AA";
list.add("BB");//elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementData数组已满,则需要扩容。默认扩容为原来长度的1.5倍。并将原有数组
中的元素复制到新的数组中。
2.2 jdk8版本:(以jdk1.8.0_271为例)
//如下代码的执行:底层会初始化数组,即:Object[] elementData = new Object[]{};
ArrayList<String> list = new ArrayList<>();
list.add("AA"); //首次添加元素时,会初始化数组elementData = new Object[10];elementData[0] = "AA";
list.add("BB");//elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementData数组已满,则需要扩容。默认扩容为原来长度的1.5倍。并将原有数组
中的元素复制到新的数组中。
小结:
jdk1.7.0_07版本中:ArrayList类似于饿汉式
jdk1.8.0_271版本中:ArrayList类似于懒汉式
建议:ArrayList(int capacity){}
3. 在ArrayList中remove后面几个元素该怎么做?(惠*、中*亿达)
前移。
4. ArrayList1.7和1.8的区别(拓*思)
类似于饿汉式、懒汉式
5. 数组和 ArrayList 的区别(阿*、*科软)
ArrayList看做是对数组的常见操作的封装。
6. 什么是线程安全的List?(平*金服)
Vector:线程安全的。
ArrayList:线程不安全。----> 使用同步机制处理。
HashMap:线程不安全。 ----> 使用同步机制处理。 -----> JUC:ConcurrentHashMap
2.3 HashMap集合源码相关
1. 说说HahMap底层实现(新*股份、顺*、猫*娱乐)
类似问题: > HashMap的实现讲一下?(腾*,上海**网络) > 说说HashMap的底层执行原理?(滴*,纬*软件,上海*想,*昂,*蝶**云,宇*科技,*东数科,猎*网) > 详细说一下 HashMap 的 put 过程(*度) > Java中的HashMap的工作原理是什么?(北京中**译咨询) > 集合类的HashMap底层(安全不安全,扩容,初始大小,添加删除查询是怎么操作的,底层是什么组成的)(湖**利软件) > HashMap 的存储过程(爱*信、杭州*智) > Hashmap底层实现及构造(汇**通、猎*、苏州博*讯动、上海*进天下、北京博*软件、*科软、大连*点科技、中*亿达、德*物流、天*伟业、猫*娱乐) > HashMap的实现原理(腾*、阿*) > HaspMap底层讲一讲(*米) > 说一下HashMap的实现,扩容机制?(*节) > 讲一下 HashMap 中 put 方法过程?(阿*)
JDK7中的HashMap的源码
1. 实例化过程
HashMap<String,Integer> map = new HashMap<>();
对应的源码:
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
//...略...
//通过此循环,得到capacity的最终值,此最终值决定了Entry数组的长度。此时的capacity一定是2的整数倍
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor; //确定了加载因子的值
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); //确定了临界值
table = new Entry[capacity]; //初始化数组,长度为capacity
//..略..
}
其中:
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor; //加载因子
int threshold;//临界值
transient Entry<K,V>[] table; //存储数组的数组
2. put(key,value)的过程
public V put(K key, V value) {
//HashMap允许添加key为null的值。将此(key,value)存放到table索引0的位置。
if (key == null)
return putForNullKey(value);
//将key传入hash(),内部使用了key的哈希值1,此方法执行结束后,返回哈希值2
int hash = hash(key);
//确定当前key,value在数组中的存放位置i
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; //如果put是修改操作,会返回原有旧的value值。
}
}
//.....
addEntry(hash, key, value, i); //将key,value封装为一个Entry对象,并将此对象保存在索引i位置。
return null; //如果put是添加操作,会返回null.
}
其中:
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//扩容的条件
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length); //默认扩容为原有容量的2倍
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
3. Entry的定义如下:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash; //使用key得到的哈希值2进行赋值。
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
2.1 jdk7中创建对象和添加数据过程(以JDK1.7.0_07为例说明):
//创建对象的过程中,底层会初始化数组Entry[] table = new Entry[16];
HashMap<String,Integer> map = new HashMap<>();
...
map.put("AA",78); //"AA"和78封装到一个Entry对象中,考虑将此对象添加到table数组中。
...
添加/修改的过程:
将(key1,value1)添加到当前的map中:
首先,需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2。
哈希值2再经过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的索引位置i。
1.1 如果此索引位置i的数组上没有元素,则(key1,value1)添加成功。 ---->情况1
1.2 如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2 --->哈希冲突
2.1 如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功。 ---->情况2
2.2 如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals()。要调用key1所在类的equals(),将key2作为参数传递进去。
3.1 调用equals(),返回false: 则(key1,value1)添加成功。 ---->情况3
3.2 调用equals(),返回true: 则认为key1和key2是相同的。默认情况下,value1替换原有的value2。
说明:情况1:将(key1,value1)存放到数组的索引i的位置
情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构,(key1,value1)指向(key2,value2)
随着不断的添加元素,在满足如下的条件的情况下,会考虑扩容:
(size >= threshold) && (null != table[i])
当元素的个数达到临界值(-> 数组的长度 * 加载因子)时,就考虑扩容。默认的临界值 = 16 * 0.75 --> 12.
默认扩容为原来的2倍。
2.2 jdk8与jdk7的不同之处(以jdk1.8.0_271为例):
① 在jdk8中,当我们创建了HashMap实例以后,底层并没有初始化table数组。当首次添加(key,value)时,进行判断,
如果发现table尚未初始化,则对数组进行初始化。
② 在jdk8中,HashMap底层定义了Node内部类,替换jdk7中的Entry内部类。意味着,我们创建的数组是Node[]
③ 在jdk8中,如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中。如果此时角标i位置上有
元素。在jdk7中是将新的(key,value)指向已有的旧的元素(头插法),而在jdk8中是旧的元素指向新的
(key,value)元素(尾插法)。 "七上八下"
④ jdk7:数组+单向链表
jk8:数组+单向链表 + 红黑树
什么时候会使用单向链表变为红黑树:如果数组索引i位置上的元素的个数达到8,并且数组的长度达到64时,我们就将此索引i位置上
的多个元素改为使用红黑树的结构进行存储。(为什么修改呢?红黑树进行put()/get()/remove()
操作的时间复杂度为O(logn),比单向链表的时间复杂度O(n)的好。性能更高。
什么时候会使用红黑树变为单向链表:当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树结构退化为单向链表。
略。建议以JDK8为主说明。
2. HashMap初始值16,临界值12是怎么算的(软**力)
16从底层源码的构造器中看到的。
12:threshold,使用数组的长度*加载因子(loadFactor)
3. HashMap长度为什么是2的幂次方(国*时代)
为了方便计算要添加的元素的底层的索引i。
4. HashMap怎么计算哈希值和索引?扩容机制?怎么解决hash冲突?(*软国际、中软*腾)
类似问题: > HashMap key的哈希冲突了怎么做(新*股份) > HashMap的默认大小是多少,以及扩容机制(顺*、凡*科技) > 讲一下HashMap的扩容机制?(好实*)
添加/修改的过程:
将(key1,value1)添加到当前的map中:
首先,需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2。
哈希值2再经过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的索引位置i。
1.1 如果此索引位置i的数组上没有元素,则(key1,value1)添加成功。 ---->情况1
1.2 如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2 --->哈希冲突
2.1 如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功。 ---->情况2
2.2 如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals()。要调用key1所在类的equals(),将key2作为参数传递进去。
3.1 调用equals(),返回false: 则(key1,value1)添加成功。 ---->情况3
3.2 调用equals(),返回true: 则认为key1和key2是相同的。默认情况下,value1替换原有的value2。
说明:情况1:将(key1,value1)存放到数组的索引i的位置
情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构,(key1,value1)指向(key2,value2)
随着不断的添加元素,在满足如下的条件的情况下,会考虑扩容:
(size >= threshold) && (null != table[i])
当元素的个数达到临界值(-> 数组的长度 * 加载因子)时,就考虑扩容。默认的临界值 = 16 * 0.75 --> 12.
默认扩容为原来的2倍。
5. HashMap底层是数组+链表,有数组很快了,为什么加链表?(润*软件)
因为产生了哈希冲突。解决方案,使用链表的方式。保证要添加的元素仍然在索引i的位置上。
6. HashMap为什么长度达到一定的长度要转化为红黑树(*度)
类似问题: > HashMap为什么用红黑树(*软国际)
红黑树的常用操作的时间复杂度O(logn),比单向链表的O(n)效率高。
7. HashMap什么时候扩充为红黑树,什么时候又返回到链表?(汉*)
类似问题: > HashMap什么时候转换为红黑树(杭州*智公司) > 当HashMap中相同hashcode值的数据超过多少时会转变成红黑树?(百*云创) > 什么时候是数据+链表,什么时候是红黑树(*软国际)
索引i的位置的链表长度超过8且数组长度达到64,需要索引i位置要变成红黑树。
当索引i的位置元素的个数低于6时,要红黑树结构转为单向链表。为什么?节省空间。
8. 在 JDK1.8中,HashMap的数据结构与1.7相比有什么变化,这些变化的好处在哪里?(海*科)
① 在jdk8中,当我们创建了HashMap实例以后,底层并没有初始化table数组。当首次添加(key,value)时,进行判断, 如果发现table尚未初始化,则对数组进行初始化。 ② 在jdk8中,HashMap底层定义了Node内部类,替换jdk7中的Entry内部类。意味着,我们创建的数组是Node[] ③ 在jdk8中,如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中。如果此时角标i位置上有 元素。在jdk7中是将新的(key,value)指向已有的旧的元素(头插法),而在jdk8中是旧的元素指向新的 (key,value)元素(尾插法)。 "七上八下" ④ jdk7:数组+单向链表 jk8:数组+单向链表 + 红黑树 什么时候会使用单向链表变为红黑树:如果数组索引i位置上的元素的个数达到8,并且数组的长度达到64时,我们就将此索引i位置上 的多个元素改为使用红黑树的结构进行存储。(为什么修改呢?红黑树进行put()/get()/remove() 操作的时间复杂度为O(logn),比单向链表的时间复杂度O(n)的好。性能更高。 什么时候会使用红黑树变为单向链表:当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树结构退化为单向链表。
9. HashMap的get()方法的原理?(顺*)
参考put()
2.4 hashCode和equals
1. hashcode和equals区别?(海*供应链管理)
hashcode根据当前对象的属性计算hash值,equals判断两个对象是否相等。
2. hashCode() 与 equals() 生成算法、方法怎么重写?(阿*校招)
进行equals()判断使用的属性,通常也都会参与到hashCode()的计算中。
尽量保证hashCode()的一致性。(使用IDEA自动生成,hashCode()自动使用相关的算法。
3. 说一下equals和==的区别,然后问equals相等hash值一定相等吗?hash值相等equals一定相等吗?(南*电网、上海*智网络)
equals相等hash值一定相等吗? 是
hash值相等equals一定相等吗?不一定
2.5 Set集合源码相关
1. HashSet存放数据的方式?(拓*软件)
底层使用HashMap。说一下HashMap
添加/修改的过程:
将(key1,value1)添加到当前的map中:
首先,需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2。
哈希值2再经过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的索引位置i。
1.1 如果此索引位置i的数组上没有元素,则(key1,value1)添加成功。 ---->情况1
1.2 如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2 --->哈希冲突
2.1 如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功。 ---->情况2
2.2 如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals()。要调用key1所在类的equals(),将key2作为参数传递进去。
3.1 调用equals(),返回false: 则(key1,value1)添加成功。 ---->情况3
3.2 调用equals(),返回true: 则认为key1和key2是相同的。默认情况下,value1替换原有的value2。
说明:情况1:将(key1,value1)存放到数组的索引i的位置
情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构,(key1,value1)指向(key2,value2)
2. Set是如何实现元素的唯一性?(湖**利软件)
不能重复,还是HashMap
3. 用哪两种方式来实现集合的排序(凡*科技)
类似问题: > 集合怎么排序?(北京中**信科技)
自然排序、定制排序。