1 栈和队列
堆栈是一种先进后出(FILO:first in last out)或后进先出(LIFI:last in first out)的结构。
队列是一种(但并非一定)先进先出(FIFO)的结构。
1.1 Stack类
java.util.Stack是Vector集合的子类。所以Stack是一个List集合类。
1.1.1 Stack类继承树

1.1.2 Stack类的新增方法
比Vector多了几个方法
- (1)peek():查看栈顶元素,不弹出。最后添加的元素位于栈顶
- (2)pop():弹出栈。返回栈顶的元素,并从集合中将该元素删除
- (3)push():压入栈 即添加到链表的头(栈顶)
@Test
public void test3() {
Stack<Integer> list = new Stack<>();
list.push(1); // 入栈
list.push(2); // 入栈
list.push(3); // 入栈
System.out.println(list); // [1, 2, 3]
/*System.out.println(list.pop()); // 结果:3。弹出栈,返回最后添加的元素。并从集合中删除该元素
System.out.println(list.pop()); // 结果:2。弹出栈,返回最后添加的元素。并从集合中删除该元素
System.out.println(list.pop()); // 结果:1。弹出栈,返回最后添加的元素。并从集合中删除该元素
System.out.println(list.pop());//java.util.NoSuchElementException。该集合中的元素已经全部弹出栈了*/
System.out.println(list.peek()); // 结果3.返回最后添加的元素,但不会删除
System.out.println(list.peek()); // 结果3.返回最后添加的元素,但不会删除
System.out.println(list.peek()); // 结果3.返回最后添加的元素,但不会删除
}
}
1.2 Queue(队列)和Deque(双端队列)接口
队列的特点是先进先出。

Queue除了基本的 Collection操作外,队列(Queue)还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)。Queue 实现通常不允许插入 元素,尽管某些实现(如 )并不禁止插入 。即使在允许 null 的实现中,也不应该将 插入到 中,因为 也用作 方法的一个特殊返回值,表明队列不包含元素。
| 抛出异常 | 返回特殊值 | |
|---|---|---|
| 插入 | add(e) | offer(e) |
| 移除 | remove() | poll() |
| 检查 | element() | peek() |
Queue接口方法测试:
public class Demo1 {
@Test
public void test01(){
// add()方法测试
Queue<Object> aa = new LinkedList<>();
System.out.println("aa.add(3) = " + aa.add(3)); // 添加元素到队列中,添加成功返回true
System.out.println("aa.add(2) = " + aa.add(2)); // 添加元素到队列中,添加成功返回true
System.out.println("aa.add(1) = " + aa.add(1)); // 添加元素到队列中,添加成功返回true
System.out.println("aa = " + aa); // aa = [3, 2, 1]
// offer()方法测试
Queue<Object> aa1 = new LinkedList<>();
System.out.println("aa1.offer(3) = " + aa1.offer(3)); // 添加元素到队列中,添加成功返回true
System.out.println("aa1.offer(2) = " + aa1.offer(2)); // 添加元素到队列中,添加成功返回true
System.out.println("aa1.offer(1) = " + aa1.offer(1)); // 添加元素到队列中,添加成功返回true
System.out.println("aa1 = " + aa); // aa1 = [3, 2, 1]
/*
// poll()方法测试
System.out.println("aa.poll() = " + aa.poll()); // 结果:3.返回最先添加的元素,并从队列中删除该元素
System.out.println("aa.poll() = " + aa.poll()); // 结果:2.返回最先添加的元素,并从队列中删除该元素
System.out.println("aa.poll() = " + aa.poll()); // 结果:1.返回最先添加的元素,并从队列中删除该元素
System.out.println("aa.poll() = " + aa.poll()); // 结果:null.当队列中已经没有元素时,返回null
*/
/*
// remove()方法测试
System.out.println("aa.remove() = " + aa.remove()); // 结果:3.返回最先添加的元素,并从队列中删除该元素
System.out.println("aa.remove() = " + aa.remove()); // 结果:2.返回最先添加的元素,并从队列中删除该元素
System.out.println("aa.remove() = " + aa.remove()); // 结果:1.返回最先添加的元素,并从队列中删除该元素
System.out.println("aa.remove() = " + aa.remove()); // 结果:抛出NoSuchElementException异常.当队列中没有元素时,抛出异常
*/
//element方法测试
System.out.println("aa.element() = " + aa.element()); // 结果3:返回最先添加的元素。但不删除
System.out.println("aa.element() = " + aa.element()); // 结果3:返回最先添加的元素。但不删除
// peek()方法测试
System.out.println("aa.peek() = " + aa.peek()); // 结果3:返回最先添加的元素。但不删除
System.out.println("aa.peek() = " + aa.peek()); // 结果3:返回最先添加的元素。但不删除
System.out.println("aa = " + aa);
}
}
Deque,名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
| 第一个元素(头部) | 最后一个元素(尾部) | |||
|---|---|---|---|---|
| 抛出异常 | 特殊值 | 抛出异常 | 特殊值 | |
| 插入 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
| 移除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
| 检查 | getFirst() | peekFirst() | getLast() | peekLast() |
此接口扩展了 Queue接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:
Queue 方法 |
等效 Deque 方法 |
|---|---|
| add(e) | addLast(e) |
| offer(e) | offerLast(e) |
| remove() | removeFirst() |
| poll() | pollFirst() |
| element() | getFirst() |
| peek() | peekFirst() |
双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:
| 堆栈方法 | 等效 Deque 方法 |
|---|---|
| push(e) | addFirst(e) |
| pop() | removeFirst() |
| peek() | peekFirst() |
结论:Deque接口的实现类既可以用作FILO堆栈使用,又可以用作FIFO队列使用。
Deque接口的实现类有ArrayDeque和LinkedList,它们一个底层是使用数组实现,一个使用双向链表实现。
2 哈希表
HashMap和Hashtable都是哈希表。
2.1 hashCode值
hash算法是一种可以从任何数据中提取出其“指纹”的数据摘要算法,它将任意大小的数据映射到一个固定大小的序列上,这个序列被称为hash code、数据摘要或者指纹。比较出名的hash算法有MD5、SHA。hash是具有唯一性且不可逆的,唯一性是指相同的“对象”产生的hash code永远是一样的。

2.2 哈希表的详解
- HashMap和Hashtable是散列表,其中维护了一个长度为2的幂次方的Entry类型的数组table,数组的每一个元素被称为一个桶(bucket),你添加的映射关系(key,value)最终都被封装为一个Map.Entry类型的对象,放到了某个table[index]桶中。使用数组的目的是查询和添加的效率高,可以根据索引直接定位到某个table[index]。
- hash表 = 顺序表+链表
- 顺序表:每储存一个数据都有开辟一块空间,会造成内存的浪费
- 链表:链表是不限长度的,每增加元素直接在后面追加即可。但是查询速度慢
- 顺序表+链表:可以综合两者的优势
- hash表如何进行数据储存的:
1. 计算一个对象的hash码
2. 对hash码进行散列处理(防止两个对象的hash码一样)
3. (采用处理后的hash码) & (顺序表长度-1) // 转化为二进制,都是1才为1。其结果一定是`0-(顺序表长度-1)`。因为顺序表的长度是2的n次方
4. 第三步的结果就是该hash码在hash表中的位置
0.HashMap底层储存原理
JKD1.7中

注意:并不是直接使用key的hashCode值来确认元素中顺序表的位置,而是经过了一系列运算。目的是让元素能均匀的分布在顺序表各位置,防止个别链表过长,影响查找效率。
JDK1.8
- JDK1.7是将{key,value}封装为Entry对象,而JDK1.8是将{key,value}封装为Node对象或者TreeNode对象。
- JKD1.7的hash表是采用:顺序表+链表,而JDK1.8是采用顺序表+链表+红黑树
- JDK1.7添加数据时,是添加在链表的前面。JDK1.8是添加在链表的末尾
- JDK1.8中当链表长度超过某一阈值,且顺序表数组长度也超过某一阈值时,会将Node对象转变为TreeNode对象。来提高查询效率

1、数组元素类型:Map.Entry
JDK1.7:
映射关系被封装为HashMap.Entry类型,而这个类型实现了Map.Entry接口。
观察HashMap.Entry类型是个结点类型,即table[index]下的映射关系可能串起来一个链表。因此我们把table[index]称为“桶bucket"。
public class HashMap<K,V>{
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
//...省略
}
//...
}

JDK1.8:
映射关系被封装为HashMap.Node类型或HashMap.TreeNode类型,它俩都直接或间接的实现了Map.Entry接口。
存储到table数组的可能是Node结点对象,也可能是TreeNode结点对象,它们也是Map.Entry接口的实现类。即table[index]下的映射关系可能串起来一个链表或一棵红黑树(自平衡的二叉树)。
public class HashMap<K,V>{
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//...省略
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;//是红结点还是黑结点
//...省略
}
//....
}
public class LinkedHashMap<K,V>{
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
//...
}

2、数组的长度始终是2的n次幂
table数组的默认初始化长度:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
如果你手动指定的table长度不是2的n次幂,会通过如下方法给你纠正为2的n次幂
JDK1.7:
Java HashMap与队列详解:数据存储与冲突解决

本文详细介绍了Java中HashMap和队列(Stack与Queue)的工作原理。HashMap通过数组+链表/红黑树结构存储键值对,利用哈希算法确保高效查找。队列遵循先进先出(FIFO)原则,Stack是其变体,后进先出。文章深入解析了HashMap的扩容机制、冲突解决策略以及JDK1.7与1.8的区别,同时讨论了队列接口Queue和Deque的使用及其在不同场景下的应用。
最低0.47元/天 解锁文章
548

被折叠的 条评论
为什么被折叠?



