一、Map
1、Map与Collection都是集合框架中的顶层接口。Map集合一次添加一对元素,也称为双列集合(Collection集合一次添加一个元素,也称为单列集合)
2、interface Map<K, V>:将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值。即 Map中存储的是键值对,且必须保证键的唯一性
3、Map接口提供三种collection视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容
4、所有通用的映射实现类应该提供两个“标准的”构造方法:一个void(无参数)构造方法,用于创建空映射;一个是带有单个Map类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射
5、嵌套类
(1)static interface Map.Entry<K, V>:映射项(键-值对)
6、方法
(1)添加
V put(K key, V value):将指定的值与此映射中的指定键关联。如果此映射以前包含一个该键的映射关系,则用指定值替换旧值(当且仅当m.containsKey(k)返回true时,才能说映射m包含键k的映射关系)。否则,返回null
void putAll(Map<? extends K, ? extends V> m):从指定映射中将所有映射关系复制到此映射中。对于指定映射中的每个键k到值v的映射关系,此调用等效于对此映射调用一次put(k, v)。如果正在进行此操作的同时修改了指定的映射,则此操作的行为是不确定的
(2)删除
void clear():从此映射中移除所有映射关系。此调用返回后,该映射将为空(清空Map集合)
V remove(Object key):如果存在一个键的映射关系,则将其从此映射中移除(根据指定的key删除这个键值对)。返回此映射中以前关联该键的值,如果此映射不包含该键的映射关系,则返回null
(3)判断
boolean containsKey(Object key):如果此映射包含指定键的映射关系,则返回true
boolean containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回true
boolean isEmpty():如果此映射未包含键-值映射关系,则返回true
(4)获取
V get(Object key):返回指定键所映射的值。如果此映射不包含该键的映射关系,则返回null
注:如果此映射允许null值,则返回null值并不一定表示该映射不包含该键的映射关系。也可能该映射将该键显示地映射到null。使用containsKey(key)操作可区分这两种情况
int size():返回此映射中的键-值映射关系数(键值对的个数)
(5)其他
Set<K> keySet():返回此映射中包含的键的Set视图
Set<Map.Entry<K, V>> entrySet():返回此映射中包含的映射关系的Set视图
Collection<V> values():返回此映射中包含的值的Collection视图。用Collection而不用Set是因为值不一定唯一
//Map有泛型,Map<Integer, String>:学号和姓名
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(8, "wangcai"); //null
map.put(8, "xiaoqi"); //wangcai,存相同键,值会被覆盖,返回旧值
System.out.println(map); //{8=xiaoqi},键和值,用等号(=)连接,表示二者有关系
map.put(2, "zhangsan");
map.put(7, "zhaoliu");
System.out.println(map); //{2=zhangsan, 7=zhaoliu, 8=xiaoqi},无序,因为new的是HashMap
//删除元素
map.remove(2); //zhangsan
System.out.println(map); //{7=zhaoliu, 8=xiaoqi}
//判断
map.containsKey(8); //true
//获取
map.get(8); //xiaoqi
map.get(6); //null
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(8, "wangwu");
map.put(2, "zhaoliu");
//键是唯一的,值不一定唯一,所以不能用Set,而是用Collection
//拿到的全是值
Collection<String> coll = map.values();
for (Iterator<String> it = coll.iterator(); it.hasNext(); ) {
String value = it.next();
System.out.println(value);
}
二、keySet()
1、获取Map中的所有元素,方法之一:先拿到所有键,再遍历所有键,每次遍历通过get(key)方法拿到值
注:Map中没有迭代器。使用Set而不用List,是为了保证元素的唯一性
2、Set<K> keySet():返回此映射中包含的键的Set视图
3、Map<K, V> --> keySet() --> Set<K> --> iterator() --> key --> get(key) --> value
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(8, "wangwu");
map.put(2, "zhaoliu");
/**
* 获取Map中的所有元素
* 方法一:
* (1)先通过keySet()方法获取Map中的所有键的Set集合
* (2)再通过Set迭代器获取到每个键
* (3)最后通过map集合的get(key)方法,获取每个键对应的值
*/
Set<Integer> keySet = map.keySet();
//Set有迭代器,而Map没有迭代器
for (Iterator<Integer> it = keySet.iterator(); it.hasNext(); ) {
//键
Integer key = it.next();
//值。get(key)方法每次只能获取一个值
String value = map.get(key);
System.out.println(key + ": " + value);
}
三、entrySet()
1、获取Map中的所有元素,方法之二:通过将Map集合的键和值的映射关系作为对象存储到Set集合中,从而实现将Map转换成Set。遍历取出每个映射关系,再获取该映射关系中的键和值
注:Map中没有迭代器。使用Set而不用List,是为了保证元素的唯一性
2、Set<Map.Entry<K, V>> entrySet():返回此映射中包含的映射关系的Set视图
3、Map<K, V> --> entrySet() --> Set<Map.Entry<K, V>> --> iterator() --> Map.Entry<K, V> --> Map.Entry中的方法getKey()、getValue() --> key、value
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(8, "wangwu");
map.put(2, "zhaoliu");
/**
* 获取Map中的所有元素
* 方法二:
* (1)先通过entrySet()方法将Map中的键和值的映射关系存储到Set中
* (2)再通过Set迭代器获取每个映射关系
* (3)最后通过Map.Entry的getKey()、getValue()方法获取每个映射中的键、值
*/
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
//Set有迭代器,而Map没有迭代器
//迭代器中的泛型与Set中的一致
for (Iterator<Map.Entry<Integer, String>> it = entrySet.iterator(); it.hasNext(); ) {
//键-值映射关系(取出的是Set中的内容)
Map.Entry<Integer, String> me = it.next();
//使用Map.Entry的getKey()获取键
Integer key = me.getKey();
//使用Map.Entry的getValue()获取值
String value = me.getValue();
System.out.println(key + ": " + value);
}
4、HashMap中,可以根据key快速取到value,除了和HashMap的数据结构密不可分,还和Entry有关。HashMap在存储过程中,并没有将key、value分开来存储,而是当做一个整体key-value来处理的,这个整体就是Entry对象。在存储的过程中,系统根据key的hashCode来决定Entry在table数组中的存储位置。在取的过程中,同样根据key的hashCode取出相对应的Entry对象
四、Map.Entry
1、public static interface Map.Entry<K, V>:映射项(键-值对)。Map.entrySet()方法返回映射的collection视图,其中的元素属于此类。获得映射项引用的唯一方法是通过此collection视图的迭代器来实现。这些Map.Entry对象仅在迭代期间有效。更确切地讲,如果在迭代器返回项之后修改了底层映射,则某些映射项的行为是不确定的,除了通过setValue()在映射项上执行操作之外
2、public static interface Map.Entry<K, V>:内部接口(接口中的静态接口),随着外部接口的加载而加载
注:Entry是键和值的映射关系对象,只有先有Map映射,才存在关系Entry。而Entry把关系封装成对象,该关系也在访问Map中的键和值。即 外部规则中有内部规则,内部规则在直接访问外部规则中的内容
3、方法
(1)K getKey():返回与此项对应的键
(2)V getValue():返回与此项对应的值。如果已经从底层映射中移除了映射关系(通过迭代器的remove()操作),则此调用的结果是不确定的
(3)V setValue(V value):用指定的值替换与此项对应的值(写入该映射)。如果已经从映射中移除了映射关系(通过迭代器的remove()操作),则此调用的行为是不确定的
五、Map子类的特点
1、Hashtable:内部结构是哈希表,是同步的(效率低)。不允许null作为键、null作为值
注:JDK1.0出现此类,单列集合是Vector,双列集合是Hashtable
2、HashMap:内部结构是哈希表(数组+单链表),是不同步的(效率高)。允许null作为键、null作为值
注:
(1)HashMap可以允许存在一个为null的key和任意个为null的value
(2)HashSet:此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。Set集合的底层代码由Map实现 -- 值统一,操作键
(3)哈希表的数据结构如下图
3、TreeMap:内部结构是二叉树(红-黑树),是不同步的。可以对Map集合中的键进行排序
注:LinkedHashMap:有序(存、取顺序一致)
六、Hashtable
1、此类实现一个哈希表,该哈希表将键映射到相应的值。任何非null对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现hashCode()和equals()方法
七、Properties
1、Properties:Hashtable的直接已知子类,用来存储键值对型的配置文件信息(可以和IO技术相结合)
2、Properties类表示了一个持久的属性集。Properties可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。一个属性列表可包含另一个属性列表作为它的“默认值”。如果未能在原有的属性列表中搜索到属性键,则搜索第二个属性列表
3、因为Properties继承于Hashtable,所以可对Properties对象应用put()和putAll()方法。但不建议使用这两个方法,因为它们允许调用者插入其键或值不是String的项。相反,应该使用setProperty()方法。如果在“不安全”的Properties对象(即包含非String的键或值)上调用store()或save()方法,则该调用将失败。类似地,如果在“不安全”的Properties对象(即包含非String的键)上调用propertyNames()或list()方法,则该调用将失败
4、Properties类是线程安全的:多个线程可以共享单个Properties对象而无需进行外部同步
5、字段
(1)protected Properties defaults:一个属性列表,包含属性列表中所有未找到值的键的默认值
6、构造函数
(1)Properties():创建一个无默认值的空属性列表
(2)Properties(Properties defaults):创建一个带有指定默认值的空属性列表
7、方法
(1)String getProperty(String key):用指定的键在此属性列表中搜索属性。如果在此属性列表中未找到该键,则接着递归检查默认属性列表及其默认值。如果未找到属性,则此方法返回null
(2)String getProperty(String key, String defaultValue):用指定的键在属性列表中搜索属性。如果在属性列表中未找到该键,则接着递归检查默认属性列表及其默认值。如果未找到属性,则此方法返回默认值变量defaultValue
(3)void list(PrintStream out):将属性列表输出到指定的输出流。此方法对调试很有用
(4)void list(PrintWriter out):将属性列表输出到指定的输出流。此方法对调试很有用
(5)void load(InputStream inStream) throws IOException:从输入流中读取属性列表(键和元素对)。此方法返回后,指定的流仍保持打开状态
(6)void load(Reader reader) throws IOException:按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)
(7)void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException:将指定输入流中由XML文档所表示的所有属性加载到此属性表中。该XML文档必须具有以下DOCTYPE声明:
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
而且该文档还必须满足上述属性DTD的要求。此方法返回后,指定的流已关闭
(8)Enumeration<?> propertyNames():返回属性列表中所有键的枚举,如果在主属性列表中未找到同名的键,则包括默认属性列表中不同的键
(9)@Deprecated public void save(OutputStream out, String comments):已过时。调用store(OutputStream out, String comments)方法并取消抛出的IOExceptions。即 如果在保存属性列表时发生I/O错误,则此方法不抛出IOException。保存属性列表的首选方法是通过store(OutputStream out, String comments)方法或storeToXML(OutputStream os, String comment)方法来进行
(10)Object setProperty(String key, String value):调用Hashtable的方法put()。使用getProperty()方法提供并行性。强制要求为属性的键和值使用字符串。返回值是Hashtable调用put()的结果(返回属性列表中指定键的旧值,如果没有值,则为null)
(11)void store(OutputStream out, String comments) throws IOException:以适合使用load(InputStream)方法加载到Properties表中的格式,将此Properties表中的属性列表(键和元素对)写入输出流。写入各个项后,刷新输出流。此方法返回后,输出流仍保持打开状态
(12)void store(Writer writer, String comments) throws IOException:以适合使用load(Reader)方法的格式,将此Properties表中的属性列表(键和元素对)写入输出字符。写入各个项后,刷新输出流。此方法返回后,输出流仍保持打开状态
(13)void storeToXML(OutputStream os, String comment) throws IOException:发出一个表示此表中包含的所有属性的XML文档。以props.storeToXML(os, comment)的形式调用此方法的行为与调用props.storeToXML(os, comment, "UTF-8");完全相同
(14)void storeToXML(OutputStream os, String comment, String encoding) throws IOException:使用指定的编码发出一个表示此表中包含的所有属性的XML文档。该XML文档要具有以下DOCTYPE声明:
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
如果指定的注释comment为null,则没有注释存储在该文档中。此方法返回后,指定的流仍保持打开状态
(15)Set<String> stringPropertyNames():返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。其键或值不是String类型的属性被忽略。返回的set不受Properties对象支持。对此Properties的改变不能在该set中反映出来,反之亦然
八、HashMap存储自定义对象
1、在Map中插入、删除和定位元素,HashMap是最好的选择(HashMap通过hashCode()对其内容进行快速查找)
2、HashMap:基于哈希表的Map接口的实现。此实现提供所有可选的映射操作,并允许使用null值和null键(除了非同步和允许使用null之外,HashMap类与Hashtable大致相同)。此类不保证映射的顺序(无序),特别是它不保证该顺序恒久不变
3、此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedMap()方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问
Map m = Collections.synchronizedMap(new HashMap(...));
4、构造方法
(1)HashMap():构造一个具有默认初始容量(16)和默认加载因子(0.75)的空HashMap
(2)HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子(0.75)的空HashMap
(3)HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空HashMap
(4)HashMap(Map<? extends K, ? extends V> m):构造一个映射关系与指定Map相同的新HashMap。所创建的HashMap具有默认加载因子(0.75)和足以容纳指定Map中映射关系的初始容量
注:
(1)初始容量:创建哈希表时,哈希表中桶的数量
(2)加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度。它衡量的是一个散列表的空间的使用程度。负载因子越大,对空间的利用更充分,但查找效率会降低;负载因子太小,散列表的数据将过于稀疏,会对空间造成严重浪费,同时,由于Map频繁的调整大小,可能会降低性能。一般不更改负载因子的值,采用默认值0.75
5、HashMap若想保证键相同,必须要提供键判断相同的依据。哈希表要覆盖hashCode()和equals()两个方法。所以,键对象要覆盖hashCode()和equals()方法,建立自己的比较相同的依据
九、TreeMap存储自定义对象
1、TreeMap implements SortedMap:基于红黑树的NavigableMap实现。该映射根据其键的自然顺序(Comparable)进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法
2、此实现不是同步的。如果多个线程同时访问一个映射,并且其中至少一个线程从结构上修改了该映射,则其必须外部同步。一般是通过对自然封装该映射的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用Collections.synchronizedSortedMap()方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
3、构造函数
(1)TreeMap():使用键的自然顺序构造一个新的、空的树映射。插入该映射的所有键都必须实现Comparable接口。另外,所有这些键都必须是可互相比较的:对于映射中的任意两个键k1和k2,执行k1.compareTo(k2);都不得抛出ClassCastException
(2)TreeMap(Comparator<? super K> comparator):构造一个新的、空的树映射,该映射根据给定比较器进行排序。插入该映射的所有键都必须由给定比较器进行相互比较:对于映射中的任意两个键k1和k2,执行comparator.compare(k1, k2);都不得抛出ClassCastException
(3)TreeMap(Map<? extends K, ? extends V> m):构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序(Comparable)进行排序。插入此新映射的所有键都必须实现Comparable接口。另外,所有这些键都必须是可互相比较的:对于映射中的任意两个键k1和k2,执行k1.compareTo(k2);都不得抛出ClassCastException
(4)TreeMap(SortedMap<K, ? extends V> m):构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。参数m是有序映射,其映射关系将存放在此映射中,并且其比较器将用来对此映射进行排序
4、TreeMap对元素进行排序的两种方式 -- 针对键 :
(1)让元素自身具备比较功能。元素需要实现Comparable接口,覆盖CompareTo()方法 -- 元素的自然排序
(2)让集合自身具备比较功能。定义一个类,实现Comparator接口,覆盖compare()方法。将该类对象(接口Comparator的子类对象)作为参数传递给TreeMap集合的构造函数 -- 比较器
十、练习
1、获取字符串中每一个字母出现的次数。eg:“fdgavcbsacdfs” --> a(2)b(1)c(2)...
/**
* 获取字符串中每一个字母出现的次数。eg:“fdgavcbsacdfs” --> a(2)b(1)c(2)...
* 思路:
* (1)每个字母对应次数,字母和次数之间存在着映射关系
* (2)当映射关系的一方是有序编号时,使用数组。当映射关系的任何一方都不是有序编号时,使用Map集合
* (3)需要保证唯一性的一方(字母)具备着顺序,使用TreeMap
*/
public static String getCharCount(String str) {
//将字符串变成字符数组
char[] chars = str.toCharArray();
//定义Map集合。结果需要按照字母顺序排序,使用TreeMap
Map<Character, Integer> map = new TreeMap<Character, Integer>();
//遍历字符数组
for (int i = 0; i < chars.length; i++) {
//如果只要字母,而指定的字符串中可能包含其他字符,需要做判断
if (!((chars[i] >= 'a' && chars[i] <= 'z') || (chars[i] >= 'A' && chars[i] <= 'Z'))) {
continue;
}
//将数组中的字母作为键查找map
//也可使用containsKey()方法判断是否包含key,再对val进行操作
Integer val = map.get(chars[i]);
//写法一:
// if(val == null){
// //如果该字母不存在,就将该字母作为键,1作为值存储到map中
// map.put(chars[i], 1);
// }else {
// //如果该字母存在,就将该字母键对应的值取出后加1,再将该字母和加1后的值存储到map中
// map.put(chars[i],val+1);
// }
//写法二:
// int count = 1;
// if (val != null) {
// count = val + 1;
// }
// map.put(chars[i], count);
//写法三:
int count = 0;
if (val != null) {
count = val;
}
count++;
map.put(chars[i], count);
}
//遍历结束,map中记录着所有字母出现的次数
//根据指定格式输出
return mapToString(map);
}
/**
* 将map按照指定格式转成字符串
*
* @param map
* @return
*/
private static String mapToString(Map<Character, Integer> map) {
//使用StringBuffer,而不是直接定义String进行拼接(节省内存空间)
StringBuffer sb = new StringBuffer();
Set<Character> ketSet = map.keySet();
for (Iterator<Character> it = ketSet.iterator(); it.hasNext(); ) {
Character key = it.next();
Integer value = map.get(key);
sb.append(key + "(" + value + ")");
}
return sb.toString();
}
2、Map集合在有映射关系时,可以优先考虑。在查表法中的应用较为多见
(1)映射关系的一方是有序的编号时,使用数组
(2)映射关系的任何一方都不是有序编号时,使用Map集合
3、Map不一定需要有序编号,旨在建立对象之间的关系。一个键,其对应的值可以是List/Set集合,集合里面存的是对象
eg:每一个班级,里面都有好多同学 map.put("班级", Set<Student>);