HashMap
特点
Collection(存储单值)和Map(存储双值)是集合框架库的两个顶级接口。
Map<K,V>,以key–value键值对的形式存储数据。
key不重复,元素的存储位置由key决定,即可以通过key寻找键值对的位置,从而得到value的值。
继承的接口
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
- Map.Entry:存储key–value的具体数值。
- Cloneable:可以使用clone方法。
- Serializable:可以被序列化。
方法
public static void main(String[] args) {
HashMap<String,Integer> map = new HashMap<>();
//增
map.put("Alice",5);
map.put("Tom",6);
map.put("Tom",7);//key值一样时,会替换value值
//删
map.remove("Alice");
//改
map.replace("Tom",10);//修改该key值下的value
//查
System.out.println(map.get("Tom"));//获取该key值下的value
//遍历1
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
System.out.println("Name:"+next.getKey()+" Age:"+next.getValue());
}
//遍历2 同理可将keySet换为valueSet
Iterator<String> iterator1 = map.keySet().iterator();
while(iterator1.hasNext()){
String next = iterator1.next();
System.out.println("Name:"+next);
}
//遍历3 同理也可将entrySet换为keySet和valueSet
for(Map.Entry<String,Integer> entry:map.entrySet()){
System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
}
}
源码分析
构造器
//双参构造
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//初始容量最大不能超过2的30次方
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//显然加载因子不能为负数 || 判断是不是一个数字
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
//指定初始容量,加载因子为默认
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//无参构造,调用双参构造器
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
put方法
public V put(K key, V value) {
//第一次添加时,初始化数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);//threshold 阈值
}
if (key == null)
return putForNullKey(value);
int hash = hash(key); //扰动处理后的key的hashcode
int i = indexFor(hash, table.length);//值的位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//先判断哈希值,哈希值不一样key一定不相同
//如果引用地址一样,key一定是一样的
//最后比较key
//这样做提高了效率,前两步判断速度快,且可以筛选掉大部分比较
//直接对象比较的话速度较慢
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //当有重复的key插入的时候就会替换掉之前的
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
//如果重写equals方法,那么一定要重写hashcode方法:
//重写了equals后,可能是两个对象按照编写者自己的逻辑相等,
//如果这样直接会判断hashcode不相等,到不了equals比较
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
//key为null时的添加
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) { //本身已经存放一个key为null
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//本身没有存放一个key为null
modCount++;
addEntry(0, null, value, 0);
return null;
}
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++;
}
扩容方法
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];//32
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;//将新数组覆盖原数组
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//转移数据的方法
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {//遍历数组
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {//默认为false
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];//头插
newTable[i] = e;//将元素放入新数组中
e = next;
}
}
}
remove方法
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);//计算哈希值和下标
Entry<K,V> prev = table[i];//前驱
Entry<K,V> e = prev;//e为当前元素
//单链表的删除
while (e != null) {
Entry<K,V> next = e.next;
Object k;
//与添加方法中同理
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e) //删除的是头一个节点
table[i] = next;
else
prev.next = next;//链接
e.recordRemoval(this);
return e;
}
prev = e;//更新
e = next;
}
return e;
}
迭代器
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++;
}
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // 下一个结点
int expectedModCount; // 快速失败
int index; // 当前数组下标
Entry<K,V> current; // 当前节点
HashIterator() {
expectedModCount = modCount;
if (size > 0) {
Entry[] t = table;
//找到数组中第一个不为空的结点
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount) //快速失败
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) { //这一步相当于指针向后移动
Entry[] t = table;
//找到数组下一个不为空的结点
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
LinkedHashMap
特点和使用与HashMap基本相同。
在Entry中引入before和after属性,构成双向链表,维护插入顺序。
但如果要维护大小顺序,则引入TreeMap集合。
TreeMap
特点
存储key—value键值对的集合,根据key的大小来排序。底层数据结构为红黑树。key不能为空。
继承的接口
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
使用
常用方法
// 找到第一个比指定的key小的值
Map.Entry<K,V> lowerEntry(K key);
// 找到第一个比指定的key小的key
K lowerKey(K key);
// 找到第一个小于或等于指定key的值
Map.Entry<K,V> floorEntry(K key);
// 找到第一个小于或等于指定key的key
K floorKey(K key);
// 找到第一个大于或等于指定key的值
Map.Entry<K,V> ceilingEntry(K key);
//找到第一个大于或等于指定key的key
K ceilingKey(K key);
// 找到第一个大于指定key的值
Map.Entry<K,V> higherEntry(K key);
//找到第一个大于指定key的key
K higherKey(K key);
// 获取最小值
Map.Entry<K,V> firstEntry();
// 获取最大值
Map.Entry<K,V> lastEntry();
// 删除最小的元素
Map.Entry<K,V> pollFirstEntry();
// 删除最大的元素
Map.Entry<K,V> pollLastEntry();
//返回一个倒序的Map
NavigableMap<K,V> descendingMap();
// 返回一个Navigable的key的集合,NavigableSet和NavigableMap类似
NavigableSet<K> navigableKeySet();
// 对上述集合倒序
NavigableSet<K> descendingKeySet();
Demo
Student类和Score类
//省略构造方法、getter方法、setter方法和toString方法
public class Student{
private String name;
private int age;
private String sex;
}
public class Score {
private int chineseScore;
private int mathScore;
private int englishScore;
}
主函数
TreeMap<Student,Score> map = new TreeMap<>();
Student student1 = new Student("Tom",18,"male");
Score score1 = new Score(78,88,98);
Student student2 = new Student("Jack",18,"male");
Score score2 = new Score(77,87,97);
Student student3 = new Student("Alice",18,"female");
Score score3 = new Score(76,86,96);
map.put(student1,score1);
map.put(student2,score2);
map.put(student3,score3);
Iterator<Map.Entry<Student,Score>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Student,Score> next = iterator.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
}
结果
自定义类作为key时,无法自动比较,应提供比较方法。
内部比较器
Student类
@Override
public class Student implements Comparable<Student>{
...
...
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
结果
外部比较器
主函数
public static void main(String[] args) {
Student student1 = new Student("Tom",18,"male");
Score score1 = new Score(78,88,98);
Student student2 = new Student("Jack",18,"male");
Score score2 = new Score(77,87,97);
Student student3 = new Student("Alice",18,"female");
Score score3 = new Score(76,86,96);
Comparator<Student> comparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
};
TreeMap<Student,Score> map = new TreeMap<>(comparator);
map.put(student1,score1);
map.put(student2,score2);
map.put(student3,score3);
Iterator<Map.Entry<Student,Score>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Student,Score> next = iterator.next();
System.out.println("key:"+next.getKey()+" value:"+next.getValue());
}
}
结果
比较
外部比较器可以提供多个比较原则。
创建类时,确定排序规则时使用内比较器。
无法改变类时,使用外比较器。
总结
- 如果需要维护key–value结构的大小顺序可以选择TreeMap。
- TreeMap底层采用红黑树进行排序。
- 使用时一定要使用比较器。
- TreeMap和HashMap使用方法基本相同。
- put时key不能为空,提供了一系列和大小有关的方法。
- 查找时间复杂度:O(log2N)。