1、Map接口及其多个实现类的对比
import org.junit.Test;import java.util.HashMap;import java.util.Map;/*** 一、Map的实现类的结构:* |----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)* |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value* |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。* 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。* 对于频繁的遍历操作,此类执行效率高于HashMap。* |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序* 底层使用红黑树* |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value* |----Properties:常用来处理配置文件。key和value都是String类型*** HashMap的底层:数组+链表 (jdk7及之前)* 数组+链表+红黑树 (jdk 8)** 面试题:* 1. HashMap的底层实现原理?* 2. HashMap 和 Hashtable的异同?* 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)**/public class MapTest {@Testpublic void test(){Map map = new HashMap();// map = new Hashtable();map.put(null,123);}}
2、Map中存储的key-value的特点
- Map与Collection并列存在。用于保存具有映射关系的数据:key-value
- Map中的key和value都可以是任何引用类型的数据
- Map 中的key 用Set来存放,不允许重复,即同一个Map 对象所对应的类,须重写hashCode()和equals()方法
- 常用String类作为Map的“键”
- key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value
- Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map接口使用频率最高的实现类
```java
/**
- 二、Map结构的理解:
- Map中的key:无序的、不可重复的,使用Set存储所有的key —-> key所在的类要重写equals()和hashCode() (以HashMap为例)
- Map中的value:无序的、可重复的,使用Collection存储所有的value —->value所在的类要重写equals()
- 一个键值对:key-value构成了一个Entry对象。
- Map中的entry:无序的、不可重复的,使用Set存储所有的entry /
<a name="mPhDm"></a># 3、Map实现类之一:HashMap- **HashMap是Map接口使用频率最高的实现类**- 允许使用null键和null值,与HashSet一样,不保证映射的顺序- 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()- 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()- 一个key-value构成一个entry- 所有的entry构成的集合是Set:无序的、不可重复的- HashMap判断两个key相等的标准是:两个key通过equals()方法返回true,hashCode值也相等。- HashMap判断两个value相等的标准是:两个value 通过equals()方法返回true。<a name="LSduu"></a>## 3.1、HashMap的底层实现原理**JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)**<br />**JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。**<br /><br /><br />HashMap源码中的重要常量```java/** DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16* DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75* threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12* TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8* MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64*/
3.1.1、HashMap在JDK7中的底层原理实现
- HashMap的内部存储结构其实是数组加链表的结合。当实例化完成一个HashMap时,系统会创建一个长度为16的Entry类型的数组,这个长度的在哈希表中被称为容量,这个数组中可存放的元素的位置我们称之为桶(bucket),每个桶都有自己的索引,系统会根据索引快速查找桶中的元素
- 每个bucket存储一个元素,即一个Entry对象,但是每一个对象可以带有一个引用变量,用于指向下一个元素 ,因此在一个桶中,就可能生出一个Entry链。而且新添加的元素作为链表的head
- 添加元素的过程
- 向HashMap中添加entry1(key,value),首先需要计算entry1中key的哈希值(根据key 所在类中的hashCode()计算得到),此哈希值经过一些算法处理以后得到底层Entry[]数组中要存的位置i.
- 如果此位置上没有数据,那么entry1元素就添加成功
- 如果此位置上已经存在数据entry2(或还有链表的存在entry3,entry4),则需要再次通过比较entry1中key的hash值和其他的entry的hash值。
- 如果彼此哈希值不同,则直接添加成功
- 如果哈希值相同,则需要继续通过equals()方法判断是否相同,如果不相同,则添加成功
- 如果相同,则将原有数据的value进行替换
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
/** 三、HashMap的底层实现原理?以jdk7为例说明:* HashMap map = new HashMap():* 在实例化以后,底层创建了长度是16的一维数组Entry[] table。* ...可能已经执行过多次put...* map.put(key1,value1):* 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。* 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1* 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据* 的哈希值:* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2* 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:* 如果equals()返回false:此时key1-value1添加成功。----情况3* 如果equals()返回true:使用value1替换value2。** 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。** 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。**//*** HashMap的扩容* 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,* 因为数组的长度是固定的。所以为了提高查询的效率,* 就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,* 最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,* 并放进去,这就是resize。** 那么HashMap什么时候进行扩容呢?* 当HashMap中的元素个数超过数组大小(数组总大小length,* 不是数组中个数size)*loadFactor时,就 会 进 行 数 组 扩 容,* loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。* 也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,* 那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,* 也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,* 然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,* 所以如果我们已经预知HashMap中元素的个数,* 那么预设元素的个数能够有效的提高HashMap的性能。*/
3.3.2、HashMap在JDK8中的底层实现原理
- 在jdk8后,创建hashMap实例后,底层并没有去创建一个长度为16的数组,而是等到首次进行put()方法是底层在去创建一个长度为16的数组
- jdk8底层的数组是Node[]类型,而非Entry[]
- jdk7底层结构只有数组+链表,jdk8 中的底层是 数组 + 链表 + 红黑树
- 当数组的某一个索引位置上的元素以链表形式存在数据个数大于8 并且当前数组的长度大于64时,此时索引上位置上的所有数据都改为使用红黑树进行存储
4、Map集合中的常用方法
```java import org.junit.Test;
import java.util.; /*
- 五、Map中定义的方法:
- 添加、删除、修改操作:
- Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
- void putAll(Map m):将m中的所有key-value对存放到当前map中
- Object remove(Object key):移除指定key的key-value对,并返回value
- void clear():清空当前map中的所有数据
- 元素查询的操作:
- Object get(Object key):获取指定key对应的value
- boolean containsKey(Object key):是否包含指定的key
- boolean containsValue(Object value):是否包含指定的value
- int size():返回map中key-value对的个数
- boolean isEmpty():判断当前map是否为空
- boolean equals(Object obj):判断当前map和参数对象obj是否相等
- 元视图操作的方法:
- Set keySet():返回所有key构成的Set集合
- Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合 / public class MapTest {
/**
- 元素查询的操作:
- Object get(Object key):获取指定key对应的value
- boolean containsKey(Object key):是否包含指定的key
- boolean containsValue(Object value):是否包含指定的value
- int size():返回map中key-value对的个数
- boolean isEmpty():判断当前map是否为空
- boolean equals(Object obj):判断当前map和参数对象obj是否相等 */ @Test public void test4(){ Map map = new HashMap(); map.put(“AA”,123); map.put(45,123); map.put(“BB”,56); // Object get(Object key) System.out.println(map.get(45)); //containsKey(Object key) boolean isExist = map.containsKey(“BB”); System.out.println(isExist);
isExist = map.containsValue(123); System.out.println(isExist);
map.clear();
System.out.println(map.isEmpty()); }
/**
- 添加、删除、修改操作:
- Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
- void putAll(Map m):将m中的所有key-value对存放到当前map中
- Object remove(Object key):移除指定key的key-value对,并返回value
- void clear():清空当前map中的所有数据 */ @Test public void test3(){ Map map = new HashMap(); //添加 map.put(“AA”,123); map.put(45,123); map.put(“BB”,56); //修改 map.put(“AA”,87);
System.out.println(map);
Map map1 = new HashMap(); map1.put(“CC”,123); map1.put(“DD”,456); map.putAll(map1);
System.out.println(map);
//remove(Object key) Object value = map.remove(“CC”); System.out.println(value); System.out.println(map);
//clear() map.clear();//与map = null操作不同 System.out.println(map.size()); System.out.println(map); } }
```javaimport org.junit.Test;import java.util.*;/*** 五、Map中定义的方法:* 添加、删除、修改操作:* Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中* void putAll(Map m):将m中的所有key-value对存放到当前map中* Object remove(Object key):移除指定key的key-value对,并返回value* void clear():清空当前map中的所有数据* 元素查询的操作:* Object get(Object key):获取指定key对应的value* boolean containsKey(Object key):是否包含指定的key* boolean containsValue(Object value):是否包含指定的value* int size():返回map中key-value对的个数* boolean isEmpty():判断当前map是否为空* boolean equals(Object obj):判断当前map和参数对象obj是否相等* 元视图操作的方法:* Set keySet():返回所有key构成的Set集合* Collection values():返回所有value构成的Collection集合* Set entrySet():返回所有key-value对构成的Set集合** 总结:常用方法:* 添加:put(Object key,Object value)* 删除:remove(Object key)* 修改:put(Object key,Object value)* 查询:get(Object key)* 长度:size()* 遍历:keySet() / values() / entrySet()** 面试题:* 1. HashMap的底层实现原理?* 2. HashMap 和 Hashtable的异同?* 1.HashMap与Hashtable都实现了Map接口。由于HashMap的非线程安全性,效率上可能高于Hashtable。Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。* 2.HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。* 3.HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。* 4.Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。* 5.Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。** 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)**/public class MapTest {/*** 元视图操作的方法:* Set keySet():返回所有key构成的Set集合* Collection values():返回所有value构成的Collection集合* Set entrySet():返回所有key-value对构成的Set集合*/@Testpublic void test5(){Map map = new HashMap();map.put("AA",123);map.put(45,1234);map.put("BB",56);//遍历所有的key集:keySet()Set set = map.keySet();Iterator iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println("*****************");//遍历所有的values集:values()Collection values = map.values();for(Object obj : values){System.out.println(obj);}System.out.println("***************");//遍历所有的key-values//方式一:Set entrySet = map.entrySet();Iterator iterator1 = entrySet.iterator();while (iterator1.hasNext()){Object obj = iterator1.next();//entrySet集合中的元素都是entryMap.Entry entry = (Map.Entry) obj;System.out.println(entry.getKey() + "---->" + entry.getValue());}System.out.println("/");//方式二:Set keySet = map.keySet();Iterator iterator2 = keySet.iterator();while(iterator2.hasNext()){Object key = iterator2.next();Object value = map.get(key);System.out.println(key + "=====" + value);}}}
5、TreeMap两种添加方式的使用
- TreeMap存储Key-Value 对时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value 对处于有序状态。
- TreeSet底层使用红黑树结构存储数据
- TreeMap的Key的排序
- 自然排序:TreeMap的所有的Key 必须实现Comparable接口,而且所有的Key应该是同一个类的对象,否则将会抛出ClasssCastException
- 定制排序:创建TreeMap时,传入一个Comparator 对象,该对象负责对TreeMap中的所有key 进行排序。此时不需要Map 的Key实现Comparable 接口
TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0
public class User implements Comparable{private String name;private int age;public User() {}public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {System.out.println("User equals()....");if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;if (age != user.age) return false;return name != null ? name.equals(user.name) : user.name == null;}@Overridepublic int hashCode() { //return name.hashCode() + age;int result = name != null ? name.hashCode() : 0;result = 31 * result + age;return result;}//按照姓名从大到小排列,年龄从小到大排列@Overridepublic int compareTo(Object o) {if(o instanceof User){User user = (User)o;// return -this.name.compareTo(user.name);int compare = -this.name.compareTo(user.name);if(compare != 0){return compare;}else{return Integer.compare(this.age,user.age);}}else{throw new RuntimeException("输入的类型不匹配");}}}
```java import org.junit.Test;
import java.util.*;
public class TreeMapTest {
/*** 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象* 因为要按照key进行排序:自然排序 、定制排序*///自然排序@Testpublic void test(){TreeMap map = new TreeMap();User u1 = new User("Tom",23);User u2 = new User("Jerry",32);User u3 = new User("Jack",20);User u4 = new User("Rose",18);map.put(u1,98);map.put(u2,89);map.put(u3,76);map.put(u4,100);Set entrySet = map.entrySet();Iterator iterator1 = entrySet.iterator();while (iterator1.hasNext()){Object obj = iterator1.next();Map.Entry entry = (Map.Entry) obj;System.out.println(entry.getKey() + "---->" + entry.getValue());}}//定制排序@Testpublic void test2(){TreeMap map = new TreeMap(new Comparator() {@Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof User && o2 instanceof User){User u1 = (User)o1;User u2 = (User)o2;return Integer.compare(u1.getAge(),u2.getAge());}throw new RuntimeException("输入的类型不匹配!");}});User u1 = new User("Tom",23);User u2 = new User("Jerry",32);User u3 = new User("Jack",20);User u4 = new User("Rose",18);map.put(u1,98);map.put(u2,89);map.put(u3,76);map.put(u4,100);Set entrySet = map.entrySet();Iterator iterator1 = entrySet.iterator();while (iterator1.hasNext()){Object obj = iterator1.next();Map.Entry entry = (Map.Entry) obj;System.out.println(entry.getKey() + "---->" + entry.getValue());}}
}
<a name="OLL9l"></a># 6、Hashtable- Hashtable是个古老的Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。- 与HashMap不同,**Hashtable不允许使用null 作为key和value**- 与HashMap一样,Hashtable也不能保证其中Key-Value 对的顺序- Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。<a name="crxPw"></a># 7、Properties处理属性文件- Properties 类是Hashtable的子类,该对象用于处理属性文件- 由于属性文件里的key、value都是字符串类型,所以**Properties 里的key和value都是字符串类型**- 存取数据时,建议使用setProperty(String key,Stringvalue)方法和getProperty(String key)方法```javaimport java.io.FileInputStream;import java.io.IOException;import java.util.Properties;public class PropertiesTest {//Properties:常用来处理配置文件。key和value都是String类型public static void main(String[] args){//快捷键:ALT+Shift+ZFileInputStream fis = null;try {Properties pros = new Properties();fis = new FileInputStream("jdbc.properties");pros.load(fis); //加载流对应文件String name = pros.getProperty("name");String password = pros.getProperty("password");System.out.println("name = " + name + ",password = " + password);} catch (IOException e) {e.printStackTrace();} finally {if(fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}
