一、Map接口
Map接口实现类的特点
1、Map
用于保存具有映射关系的数据:key-value
2、Map
中的key
和value
可以是任何引用类型的数据,会封装到HashMap$Node
对象中
3、Map
中的key
不允许重复(原因和HashSet
一样),value
允许重复
5、Map
的key
可以为null
,value
也可以为null
,但是key
为null
只能有一个,value
为null
可以有多个
package com.level7.Map_;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class map01 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("xrj", "123");
map.put("x2", "123");
map.put("x3", "123");
map.put("x4", null);
map.put(null, null );
map.put("xrj", "456");
System.out.println(map);
System.out.println(map.get("xrj"));
}
}
Map底层存储
1、key-value
实际是存放在HashMap$Node node = newNode(hash, key, value, null);
2、key-value
为了方便程序员的遍历,还会创建EntrySet
集合,该集合存放的元素类型为Map.Entry<K, V>
3、entrySet
中,定义的数据类型为Map.Entry<K, V>
,但实际存放的数据类型还是HashMap$Node
,因为HashMap$Node
是Map.Entry<K, V>
的子类,因此他可以存放进去
4、当把HashMap$Node
对象存放到 entrySet
中就方便我们的遍历,因为Map.Entry
提供了两个方法getKey()
和getValue()
,可以得到当前Entry
的key
和value
5、在entrySet
中,并没有创建新的数据,而是指向了table
表中HashMap$Node
的地址
6、HashMap
中还有keySet
方法用于返回一个Set
表示当前map所有的key
,vlaues
方法用于返回一个Collection
表示当前map所有的value
package com.level7.Map_;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class map01 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("xrj", "123");
map.put("x2", "123");
map.put("x3", "123");
map.put("x4", null);
map.put(null, null );
map.put("xrj", "456");
System.out.println(map);
System.out.println(map.get("xrj"));
// entrySet()
Set set = map.entrySet();
for (Object o : set) {
// System.out.println(o.getClass()); // class java.util.HashMap$Node
Map.Entry entry = (Map.Entry) o;
// 因为Map.Node是默认访问修饰符,因此不能访问
System.out.println(entry.getKey() + " = " + entry.getValue());
}
System.out.println(map.keySet().getClass()); // class java.util.HashMap$KeySet
System.out.println(map.keySet());
System.out.println(map.values().getClass()); // class java.util.HashMap$Values
System.out.println(map.values());
}
}
Map常用方法
package com.level7.Map_;
import java.util.HashMap;
import java.util.Map;
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
map.put("xrj", "abc");
map.put("xrj", "xyy");
map.put("x1", "a1");
map.put("x2", "a2");
map.put("x3", "a3");
System.out.println(map);
// remove:根据键删除该键值对,也可以根据键值对删除
map.remove("x3");
System.out.println(map);
// get: 根据键获取值
System.out.println(map.get("xrj"));
// size: 获取元素个数
System.out.println(map.size());
// isEmpty: 判断map是否为空
System.out.println(map.isEmpty());
// clear: 清空map
// map.clear();
// System.out.println(map);
// containsKey: 查找键是否存在
System.out.println(map.containsKey("xrj"));
// containsValue: 查找值是否存在
System.out.println(map.containsValue("a1"));
}
}
Map接口遍历方法
1、containsKey
查找键是否存在
2、keySet
获取所有的键
3、entrySet
获取所有键值对关系
4、values
获取所有的值
package com.level7.Map_;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Map_for {
public static void main(String[] args) {
Map map = new HashMap();
map.put("xrj", "123");
map.put("xyy", "456");
map.put("abc", "789");
map.put("zs", null);
map.put(null, "aaa");
// 1.先获取所有的key,然后通过key遍历map
Set key = map.keySet();
for (Object o : key) {
System.out.println(o + " " + map.get(o));
}
// 迭代器遍历
Iterator iterator = key.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next + " " + map.get(next));
}
System.out.println("=============");
// 2.通过entrySet遍历
Set set = map.entrySet();
for (Object o : set) {
Map.Entry entry = (Map.Entry)o;
System.out.println(entry.getKey() + " " + entry.getValue());
}
// 迭代器遍历
Iterator iterator1 = set.iterator();
while (iterator1.hasNext()) {
Map.Entry entry = (Map.Entry)iterator1.next();
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
二、HashMap
1、与HashSet
一样,HashMap
不保证映射的顺序,因为底层是以hash表的方式来存储的(JDK8的HashMap
底层是数组+链表+红黑树)
2、HashMap
没有实现同步,因此是线程不安全的
HashMap底层机制
1、HashMap
底层维护了Node
类型的数组table
,默认为null
2、当创建对象时,加载因子loadfactor
初始化为0.75
3、当添加key-val
时,通过key
的哈希值得到在table
的索引。然后判断该索引处是否有元素,如果没有元素就直接添加。如果该索引处有元素,继续判断该元素的key
和准备加入的key
相是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
4、第1次添加,则需要扩容table容量为16,临界值threshold
为12(16*0.75)
5、以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD
(默认是8),且table
的大小>= MIN_TREEIFY_CAPACITY
(默认64),就会进行树化(红黑树)
源码分析
package com.level7.Map_;
import java.util.HashMap;
public class HashMapSource {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);
map.put("php", 10);
map.put("java", 20);
}
}
1、HashMap map = new HashMap();
执行这句话,就创建一个HashMap
,loadFactor
初始化为0.75
2、map.put("java", 10);
第一次添加,进入put
方法,计算key
的hash值,然后调用putVal
方法
3、进入putVal
方法,因为是第一次添加,table
为null
,所以需要进入resize
进入扩容,第一次扩容后大小为16。然后p = tab[i = (n - 1) & hash]
通过这条语句,i
表示当前这个k-v
应该插入table
的位置,p 表示当前位置的对象,如果为空,表示当前位置未存放元素,可以直接插入。
map.put("java", 20);
如果执行这条语句,p = tab[i = (n - 1) & hash]
执行完这条语句后,p 不为null,说明p这个位置存有元素,进入else
语句,如果当前插入的元素的key
的hash值和p 的hash值相等 并且 插入元素的key 要么和p的key是同一个对象,要么equal
后相等,说明当前插入元素的key
已经存在,那么value
需要进行替换,用e 保存当前位置原来的对象,进入if (e != null)
中进行value
的替换。
如果if
的条件不满足,先进入else if
中判断当前链表是否是树结构,如果是树结构,使用树的方式查找,如果不是,那么就还是链表结构,就一个一个遍历查找,如果找到最后为空,就将当前元素插入进入,同时,元素数量到达8个,即插入第9个元素(且table容量达到64)进行树化,如果遍历过程中,找到和当前插入元素的key
相同的,就进行value
的替换。
如果删除元素时,红黑树上元素个数小于8,会退化为链表
三、Hashtable
1、存放的元素是键值对
2、Hashtable
的键和值都不能为null
,否则会抛出空指针异常
3、Hashtable
使用方法和HashMap
一样
4、Hashtable
是线程安全的,HashMap
是线程不安全的
源码分析
package com.level7.Map_;
import java.util.Hashtable;
public class HashTable01 {
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
hashtable.put("x1", 123);
hashtable.put("x2", 123);
hashtable.put("x3", 123);
hashtable.put("x4", 123);
hashtable.put("x5", 123);
hashtable.put("x6", 123);
hashtable.put("x7", 123);
hashtable.put("x8", 123);
hashtable.put("x9", 123);
hashtable.put("x0", 123);
}
}
1、Hashtable hashtable = new Hashtable();
执行这条语句。会创建一个Hashtable
,初始化大小为11,加载因子为0.75
2、hashtable.put("x1", 123);
第一次执行put
,如果value
为null
就抛出空指针异常。然后通过int hash = key.hashCode();
计算出当前key
的hash值,如果key
为null,这里会抛出异常,因此key
也不可以为null,通过int index = (hash & 0x7FFFFFFF) % tab.length;
计算出应该插入的位置。Entry<K,V> entry = (Entry<K,V>)tab[index];
这条语句是得到当前要插入位置的对象,如果不为空,那么就进入for
查找和当前插入元素的hash值相同并且equals
后也相等(说明key是相同的),就进行value
的替换,如果entry
为空或者在for
循环中没找到,就执行addEntry(hash, key, value, index);
来添加新的元素
3、addEntry
方法中,首先判断当前容量是否达到了临界值,如果达到临界值,就执行rehash()
方法进行扩容,否则,就创建新的entry
然后添加进去。
tab[index] = new Entry<>(hash, key, value, e);
这里传入的e
是和当前插入元素hash值相同的链表的第一个节点,相当于头插法。
4、rehash()
方法,每次扩容为原始大小的2倍+1。Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
然后创建新的table数组。同时进入for
循环,将旧的table中的数据复制到新的里边。
Hashtable和HashMap对比
版本 | 线程安全 | 效率 | 允许null键null值 | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hahstable | 1.0 | 安全 | 较低 | 不可以 |
四、Properties
1、Properties
类继承自Hashtable
类并实现了Map
接口,也是使用键值对保存数据。他的put
源码和Hashtable
相同
2、Properties
可以用于从XXX.properties
文件中,加载数据到Properties
类对象,进行读取和修改
3、Properties
也不可以有空键和空值
package com.level7.Map_;
import java.util.Properties;
public class Properties_ {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("x1", 1);
properties.put("x2", 2);
properties.put("x3", 3);
properties.put("x4", "400");
properties.put(123, 4);
System.out.println(properties);
// 修改
properties.put("x1", 123);
System.out.println(properties);
// 删除
properties.remove("x3");
System.out.println(properties);
// 查找
System.out.println(properties.get("x1"));
System.out.println(properties.get(123));
System.out.println(properties.getProperty("x4")); // getProperty获取的key和value必须都是String
}
}
五、TreeMap
可以将键按指定规则进行排序
无参构造
package com.level7.Map_;
import java.util.TreeMap;
public class TreeMap_ {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap();
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("xrj", "徐睿杰");
}
}
1、TreeMap treeMap = new TreeMap();
执行后会创建一个TreeMap
,比较器为空
2、treeMap.put("jack", "杰克");
执行后,直接进入put
方法,因此第一次添加,所以根节点root
为null
,然后进入默认的compare
方法来比较key
的大小,然后创建节点添加进去
3、第二次添加,进入put
方法后,根节点不为空,但是传入的比较器为空,因此进入下边代码中,比较key
的大小,如果相等,就进行值的替换。和TreeSet
一样
有参构造
package com.level7.Map_;
import java.util.Comparator;
import java.util.TreeMap;
public class TreeMap_ {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap(new Comparator(){
@Override
public int compare(Object o1, Object o2){
// return ((String)o1).compareTo((String)o2);
return ((String)o1).length() - ((String)o2).length();
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("xrj", "徐睿杰");
System.out.println(treeMap);
}
}
1、TreeMap treeMap = new TreeMap(new Comparator(){
执行后会创建一个TreeMap
传入的有比较器
2、之后每次add
调用的比较器就是我们传入的。
3、treeMap.put("xrj", "徐睿杰");
当执行到这里时,因为我们传入的比较器是比较字符串长度大小,因此这个传入后,key
比较后是相等的,无法插入,因此会进行value
的替换
六、集合的选择
1、Collection
接口(单列对象)
允许重复:List
增删多:LinkedList
【底层维护了一个双向链表】
改查多:ArrayList
【底层维护了一个Object类型的可变数组】
不允许重复:Set
无序:HashSet
【底层是HashMap,数组+链表+红黑树】
排序:TreeSet
插入和取出顺序一致:LinkedHashSet
【底层是LinkedHashMap,数组+链表+双向链表】
2、Map
(键值对)
键无序:HashMap
【底层是:哈希表 jdk7:数组+链表 jdk8:数组+链表+红黑树】
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
不管是TreeSet
还是TreeMap
,他们传入值时,key
的类型必须相同,因为要进行key
值的比较,如果类型不同无法比较会抛出异常