Java8增强的Map集合
Key-value都可以是任何引用类型的数据,Map的Key不允许重复即同一个Map对象的任何两个key通过equals方法比较总是返回false。
如果把Map里的所有key放在一起来看,他们就组成了一个Set集合:所有key没有顺序,key与key之间不能重复,实际上Map确实包含了一个keySet方法,用来返回Map里所有key组成的Set集合。
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都为null的Map就实现了Set集合。
如果把Map里的所有value放在一起来看,他们又非常类似于一个List:元素与元素之间可以重复,但每个元素可以根据索引来查找,只是Map中的索引不再使用整数值,而是以另一个对象作为索引。
public class MapTest {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("java", 1);
map.put("python", 2);
map.put("hadoop", 3);
//{python=2, java=1, hadoop=3}
System.out.println(map);
//如果当前Map中已经有一个与该Key相等的key-value对,则新的key-value会覆盖原来的key-value对。并返回之前被覆盖的value
Integer put = map.put("java", 4);
System.out.println(put);//1
for(Entry map2:map.entrySet()) {
System.out.println(map2);
//python=2
//java=4
//hadoop=3
}
for(Object obj : map.keySet()) {
System.out.println(obj);//python java hadoop
}
}
}
Java8为Map新增加的方法
public class NewMapTest {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("123", 456);
//{123=456}
System.out.println(map);
/**
* compute 和 computeIfAbsent 和 computeIfPresent总结
*
* compute 如果函数式接口中的返回值不为null,那就是用接口中返回值来覆盖原value
* 如果接口中返回值为null,那么就删除此map对
* 如果原value为null,那就用接口中返回值覆盖原value
* computeIfAbsent
* 如果函数式接口中的返回值不为null,而且原value不为null,那么不做改变
* 如果接口中返回值为null,不做改变
* 如果原value为null,那么就使用接口中的返回值覆盖原value
*/
/**
* map.compute(key, remappingFunction)
* 该方法使用remappingFunction根据原来key-value对计算一个新value
* 只要新value不为null,就使用新value覆盖原来value;
* 如果原来value不为null,但新value为null,则删除原key-value对;
* 如果原value,新value同时为null,那么方法不改变任何key-value对,直接返回null;
*/
/*
Integer compute = map.compute("123", (key,value) -> Integer.parseInt(key));
System.out.println(compute);//123
System.out.println(map);//{123=123}
Integer compute = map.compute("123", (key,value) -> null);
System.out.println(map);//{}
*/
/*
map.put("1234", null);
Integer compute = map.compute("1234", (key,value) -> 0);
System.out.println(compute);//0
System.out.println(map);//{123=456, 1234=0}
*/
/**
* Absent 缺席
* map.computeIfAbsent(key, mappingFunction)
* 如果传给该方法的key参数在Map中对应的value为null,则使用mappingFunction根据原来key,value计算一个新的结果
* 如果计算结果不为null,则用计算结果覆盖原有的value。如果原Map原来不包括该Key,那么该方法可能会添加一组key-value对。
*/
/*
map.put("1234", null);
map.computeIfAbsent("1234", (key) -> Integer.parseInt(key));
System.out.println(map);//{123=456, 1234=1234}
*/
/*
Integer computeIfAbsent = map.computeIfAbsent("1231", (e) -> Integer.parseInt(e));
System.out.println(computeIfAbsent);//1231
System.out.println(map);//{123=456, 1231=1231}
*/
/*
Integer computeIfAbsent = map.computeIfAbsent("123", (e) -> Integer.parseInt("123123123"));
System.out.println(computeIfAbsent);//456
System.out.println(map);//{123=456}
*/
/**
* Present 提出;介绍;呈现;赠送
* map.computeIfPresent(key, remappingFunction)
* 如果传给该方法的key参数在Map中对应的value不为null,该方法使用remappingFunction根据原key-value计算一个新结果,
* 如果计算结果不为null,则使用该结果覆盖原来的value,
* 如果计算结果为null,则删除原key-value对
*/
/*
Integer computeIfPresent = map.computeIfPresent("123", (key,value) -> 0);
System.out.println(computeIfPresent);//0
System.out.println(map);//{123=0}
*/
/*
Integer computeIfPresent = map.computeIfPresent("1234", (Key,value) -> 0);
System.out.println(computeIfPresent);//null
System.out.println(map);//{123=456}
*/
/*
map.put("1234", null);
Integer computeIfPresent = map.computeIfPresent("1234", (key,value) -> 12);
System.out.println(computeIfPresent);//null
System.out.println(map);//{123=456, 1234=null}
*/
/*
Integer computeIfPresent = map.computeIfPresent("123", (key,value) -> null);
System.out.println(computeIfPresent);//null
System.out.println(map);//{}
*/
//获取指定key对应的value,如果key不存在那么就返回指定value
Integer orDefault = map.getOrDefault("1234", 452);
System.out.println(orDefault);//452
/**
* 该方法会先根据key参数获取该Map中对应的value,如果获取的value为null
* 则直接用传入的value覆盖原有的value,在这种情况下,可能要添加一组map对,
* 如果value不为null,则使用函数接口根据value,新value计算出一个新的结果,并用得到的结果去覆盖原有的value
*/
Integer merge = map.merge("123", 123123, (key,value) -> value+1000);
System.out.println(merge);//124123
System.out.println(map);//{123=124123}
}
}
Java8改进的HashMap和Hashtable实现类
Hashtable和HashMap区别
Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能高一点,但如果有多个线程访问同一个Map对象时,使用Hashtable实现类会更好、
Hashtable不允许使用null作为key和value,如果试图把null值放入Hashtable里,将引发空指针异常,但HashMap可是使用null作为key和value、
为了成功的在HashMap,Hashtable中存储,获取对象,用作key的对象必须实现hashCode方法和equals方法。
类似于HashSet,HashMap,Hashtable判断两个key相等的标准也是:两个key通过equals方法返回true,两个key的hashCode值也相等。
HashMap和Hashtable判断两个value相等的标准是:只要两个对象通过equals方法比较返回true即可。
public class ABHashtable {
public static void main(String[] args) {
Hashtable hashtable = new Hashtable<>();
hashtable.put(new A(123), "123");
hashtable.put(new A(1234), "1234");
hashtable.put(new A(1236), new B());
System.out.println(hashtable);
System.out.println(hashtable.containsValue("32342342"));//true
System.out.println(hashtable.containsKey(new A(123)));//true
System.out.println(hashtable.containsKey(new A(123123)));//false
}
}
上述代码解释:上述Hashtable中包含了B对象,而且重写了B对象的equals方法,它与任何对象通过equals都会返回true,所以在第一个输出是true。根据Hashtable判断两个key相等的标准,在第二个输出的时候,因为通过equals和hashCode都返回true,所以Hashtable判断这两个key相等,所以为true。
与HashSet类似的是,如果使用可变对象作为HashMap,Hashtable的key,如果程序修改了可变对象,那么程序再也无法准确访问到Map中被修改过的key。
public class ABHashtable2 {
public static void main(String[] args) {
HashMap hashtable = new HashMap();
hashtable.put(new A(123), "123");
hashtable.put(new A(1234), "1234");
//{mapTest.A@4d2=1234, mapTest.A@7b=123}
System.out.println(hashtable);
A next = (A) hashtable.keySet().iterator().next();
System.out.println(next.count);//1234
next.count = 123;
//{mapTest.A@7b=1234, mapTest.A@7b=123}
System.out.println(hashtable);
hashtable.remove(new A(123));
//只能删除没有被修改的key所对应的key-value对
System.out.println(hashtable);//{mapTest.A@7b=1234}
System.out.println(hashtable.get(new A(123)));//null
}
}
尽量不要使用可变对象作为key,如果确实需要,则尽量不要在程序中修改作为key的对象。
LinkedHashMap实现类
LinkedHashMap也使用双向链表来维护key-value对的次序,其实只需要考虑key的次序,该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
因为他使用链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能,迭代输出LinkedHashMap的元素时,将会安添加key-value的顺序输出。
public class LinkedHashMapS {
public static void main(String[] args) {
LinkedHashMap map = new LinkedHashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
System.out.println(map);//{1=1, 2=2, 3=3}
}
}
使用Properties读写属性文件
Properties是Hashtable类的子类
Properties相当于一个key,value都是String的Map
public class PropertiesTest {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties properties = new Properties();
properties.setProperty("w","zq");
properties.setProperty("w1","zq1");
properties.setProperty("w2","zq2");
//输出文件目录,文件说明
properties.store(new FileOutputStream(new File("myProperties.properties")), "comment");
properties.storeToXML(new FileOutputStream(new File("myProperties1.xml")), "comment");
}
}
myProperties
#comment
#Fri Mar 09 10:11:42 CST 2018
w=zq
w1=zq1
w2=zq2
myProperties1
comment
zq
zq1
zq2
SortedMap接口和TreeMap实现类
TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对时,需要根据key对节点进行排序。TreeMap可以保证所有的key-value对处于有序状态。TreeMap也可以自然排序和定制排序。
TreeMap中判断两个key相等的标准是:两个key通过compareTo方法返回0,TreeMap即认为这两个key是相等的。
如果使用自定义类作为TreeMap的key,且想让TreeMap良好的工作,则重写该类的equals方法和compareTo方法时保持一致的返回结果。
Set和Map的关系十分密切,java源代码就是先实现了HashMap,TreeMMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
public class TreeMapTest {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap<>();
treeMap.put(12, 34);
treeMap.put(-12, 34);
treeMap.put(122, 34);
treeMap.put(0, 34);
//{-12=34, 0=34, 12=34, 122=34}
System.out.println(treeMap);
}
}
WeakHashMap实现类
WeakHashMap与HashMap的区别是:HashMap的key保留对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应的key-value对;但WeakHashMap的key只保留了实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。
public class WeakHashMapTets {
public static void main(String[] args) {
WeakHashMap weakHashMap = new WeakHashMap<>();
weakHashMap.put(new String("1"), new String("1"));
weakHashMap.put(new String("2"), new String("2"));
weakHashMap.put(new String("3"), new String("3"));
weakHashMap.put("4", new String("4"));
//{4=4, 1=1, 2=2, 3=3}
System.out.println(weakHashMap);
System.gc();
System.runFinalization();
//{4=4}
System.out.println(weakHashMap);
//第四组key-value对的key是一个字符串直接量,系统会自动保留对该字符串对象的强引用,所以垃圾回收时不会回收他
}
}
如果需要使用WeakHashMap的key来保留对象的弱引用,则不要让该key所引用的对象具有任何强引用了否则将失去WeakHashMap的意义。
IdentityHashMap实现类
在IdentityHashMap中,当且仅当两个key严格相等key1 == key2时,IdentityHashMap才认为两个key相等,对于普通的HashMap而言,只要key1和key2通过equals方法比较返回true,且他们的hashCode值相等即可。
public class IdentityHashMapTest {
public static void main(String[] args) {
IdentityHashMap identityHashMap = new IdentityHashMap<>();
identityHashMap.put(new String("r"),"r");
identityHashMap.put(new String("r"),"r");
identityHashMap.put("java", 0);
identityHashMap.put("java", 1);
//{java=1, r=r, r=r}
System.out.println(identityHashMap);
/**
* 由于上面new String()的地址值不一样,IdentityHashCode返回值会不一样
* 所以第一个String与第二个String并不一样
* 但是java字符串直接量是一样的,所以第二次put就会覆盖原来的value
*/
}
}
EnumMap实现类
EnumMap中的所有key都必须是单个枚举类的枚举值,创建EnumMap时必须显示或隐式指定它对应的枚举类。
EnumMap具有如下特征
EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑,高效。
EnumMap根据key的自然顺序,即枚举值的定义顺序,来维护key-value对的顺序。
EnumMap不允许使用null作为key,但允许使用null作为value,如果试图使用null作为key时将抛出空指针异常。如果只是查询是否包含值为null的key,或只是删除值为null的key,都不会抛出异常。
创建EnumMap时必须指定一个枚举类,从而将该EnumMap和指定的枚举类关联起来。
public class EnumMapTest {
public static void main(String[] args) {
EnumMap enumMap = new EnumMap(EnumMaps.class);
System.out.println(enumMap);//{}
enumMap.put(WINTER, "4");
enumMap.put(SPRING, "4");
enumMap.put(SUMMER, "4");
//{SPRING=4, SUMMER=4, WINTER=4}
System.out.println(enumMap);
}
}
各Map实现类的性能分析
HashMap和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的,线程安全的集合,因此HashMap通常比Hashtable要快。
TreeMap通常比HashMap,Hashtable要慢,尤其在插入删除key-value对时更慢,因为TreeMap底层采用红黑树来管理key-value对,红黑树的每个节点就是一个key-value对。
使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无需专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),去的由key组成的Set,然后使用toArry()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。
对于一般的应用场景,程序应该多考虑使用HashMap,因为HashMap正是为快速查询设计的:HashMap底层其实也是采用数组来存储key-value对。但如果程序需要一个总是排序好的Map时,则可以考虑使用TreeMap。
LinkedHashMap比HashMap慢一点,因为他需要维护链表来保持Map中key-value时的添加顺序。IdentityHashMap心梗没有特别出色之处,因为他采用与HashMap基本相似的实现,只是它使用==而不是equals方法来判断元素相等。EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。
HashSet和HashMap的性能选项
对于HashSet及其子类而言,它们采用hash算法来决定集合中元素的存储位置,并通过hash算法来控制集合的代销;对于HashMap,Hashtable及其子类而言,他们采用hash算法来决定Map中key的存储,并通过hash算法
设置不可变集合
public class NoModifyCollections {
//不可变对象都不可以增加和删除操作
public static void main(String[] args) {
//创建一个空的不可变的List对象
List emptyList = Collections.emptyList();
//创建一个还有一个元素,且不可改变的Set对象
Set singleton = Collections.singleton("java");
HashMap map = new HashMap<>();
map.put("", "");
//返回普通Map对象对应的不可变版本
Map map2 = Collections.unmodifiableMap(map);
}
}
Java9 增加的不可变集合
public class NoModifyCollections9 {
public static void main(String[] args) {
List of = List.of("");
Set of2 = Set.of("");
Map of3 = Map.of("1","2","3","4");
System.out.println(of3);//{3=4, 1=2}
Map ofEntries = Map.ofEntries(Map.entry("", ""));
System.out.println(ofEntries);//{=}
}
}
繁琐的接口Enumeration
繁琐的接口Enumeration:只能遍历Vector和Hashtable这种老java类
public class EnumerationTeST {
public static void main(String[] args) {
Vector vector = new Vector();
vector.add("3");
vector.add("2");
vector.add("1");
Enumeration elements = vector.elements();
while (elements.hasMoreElements()) {
System.out.println(elements.nextElement()); // 3 2 1
}
}
}