存储一对一对的数据(key-value健值对)
实现类
HashMap
元素特点
所有key彼此之间是不可重复的、无序的;key用Set来存放,不允许重复,同一个Map对象所对应的类,必须重写hashCode()和equals()
所有的value彼此之间是可重复的、无序的,就构成一个Collection集合
一个key和value构成一个Entry对象,多个Entry对象彼此之间是无序的、不可重复的
方法
// 将指定key-value添加(或修改)到当前map对象中
Object put(Object key, Object value)
// 将m中所有的key-value对存放到当前map中
void putAll(Map m)
// 移除指定key的key-value对,并返回value
Object remove(Obejct key)
// 清空当前map中的所有数据
voidd clear()
// 获取指定key对应的value
Object get(Object key)
// 是否包含指定的key
boolean containsKey(Object key)
// 是否包含指定的value
boolean containsValue(Object value)
// 返回map中的key-value对的个数
int size()
// 判断当前map是否为空
boolean isEmpty()
// 判断当前map和参数对象obj是否相等
boolean equals(Object obj)
// 返回所有key构成的Set集合
Set keySet()
// 返回都有value构成的Collection集合
Collection values()
// 返回都有key-value对构成的Set集合
Set enterySet()
使用
public void test() {
HashMap hashMap = new HashMap();
hashMap.put("AA", 56);
hashMap.put("CC", 50);
hashMap.put("BB", 78);
// 遍历key
Set keySet = hashMap.keySet();
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key);
System.out.println(hashMap.get(key));
}
// 遍历value
Collection values = hashMap.values();
for (Obejct obj: values) {
System.out.println(obj)
}
// 遍历key-value
Set enterySet = hashMap.enterySet();
Iterator iterator1 = enterySet.iterator();
while (iterator1.hasNext()) {
Map.Entry entry = (Map.Entry) iterator1.next();
System.out.println(entry.getKey()); // 获取key
System.out.println(entry.getValue()); // 获取value
}
// 遍历key-value
for (Map.Entry entry: hashMap.enterySet()) {
System.out.println(entry.getKey()); // 获取key
System.out.println(entry.getValue()); // 获取value
}
}
LinkedHashMap
HashMap的子类
在HashMap使用的数据结构基础上,添加了一组双向链表,用于记录添加元素的先后顺序;即我们可以按照添加元素的顺序实现遍历;便于频繁的查询操作
TreeMap
底层使用红黑树存储。可以按照添加的key-value中key元素的指定的属性大小顺序进行遍历。实际使用考虑自然排序、定制排序
向TreeMap中添加的key必须是同一个类型的对象
使用-自然排序
class Personal implements Comparable {
private String name;
private int age;
public Personal(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o) {
}
}
public void test() {
TreeMap t = new TreeMap();
t.add(new Personal("Tom", 23), 100);
t.add(new Personal("Jack", 23), 80);
t.add(new Personal("Lijie", 18), 130);
for (Map.Entry entry: t.enterySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
使用-自定义排序
class Personal {
private String name;
private int age;
public Personal(String name, int age) {
this.name = name;
this.age = age;
}
}
public void test() {
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
}
};
TreeMap t = new TreeMap(comparator);
t.add(new Personal("Tom", 23), 100);
t.add(new Personal("Jack", 23), 80);
t.add(new Personal("Lijie", 18), 130);
for (Map.Entry entry: t.enterySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
Hashtable
Properties
Hashtable的子类
其key和value都是String类型,常用来处理属性文件
使用
Properties properties = new Properties();
/**
* 文件中为key=value形式的数据
* name=Tome
* password123456
*/
File file = new File("文件路径");
// 加载流中的文件中的数据
properties.load(new FileInputStream(file));
// 获取文件中指定key的数据
properties.getProperty("key");
HashMap与Hashtable的区别
HashMap是主要实现类,线程不安全的,效率低;可以添加null的key和value值;底层使用数组+单向链表+红黑树结构进行存储(jdk8中引入红黑树)
Hashtable是古老实现类,线程安全的,效率高;不可以添加null的key和value值;底层使用数组+单向链表结构进行存储
HashMap源码
JDK7创建对象和添加数据过程
创建对象的过程中,底层会初始化数组Entry[] table = new Entry[16];
put的值封装成一个Entry对象中,将此对象添加到table数组中
添加/修改的过程
将(k1,value1)添加到当前的map中
首先,需要调用key所在类的hashCode方法,计算对应的哈希值1,此哈希值1经过某种算法(hash() map的内部方法)之后,得到哈希值2
哈希值2在经过某种算法(indexFor() map的内部方法)之后,就确定了(key1,value1)在数组table中的索引位置i
如果此索引位置i的数组上没有元素,则(key1,value1)添加成功 ---> 情况1
如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2 ----> 这种情况叫做哈希冲突
如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功 ---> 情况2
如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals()(比较前会先对比地址,地址相同就表示key1与key2相同)。要调用key1所在类的equals(),将key2做为参数传进去
调用equals() 返回false,则(key1,value1)添加成功 ---> 情况3
调用equals() 返回true,则认为key1和key2是相同的,默认情况下,value1替换原有的value2
说明
情况1
将(key1,value1)存放到数组的索引i的位置
情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构,(key1,value1)指向(key2,value2)
扩容情况
当元素个数达到临界值(数组的长度*加载因子(默认0.75))时,且要添加的元素所在数组索引位置有元素时就考虑扩容;默认扩容为原来的2倍
扩容时会重新计算添加元素key的哈希值2
说明
底层初始化的数组Entry[]的长度一定是2的n次方(为了更高效率确定元素位置(使用位运算))
key为null的值,将此(key,value)的元素保存到table索引为0的位置
使用key得到的hash值2会保存在Entry中,所以map数据修改时,使用的hash值还是最初始的
JDK8与JDK7的不同
创建对象的过程中,底层并没有初始化table数组
当首次添加(key,value)时进行判断,如果发现table尚未初始化,则对数组进行初始化
HashMap底层定义了Node内部类,替换了jdk7中的Entry类,意味着,我们创建的数组是Node[]
如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中,如果此时角标i位置上有元素,是将旧的元素指向新的元素
结构为数组+单向链表+红黑树
如果数组索引i位置上的元素个数达到8,并且数组的长度达到64时会将此索引位置上的多个元素改为使用红黑树的结构存储
(使用红黑树进行put、remove、get操作的时间复杂度为o(logn),比单向链表的时间复杂度o(n)好,性能更高)
红黑树退化为单向链表
当使红黑树的索引i位置上的元素个数低于6时
LinkedHashMap源码
在HashMap使用的数组+单向链表+红黑树的基础上,又增加了一对双向链表,记录添加的(key,value)的先后顺序。便于我们遍历所有的key-value
LinkedHashMap重写了HashMap的newNode方法
LinkedHashMap内部定义了一个Entry继承了HashMap的Node,在其基础上增加了一个after属性,实现了双向列表