Map
Map用于保存具有映射关系的数据,因此Map集合中保存着两组值。一组保存Map里的key,另外一组保存Map里的value,key和value都是可以任何引用类型的数据。Map中的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总能返回false。
key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的,确定的value。从map中取出数据时,只要给出指定的key就可以取出对应的value。如果把map里面的两组值拆开来看,map里所有的key放在一起来看,它们就组成了一个set集合,map里面有一个keySet()方法,用于返回Map里所有key组成的Set值。
不仅如此Map里的key集和set集合里元素存储形式也很像,Map子类和Set子类在名字上也惊人地相似。比如Set接口下有HashSet、LinkedHashSet SortedSet(接口) TreeSet EnumSet等子接口和实现类。而Map接口下则有HashMap LinkedHashMap SortedMap(接口) TreeMap EnumMap 等子接口和实现类。正如他们的名字所暗示的,Map的这些实现类和子接口中key集的存储形式和对应Set集合中元素的存储形式完全相同。
Set与Map之间的关系非常密切,虽然Map中放的元素是key-value对,Set集合中放的元素是单个元素,但如果把key-value对中的value当成key的附庸:key在哪里,value就跟在哪里。这样就可以像对待Set一样来对待Map了。事实上,map提供了一个Entry内部类来封装key-value对,而计算Entry存储时,只考虑了Entry封装的key。从java源码来看,java是先实现了Map,然后通过包装一个所有value都为空对象的Map就实现了Set集合。
如果把Map所有value放在一起看,它们又非常类似于一个List:元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中索引不再使用整数值,而是以另一个对象作为索引。如果需要从List集合中取出元素,则需要提供该元素的数字作为索引:如果需要从Map中取出元素,则需要提供该元素的key索引。因此,Map有时候也被称为字典。p324中提供Map接口中一些常用的方法。HashMap重写了toStringg()方法,实际上所有的Map实现类都实现了toString()方法,调用Map对象的toString()方法总是返回如下格式的字符串:{key1=value1,key2=value2…}。
简单测试如下:
package Map;
import java.util.HashMap;
public class MapTest {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("test1",109);
map.put("test2",10);
map.put("test3",79);
map.put("test4",79);
//如果放入重复的key,新的value会覆盖原有的value,并返回被覆盖的value
System.out.println(map.put("test2",222));
System.out.println(map);
//判断是否包含指定的value
System.out.println(map.containsValue(222));
//判断是否包含指定的key
System.out.println(map.containsKey("test2"));
//获取所有的key然后进行遍历
for (String key: map.keySet()
) {
System.out.println(key + "-->" + map.get(key));
}
//通过key来删除key value
map.remove("test2");
System.out.println(map);
}
}
结果如下:
在java8中为Map增加了一些方法,具体方法在p327中有讲,现在先进行简单的测试一下方法的使用。
package Map;
import java.util.HashMap;
import java.util.Map;
public class MapTest2 {
public static void main(String[] args) {
Map hashMap = new HashMap();
hashMap.put("xiaolu",20);
hashMap.put("xiaohuan",30);
hashMap.put("xiaoli",40);
hashMap.put("xiaoding",50);
//尝试替换key为"xiaoji"的value,由于原map中没有对应的key,所以map不会改变.
hashMap.replace("xiaoji",60);
System.out.println(hashMap);
//使用原value与传入参数计算出来的结果覆盖原有的value
hashMap.merge("xiaoding",10,(oldValue,param)->(Integer) oldValue + (Integer) param);
//可以看到xiaoding所对应的数据增加了10
System.out.println(hashMap);
//当key为xiaohu对应的value为null或不存在时,使用计算结果作为新的value
hashMap.computeIfAbsent("xiaohu",key-> ((String) key).length());
System.out.println(hashMap);
//当key为“xiaohu”对应的value存在时,使用计算的结果作为新的value
hashMap.computeIfPresent("xiaohu",(key,value)->(Integer)value*(Integer)value);
System.out.println(hashMap);
}
}
结果如下:
HashMap和Hashtable都是Map接口的典型实现类,他们之间的关系就类似于ArrayList和Vector。HashMap是线程不安全的,Hashtable是线程安全的。Hashtable中value和key都不允许为null。HashMap就可以,但是,key只能存在一个null,value则可以无数个null。Hashtable比较古老,一般不用。
与HashSet集合不能保证元素顺序一样,HashMap也不能保证key-value的顺序。类似于HashSet,HashMap判断两个key相等的标准也是:两个key通过equals()方法返回true,两个key的hashCode值也相等。
除此之外,HashMap还有一个containsValue()方法,用于判断是否包含指定的value。
HashMap如何判断两个value相等呢?只要判断两个对象通过equals()方法比较返回true即可。
代码如下:
package Map;
import java.util.HashMap;
//A这个类,他的equals()和hashCode()这两个方法都是通过其实例变量决定
class A{
private int count;
public A(int count){
this.count = count;
}
public boolean equals(Object object){
//说明同一个对象
if(object == this){
return true;
}else if(object!=null && object.getClass() == this.getClass()){
A a = (A)object;
return a.count == this.count;
}else {
return false;
}
}
/**
* 通过count来返回它的hashCode值
* @return
*/
public int hashCode(){
return this.count;
}
}
class B{
public boolean equals(Object object){
return true;
}
}
public class MapTest3 {
public static void main(String[] args) {
A a = new A(2000);
A a1 = new A(122);
A a2 = new A(11);
B b = new B();
HashMap hashMap = new HashMap();
hashMap.put(a,"大话数据结构");
hashMap.put(a1,"大话设计模式");
hashMap.put(a2,"网络是怎么连接的");
System.out.println(hashMap);
//b调用equals()方法返回true
System.out.println(hashMap.containsValue(b));
//两个对象的equals()相等,并且hashCode()相等
System.out.println(hashMap.containsKey(new A(2000)));
//删除最后一个键值对
hashMap.remove(a2);
System.out.println(hashMap);
}
}
与hashMap一样,尽量不要使用可变对象做为HashMap、Hashtable的key,如果确实需要使用可变对象作为key,则尽量不要在程序中修改做为key的可变对象。
当使用自定义类作为HashMap、Hashtable的key时,如果重写改类的equals(Object obj)和hashCode()方法时,则应该保证两个方法的判断标准一致。
LinkedHashMap实现类
1.它是HashMap的子类;
2.它使用双向链接来维护key-value对顺序,迭代顺序与key-value对的插入顺序保持一致;
3.它适合遍历全部元素;
4.HashMap适合添加元素;
测试代码:
package Map;
import java.util.LinkedHashMap;
public class LinkedHashMapTest {
public static void main(String[] args) {
LinkedHashMap linkedHashMap = new LinkedHashMap();
linkedHashMap.put(1,1);
linkedHashMap.put(2,1);
linkedHashMap.put(3,1);
System.out.println(linkedHashMap);
//用forEach来进行遍历,可以选择遍历的格式
linkedHashMap.forEach((key,value)-> System.out.println("key = " + key + " value = " + value));
}
}
结果如下:
使用Properties类来读写属性文件
Properties类在处理属性文件时特别方便。Properties可以把Map对象和属性文件关联起来,从而把Map对象中的key-value对写入属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。由于属性文件属性名和属性值只能是字符串类型,故Properties中key和value都是字符串类型。
package Map;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.setProperty("username","xiaolu");
properties.setProperty("password","123456");
//将Properties中的key-value对保存到a.txt文件中
properties.store(new FileOutputStream("a.txt"),"这是备注");
Properties properties2 = new Properties();
properties2.setProperty("gender","male");
//将a.txt文件中key-value追加到properties2中
properties2.load(new FileInputStream("a.txt"));
System.out.println(properties2);
}
}
所以,Properties可以把key-value以XML文件的形式进行保存,也可以从XML文件中加载key-value对,用法与此类似。
TreeMap
1.TreeMap是一个红黑树的数据结构
2.TreeMap存储时,根据key对节点进行排序,TreeMap可以保证所有的key-value对处于有序状态;
3.TreeMap有两种排序方式;
4.自然排序:TreeMap的所有key必须实现Comparable
5.定制排序:创建TreeMap时,传入一个
简单来说:TreeMap的作用是当你要存key-value时;同时,你想要按照顺序进行存储,这样就可以使用它来进行了。
代码如下:
package Map;
import java.util.TreeMap;
class R implements Comparable {
int count;
public R(int count) {
this.count = count;
}
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object != null && object.getClass() == R.class) {
R obj = (R) object;
return obj.count == this.count;
} else {
return false;
}
}
@Override
public int hashCode() {
return this.count;
}
@Override
public String toString() {
return "R{" +
"count=" + count +
'}';
}
@Override
public int compareTo(Object o) {
R r = (R) o;
if (r.count > this.count) {
return -1;
} else if (r.count < this.count) {
return 1;
}else {
return 0;
}
}
}
public class TreeMapTest {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap();
treeMap.put(new R(3), "lu");
treeMap.put(new R(-5), "luguo");
treeMap.put(new R(9), "luguobao");
System.out.println(treeMap);
//返回最大的key所对应的key-value
System.out.println(treeMap.lastEntry());
//返回位于key后面的一位key-value值
System.out.println(treeMap.higherKey(new R(2)));
System.out.println(treeMap.lowerEntry(new R(2)));
}
}
WeakHashMap
WeakHashMap和HashMap的使用方法基本一致,但是,WeakHashMap是弱引用,而HashMap是强引用。对应HashMap来说,只要HashMap对象不被销毁,则该对象所有的key都不会被垃圾回收。可以理解为HashMap对其有着强引用。但是,如果是选择WeakHashMap,则key只是保持着弱引用,也就是说,当key没有被其他强引用对象所引用的话 就会被垃圾回收站给回收。
package Map;
import java.util.WeakHashMap;
public class WeakHashMapTest {
public static void main(String[] args) {
WeakHashMap weakHashMap = new WeakHashMap();
weakHashMap.put(new String("yuwen"), "xixi");
weakHashMap.put(new String("shuxue"), "xixi");
weakHashMap.put(new String("yingyu"), "xixi");
weakHashMap.put("zhongguo","nihao");
System.out.println(weakHashMap);
//通知系统进行垃圾回收
System.gc();
System.runFinalization();
System.out.println(weakHashMap);
}
}
因为前三个new String 是匿名对象,没有强引用,当执行垃圾回收以后,会发现第二次输出的只有一个key-value。“zhongguo”:这个代表的是字符串直接量 使用缓冲池保存对该字符串对象的强引用。
如果要使用WeakHashMap则加入key应该是弱引用的,不然,就没有意义了。
EnumMap
注意点:
1.内部以数组形式进行保存,非常高效
2.所有的key必须是单个枚举类的枚举值,创建时需要和枚举类进行联系
3.不允许使用null为key值
4.排序是按照Season的排序进行的
用法:
package Map;
import java.util.EnumMap;
enum Season{
SPRING,SUMMER,FALL,WINTER
}
public class EnumMapTest {
public static void main(String[] args) {
EnumMap enumMap = new EnumMap(Season.class);
enumMap.put(Season.SUMMER,"夏天到了");
enumMap.put(Season.SPRING,"秋天到了");
System.out.println(enumMap);
}
}
结果如下:
各Map实现类的分析
HashMap:一般情况多使用它,因为HashMap底层是采用数组来存储key-value的,为了快速查询设计的。
TreeMap:由于底层是用红黑树来进行管理key-value。所以,速度会比HashMap慢很多,但是,它的优点是无须进行排序操作。当TreeMap被填充后,就可以调用keySet(),取得由key组成的Set,然后,使用toArray()方法生成key数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速查询对象。
这样就可以知道new R(-3)是在索引为1的位置上。
所以,一般情况下,使用HashMap,如果程序需要一个总是排好序的Map,则使用TreeMap。LinkedHashMap比HashMap慢一些,性能没有出色之处,他使用==而不是equals来判断元素相等。EnumMap的性能最好,但它只能使用同一个枚举类的枚举值来作为key。
关于==和equals()的区别,可以参考以下博客==与equals区别重点: HashSet和HashMap的性能选项
HashSet和HashMap中,采用hash算法来决定集合中元素的存储位置。hash表中存储元素的位置称为桶,正常来说,一个桶中纯一个元素的时候,效率是最高的。hash算法可以根据hashCode值计算出“桶”的存储位置,在发生冲突的时候,单个桶会存储多个元素,以链表的方式来进行存储。
HashSet、HashMap中hash表有如下属性:
容量:hash表中桶的数量
初始化容量:创建hash表时桶的数量。这个可以指定的
尺寸:当前hash表中记录的数量
负载因子:负载因子等于 “尺寸/容量” 负载因子小的时候,代表表中冲突少、适合插入。
负载极限:决定了hash表的最大填满程度,当到达指定负载极限时,hash表会自动成倍的增加容量,桶的数量。并且将原来的对象重新分配到新桶内
负载极限默认为0.75 这是时间和空间的一种折中:如果负载极限太高,可以减小占用内存空间,但是会增加查询数据的时间开销。如果负载极限过低,就会增加hash表所占用的内存空间。
**使用建议:**如果知道要存很多数据,就在初始化的过程中,使用较大的初始化容量。更高效的增加记录。