Map接口继承树:

Map接口概述:

  • Map 与Collection并列存在。用于保存具有映射关系的数据:key-value。
  • Map 中的 key 和 value 都可以是任何引用类型的数据。
  • Map 中的 key 用 Set 来存放,不允许重复,即同一个Map 对象所对应的类,须重写 hashCode() 和 equals() 方法。
  • 常用 String 类作为Map的 “键”。
  • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value。
  • Map 接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map接口使用频率最高的实现类

Map的实现类的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。

原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。

|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
底层使用红黑树

|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型

HashMap的底层:数组+链表 (jdk7及之前)

数组+链表+红黑树 (jdk 8)

Map结构的理解:

  • Map中的key:无序的、不可重复的,使用Set存储所有的key —–> key所在的类要重写equals() 和 hashCode() 【以HashMap为例】。
  • Map中的value:无序的、不可重复的,使用Collection存储所有的value —–> value所在的类要重写equals()。
  • 一个键值对:key–value 构成了一个 Entry 对象。
  • Map 中的entry:无序的、不可重复的,使用Set存储所有的entry。

HashMap的底层实现原理【以jdk7为例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
		HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put...

map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。-----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。-----情况2
如果key1的哈希值与已经存在的数据的哈希值相同,将要进一步比较,调用key1所在类的equnls(key2)方法进行比较:
如果equals()返回false:此时key1和value1添加成功。-----情况3
如果equals()返回true:使用value1替换value2。

补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

在不断添加的过程中,会涉及到数组的扩容问题,当超出临界值(且要存放的位置非空)时扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据赋值过来。

JDK8相较于JDK7在底层实现方面的不同:
1new HashMap():底层没有创建一个长度为16的数组。
2:JDK8 底层采用的数组是Node[],而非Entry[]。
3:首次调用put()方法时,底层创建长度为16的数组。
4:JDK7底层结构只有:数组+链表。 JDK8底层结构:数组+链表+红黑树。
4.1:形成链表时,七上八下(JDK7:新的元素指向指向旧的元素。JDK8:旧的元素指向新的元素)
4.2:当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组长度 > 64 时,此时索引位置上的所有数据改为使用红黑树存储。


HashMap源码中的重要常量:

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
  • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子
  • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树
  • UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
  • table:存储元素的数组,总是2的n次幂
  • entrySet:存储具体元素的集
  • size:HashMap中存储的键值对的数量
  • modCount:HashMap扩容和结构改变的次数。
  • threshold:扩容的临界值,=容量*填充因子
  • loadFactor:填充因子

LinkedHashMap:

  • LinkedHashMap 是 HashMap 的子类。
  • 在HashMap 存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。
  • 与LinkedHashSet 类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致。

HashMap与LinkedHashMap的底层源码比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--------------HashMap底层源码:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
--------------LinkeHashMap底层源码:
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);
}
}

Map常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
 添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据

元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等

元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合

// 添加 删除 修改
public void test3(){
Map map = new HashMap();
//添加
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
//修改
map.put("AA",87);

System.out.println(map);

Map map1 = new HashMap();
map1.put("CC",123);
map1.put("DD",123);

map.putAll(map1);

System.out.println(map);

//remove(Object key)
Object value = map.remove("CC");
System.out.println(value);
System.out.println(map);

//clear()
map.clear();//与map = null操作不同
System.out.println(map.size());
System.out.println(map);
}

// 查询
public void test4(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
// Object get(Object key)
System.out.println(map.get(45));
//containsKey(Object key)
boolean isExist = map.containsKey("BB");
System.out.println(isExist);

isExist = map.containsValue(123);
System.out.println(isExist);

map.clear();

System.out.println(map.isEmpty());

}

// 元视图:俗称遍历。
public void test5(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,1234);
map.put("BB",56);

//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();
//遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){
System.out.println(obj);
}
System.out.println();
//遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());

}
System.out.println();
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "=====" + value);

}

}

TreeMap:

  • TreeMap存储 Key-value对时,需要根据 key-value对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
  • TreeSet底层使用红黑树结构存储数据。
  • TreeMap的key排序:
    • 自然排序:TreeMap 的所有Key必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClassCastException。
    • 定制排序:创建 TreeMap 时,传入一个Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 key 实现 Comparable 接口。
  • TreeMap判断两个key相等的标准:两个key通过 commparTo() 方法或者compare() 方法返回0。

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class TreeMapTest {

//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要按照key进行排序:自然排序 、定制排序
//自然排序
@Test
public void test1(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());

}
}

//定制排序
@Test
public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());

}
}


}

Hashtable:

  • Hashtable 是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
  • Hashtable 实现原理和 HashMap 相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以户用。
  • 与HashMap不同,Hashtable 不允许使用 null 作为 key 和value。
  • 与HashMap一样,Hashtable 也不能保证其中 key-value 对的顺序。
  • Hashtable 判断两个key相等、两个value相等的标准,与HashMap一致。

Properties:

  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件。
  • 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型。
  • 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。

Demo

1
2
3
4
Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);

Collections工具类:

  • Collections 是一个操作 Set、List 和 Map 等集合的工具类。

  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。

  • 排序操作:(均为static方法)

    reverse(List):反转 List 中元素的顺序

    shuffle(List):对 List 集合元素进行随机排序

    sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序

    sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序

    swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class CollectionsTest {

/*
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

*/
@Test
public void test2(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(-97);
list.add(0);

//报异常:IndexOutOfBoundsException("Source does not fit in dest")

// List dest = new ArrayList();
// Collections.copy(dest,list);
//正确的:
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);

System.out.println(dest);


/*
Collections 类中提供了多个 synchronizedXxx() 方法,
该方法可使将指定集合包装成线程同步的集合,从而可以解决
多线程并发访问集合时的线程安全问题

*/
//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);


}

@Test
public void test1(){
List list = new ArrayList();
list.add(123);
list.add(43);
list.add(765);
list.add(765);
list.add(765);
list.add(-97);
list.add(0);

System.out.println(list);

// Collections.reverse(list);
// Collections.shuffle(list);
// Collections.sort(list);
// Collections.swap(list,1,2);
int frequency = Collections.frequency(list, 123);

System.out.println(list);
System.out.println(frequency);

}

}