Map集合类型
- Map
特点:存储的是键值对映射关系,根据key可以找到value。 - HashMap
1.采用HashTable存储结构
2.添加快、删除快、查询快
3.缺点:key无序 - LinkedHashMap
1.采用HashTable存储结构,同时采用链表维护次序
2.key有序(添加顺序) - TreeMap
1.采用红黑树的存储结构
2.优点:key有序,查询速度比List快(按照内容查询)
3.缺点:查询速度没有HashSet快
使用Map存储国家简称-国家名称映射
package com.bjsxt.map;
import java.util.*;
/*
HashMap
key:唯一、无序 HashSet
value:不唯一、无序 Collection
*/
public class TestMap {
public static void main(String[] args) {
//创建一个Map集合对象
Map<String, String> map = new HashMap<String, String>();
Map<String, String> map1 = new LinkedHashMap<String,String>();
Map<String, String> map2 = new TreeMap<String, String>();
//向Map中添加元素
map.put("CN", "China");
map.put("jp", "Japan");
map.put("us", "the United States");
map.put("uk", "England");
map.put("en", "England");
//从Map中根据key获取value
System.out.println(map.size());
System.out.println(map);
System.out.println(map.containsKey("CN"));
System.out.println(map.isEmpty());
System.out.println(map.get("CN"));
System.out.println(map.get("if"));
System.out.println(map.keySet());//Set 得到所有的key
System.out.println(map.values());//Collection 得到所有的value
System.out.println("==============================================");
//Map的遍历
//思路一:先得到所有的key,再根据key找到value
Set<String> keySet=map.keySet();
for (String key:keySet
) {
System.out.println(key+"---->"+map.get(key));
}
System.out.println("==============================================");
//思路二:先得到所有的key-value组成的Set,然后输出每个key-value
/*
Set<Map.Entry<String, String>> entrySet = map.entrySet();
Iterator<Map.Entry<String, String>> it = entrySet.iterator();
while (it.hasNext()) {
//取出一个entry
Map.Entry<String, String> entry = it.next();
//输出一个entry
System.out.println(entry.getKey()+"------>"+entry.getValue());
}
*/
//推荐使用,和思路二一样
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String,String> entry:
entrySet) {
// System.out.println(entry);
System.out.println(entry.getKey()+"--->"+entry.getValue());
}
}
}
使用各种Map存储学号-学生映射
package com.bjsxt.map;
import com.bjsxt.set.Student;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
使用Map存储学号和学生的映射
*/
public class TestMap2 {
public static void main(String[] args) {
//创建一个Map对象存储key-value
Map<Integer, Student> map = new HashMap<Integer, Student>();
//向Map对象中添加多个键值对
Student s1 = new Student(1, "张永昌", 21, 380);
Student s2 = new Student(2, "zhang", 21, 370);
Student s3 = new Student(3, "yong", 21, 360);
Student s4 = new Student(4, "chang", 21, 390);
Student s5 = new Student(5, "hhhhh", 21, 375);
Student s6 = new Student(2, "zhang", 21, 370);
map.put(s1.getSno(),s1);
map.put(s2.getSno(),s2);
map.put(s3.getSno(),s3);
map.put(s4.getSno(),s4);
map.put(s5.getSno(),s5);
map.put(s6.getSno(),s6);
//其他方法
// map.clear();
map.remove(2);
//遍历输出
System.out.println(map.size());
System.out.println(map.get(3));
System.out.println("===========================================");
Set<Map.Entry<Integer, Student>> entrySet = map.entrySet();
for (Map.Entry<Integer,Student> entry:entrySet
) {
System.out.println(entry.getValue());
}
}
}
理解HashMap的源码
- 在JDK1.7及以前,HashMap底层就是一个table数组+链表实现的哈希表存储结构。
- 链表的每一个节点就是一个Entry,其中包括键key、值value、键的哈希码hash、执行下一个节点的引用next四部分。
static class Entry<K, V> implements Map.Entry<K, V> {
final K key; //key
V value;//value
Entry<K, V> next; //指向下一个节点的指针
int hash;//哈希码
}
- JDK1.7中HashMap的主要成员变量及其含义:
public class HashMap<K, V> implements Map<K, V> {
//哈希表主数组的默认长度
static final int DEFAULT_INITIAL_CAPACITY = 16;
//默认的装填因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//主数组的引用!!!!
transient Entry<K, V>[] table;
int threshold;//界限值 阈值
final float loadFactor;//装填因子
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;//0.75
threshold = (int) Math.min(capacity * loadFactor,
MAXIMUM_CAPACITY + 1);//16*0.75=12
table = new Entry[capacity];
....
}
}
- 调用put方法添加键值对。哈希表三步添加数据原理的具体实现;是计算key的哈希码,和value无关。特别注意:
1.第一步计算哈希码时,不仅调用了key的hashCode(),还进行了更复杂处理,目的是尽量保证不同的key尽量得到不同的哈希码。
2.第二步根据哈希码计算存储位置时,使用了位运算提高效率。
3.第三步添加Entry时添加到链表的第一个位置,而不是链表末尾。
4.第三步添加Entry是发现了相同的key已经存在,就使用新的value替代旧的value,并且返回旧的value。
public class HashMap {
public V put(K key, V value) {
//如果key是null,特殊处理
if (key == null)
return putForNullKey(value);
//1.计算key的哈希码hash
int hash = hash(key);
//2.将哈希码代入函数,计算出存储位置 y= x%16;
int i = indexFor(hash, table.length);
//如果已经存在链表,判断是否存在该key,需要用到equals()
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如找到了,使用新value覆盖旧的value,返回旧value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;// the United States
e.value = value;//America
e.recordAccess(this);
return oldValue;
}
}
//添加一个结点
addEntry(hash, key, value, i);
return null;
}
final int hash(Object k) {
int h = 0;
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
//作用就相当于y = x%16,采用了位运算,效率更高
return h & (length-1);
}
}
- 调用get方法根据key获取value。
1.哈希表三步查询数据原理的具体实现。
2.其实是根据key找Entry,再从Entry中获取value即可。
public V get(Object key) {
//根据key找到Entry(Entry中有key和value)
Entry<K,V> entry = getEntry(key);
//如果entry== null,返回null,否则返回value
return null == entry ? null : entry.getValue();
}
- 添加元素时达到了阈值,需要扩容,每次扩为原来主数组容量的2倍。
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果达到了门槛值,就扩容,容量为原来容量的2位 16---32
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//添加节点
createEntry(hash, key, value, bucketIndex);
}
- 在JDK1.8中有了一些变化,当链表的存储的数据个数大于等于8的时候,不再采用链表存储,而采用了红黑树存储结构。
- 这么做主要是查询的时间复杂度上,链表为O(n),而红黑树一直是O(logn)。如果冲突多,并且超过8,采用红黑树来提高效率。
理解TreeMap的源码
- 基本特征:二叉树、二叉查找树、二叉平衡树、红黑树。
- 每个节点的结构
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}
- TreeMap主要成员变量及其含义
public class TreeMap<K, V> implements NavigableMap<K, V> {
private final Comparator<? super K> comparator;//外部比较器
private transient Entry<K, V> root = null; //红黑树根节点的引用
private transient int size = 0;//红黑树中节点的个数
public TreeMap() {
comparator = null;//没有指定外部比较器
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;//指定外部比较器
}
}
- 添加原理
1.从根节点开始比较 。
2.添加过程就是构造二叉平衡树的过程,会自动平衡 。
3.平衡离不开比较:外部比较器优先,然后是内部比较器。如果两个比较器都没有,就抛出异常。
public V put(K key, V value) {
Entry<K,V> t = root;
//如果是添加第一个节点,就这么处理
if (t == null) {
//即使是添加第一个节点,也要使用比较器
compare(key, key); // type (and possibly null) check
//创建根节点
root = new Entry<>(key, value, null);
//此时只有一个节点
size = 1;
return null;
}
//如果是添加非第一个节点,就这么处理
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
//如果外部比较器存在,就使用外部比较器
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;//在左子树中查找
else if (cmp > 0)
t = t.right; //在右子树查找
else
//找到了对应的key,使用新的value覆盖旧的value
return t.setValue(value);
} while (t != null);
}
else {
//如果外部比较器没有,就使用内部比较器
....
}
//找到了要添加的位置,创建一个新的节点,加入到树中
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
size++;
return null;
}
- 查询原理基本同添加
public V get(Object key) {
//根据key(cn)找Entry(cn--China)
Entry<K,V> p = getEntry(key);
//如果Entry存在,返回value:China
return (p==null ? null : p.value);
}
final Entry<K, V> getEntry(Object key) {
//如果外部比较器存在,就使用外部比较器
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
//如果外部比较器不存在,就使用内部比较器
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K, V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
//如果找到了,就返回Entry
return p;
}
//如果没有找到,就返回null
return null;
}