一、Map 接口
接下来讲的都是基于 jdk8 来开展的。
1.1 特点
1、Map 与 Collection 并列存在。Map 是用于保存具有映射关系的数据,即 key-value。
2、Map 中的 key 和 value 可以是任何引用类型的数据类型。
3、Map 中的 key 不允许重复,原因和 HashSet 一样。
4、Map 中的 value 是可以重复的。
5、Map 中的 key 可以为 null,value 也可以为 null,注意 key 为 null 时只能有一个,value 为 null 时可以有多个。
6、常用 String 类作为 Map 的 key
7、key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对于的 value。
8、Map 存放数据的 key-value 示意图,一对 key-value 是放在一个 Node 中的,又因为 Node 实现了 Entry 接口,也有人说是一对 key-value 就是一个 Entry
1.2 常用实现类
HashMap、HashTable、SortedMap(接口)、TreeMap 、LinkedHashMap、Properties 等。
1.3 常用方法
public class TestMap {
public static void main(String[] args) {
Map map = new HashMap<>();
// put 添加元素
map.put("孙悟空","唐僧");
map.put("孙悟空","猪八戒");
map.put("宋江","潘金莲");
map.put("武大郎","西门庆");
map.put("曹操",null);
map.put(null,"荀彧");
// get 根据 key 获取元素
Object o = map.get("武大郎");
// 根据 key 删除元素
map.remove("宋江");
// 判断 key 是否存在
boolean b = map.containsKey(null);
System.out.println(b);
// 获取 map 的元素个数
System.out.println(map.size());
// 判断 map 元素个数是否为 0
System.out.println(map.isEmpty());
// 清除 map
map.clear();
}
}
1.4 遍历方式
1.4.1 keySet 方式
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("孙悟空","唐僧");
map.put("武大郎","西门庆");
// 先取出所有的 key,然后再取出 value
Set keySet = map.keySet();
// (1) 使用增强 for 循环
for (Object obj :keySet) {
System.out.println(obj+"-"+map.get(obj));
}
System.out.println("-----------------------");
// (2) 迭代器
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next+"-"+map.get(next));
}
}
1.4.2 value 值方式
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("孙悟空","唐僧");
map.put("武大郎","西门庆");
// 只能取出所有的 values
Collection values = map.values();
// (1) 增强 for 循环
for (Object obj :values) {
System.out.println(obj);
}
System.out.println("-----------------------");
//(2)迭代器
Iterator iterator1 = values.iterator();
while(iterator1.hasNext()){
Object next = iterator1.next();
System.out.println(next);
}
}
1.4.3 EntrySet 方式
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("孙悟空","唐僧");
map.put("武大郎","西门庆");
Set set = map.entrySet();
// (1) 增强 for 循环
for (Object entry :set) {
// 将 entry 转换成 Map.Entry
Map.Entry m = (Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
System.out.println("-----------------------");
// (2) 迭代器
Iterator iterator2 = set.iterator();
while (iterator2.hasNext()) {
Map.Entry m= (Map.Entry) iterator2.next();
System.out.println(m.getKey()+"-"+m.getValue());
}
}
1.5 习题练习
使用 HashMap 添加 3 个员工对象,要求:键为员工 id,值为员工对象。并遍历显示工资 > 18000 员工(遍历方式最少两种)。员工类:姓名、工资、员工 id。
class Employee{
private String id;
private String name;
private double salary;
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// setter、getter、toString
@Override
public String toString() {
return "Employee{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
public static void main(String[] args) {
Map map = new HashMap();
Employee e1 = new Employee("1","张三",20000);
Employee e2 = new Employee("2","李四",8000);
Employee e3 = new Employee("3","王五",60000);
map.put(e1.getId(),e1);
map.put(e2.getId(),e2);
map.put(e3.getId(),e3);
Set set = map.keySet();
for (Object key :set) {
Employee employee = (Employee)map.get(key);
if(employee.getSalary()>18000){
System.out.println(key+"-"+employee);
}
}
System.out.println("--------------");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
Employee employee = (Employee)map.get(key);
if(employee.getSalary()>18000){
System.out.println(key+"-"+employee);
}
}
System.out.println("--------------");
Set set1 = map.entrySet();
for (Object entrySet :set1) {
Map.Entry entry = (Map.Entry)entrySet;
Employee employee = (Employee) entry.getValue();
if(employee.getSalary()>18000){
System.out.println(entry.getKey()+"-"+employee);
}
}
}
二、HashMap
2.1 特点
1、HashMap 是 Map 接口使用频率最高的实现类
2、HashMap 是以 key-value 对的形式存储数据(HashMap$Node 类型)
3、 key 不能重复,但是值可以重复,允许使用 null 键和 null 值
4、如果添加相同的 key ,则会覆盖原来的 key-value ,等同于修改(key 不会替换,value 会替换)
5、与 HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式存储的(jdk8 的HashMap 底层为 数组+链表+红黑树)
6、HashMap 没有实现同步,因此是线程不安全的。方法没有做同步互斥的操作,没有 synchronized。
2.2 底层机制
1、无论是数组里面、链表里面还是红黑树里面存储的 key-value 元素,都是 HashMap$Node 的对象,而 HashMap$Node 类实现了 HashMap$Entry 接口
2、jdk1.7 的 HashMap 底层实现为 数组+链表 的形式,jdk1.8 的 HashMap 底层实现为 数组+链表+红黑树
3、jdk1.7 的 HashMap 当给链表添加元素的时候,它添加到链表的头部。而 jdk1.8 的 HashMap 当给链表添加元素的时候,它会添加到链表的尾部。
2.3 扩容机制
扩容机制和 HashSet 完全一致。
1、HashMap 底层维护了 Node 类型的数组 table,默认为 null
2、当创建对象的时候,将加载因子初始化为 0.75
3、当添加 key-value 时,通过 key 的哈希值得到在 table 的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的 key 和准备加入的 key 是否相等,如果相等,则直接替换 value;如果不相等则需要判断是树结构还是链表结构,做出相应处理,如果添加时发现容量不够,则需要扩容。
4、第一次添加,则需要扩容 table 容量为 16,临界值为 12(16*0.75)
5、以后再扩容,则需要扩容 table 容量为原来的 2 倍,临界值也为原来的 2 倍,即 24,依此类推。
6、在 java8 中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD (默认是 8 ) 并且 table 数组的大小 >= MIN_TREEIF_CAPACITY(默认是 64),就会进行树化,否则仍然采用数组的扩容机制。
三、Hashtable
3.1 特点
1、Hashtable 的键和值都不能为 null,否则会抛空指针异常。
2、Hashtable 的使用方法基本和 HashMap 一样。
3、Hashtable 是线程安全的,HashMap 是线程不安全的。
3.2 底层机制
1、底层是数组,类型为 Hashtabl$Entry,初始大小为 11
2、临界值为 11*0.75 = 8,加载因子也是 0.75
3.3 扩容机制
初始容量为 11,当容量到达 8 个的时候,就会触发扩容机制,第二次扩容之后的大小为 23,所以它的扩容机制为:2n+1,n 为现在的容量。
3.4 Hashtable 和 HashMap 对比
版本 | 线程安全 | 效率 | 允许 null 键和 null 值 | |
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hashtable | 1.0 | 安全 | 较低 | 不可以 |
四、Properties
4.1 特点
1、Properties 类继承自 Hashtable 类并实现了 Map 接口,也是使用一种键值对的形式来保存数据。
2、它的使用特点和 Hashtable 类似,不能存放 null 键和 null 值
3、Properties 类还可以从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行读取和修改。
4、在工作中经常使用 xxx.properties 文件作为配置文件。
4.2 常用方法
public class PropertiesTest {
public static void main(String[] args) {
Properties properties = new Properties();
// 新增
properties.put("name","zhangSan");
// properties.put(null,"zhangSan"); 抛出空指针异常
// properties.put("age",null);抛出空指针异常
properties.put("age",20);
properties.put("address","北京市");
// 修改
properties.put("address","天津市");
// 查看
System.out.println(properties.get("age"));
// 删除
System.out.println(properties.remove("name"));
System.out.println(properties);
}
}
五、TreeMap
5.1 特点
5.1.1 无参构造
当我们使用无参构造器创建 TreeMap 时,属于自然排序,当进行去重操作时,会调用 key 默认实现的 Comparable 类的 compareTo() 方法进行去重。
public class TreeMapTest {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap();
// 使用默认的无参构造器创建 TreeMap,是无序的
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("kui","亏瑞斯");
treeMap.put("smith","史密斯");
System.out.println("TreeMap="+treeMap);
}
}
5.1.2 有参构造
TreeMap 提供了一个有参的构造器,可以传入一个比较器(匿名内部类)并指定排序规则对 key 进行排序。
public class TreeMapTest {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 按照长度大小排序
return ((String)o1).length()-((String)o2).length();
}
});
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("kuisijf","亏瑞斯");
treeMap.put("smith","史密斯");
System.out.println("TreeMap="+treeMap);
}
}
5.2 底层机制
1、有参构造器把实现了 Comparator 接口的匿名内部类对象传送给 TreeMap 的 comparator 属性。
2、调用 put 方法,第一次添加的时候,把 key-value 封装到 Entry 对象,放入 root 中。以后的添加,每次都遍历所有的 key,调用匿名内部类的 compare() 方法进行比较。 compare() 方法执行完毕后会返回一个 int 类型的结果,若结果小于 0 ,则该元素放在比较元素左边;若结果大于 0,则该元素放在比较元素右边;若结果等于 0,数据不添加进去。
5.3 习题演练
public class TreeMapTest {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 按照长度大小排序
return ((String)o1).length()-((String)o2).length();
}
});
treeMap.put("jack","杰克");
treeMap.put("tom","汤姆");
treeMap.put("kuisijf","亏瑞斯");
treeMap.put("smith","史密斯");
treeMap.put("xhf","许海峰");// 加不进去,因为他的长度和 tom 一样
System.out.println("TreeMap="+treeMap);
}
}
六、ConcurrentHashMap
6.1 特点
线程安全,在多线程的条件下,效率比 HashTable 高一些。
6.2 底层机制
添加元素的时候,把这个容器分成了 16 段,每一次往里面插得时候,只锁定 16 段中的一个(16 是比较通用的,可以配),就是把这个锁给细化了。
两个线程就可以同时并发的往里面插数据,就不需要锁定整个对象了。关键在于使用了锁分离技术。它使用了多个锁来控制对 hash 表的不同部分进行的修改。
ConcurrentHashMap 内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的 Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。大量的利用了 volatile,final,CAS 等 lock-free 技术来减少锁竞争对于性能的影响。
七、总结
八、Collections 常用方法介绍
8.1、简介
Collections 是一个操作 Set、List 和 Map 等集合的工具类,它提供了一系列静态的方法对集合中的元素进行排序、查询和修改等操作。
8.2、操作方法
8.2.1 排序操作
public class CollectionsTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
// 反转 list 中的元素
Collections.reverse(list);
System.out.println("list="+list);
// 对 list 中的元素进行随机排序
Collections.shuffle(list);
System.out.println("list="+list);
// 对 list 中的元素进行自然排序,就是按照字母的顺序排序
Collections.sort(list);
System.out.println("自然排序后"+list);
// 可以自定义指定排序方法
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("指定排序后"+list);
// 将 list 集合中的 i 位置元素和 j 位置元素进行交换
Collections.swap(list,0,1);
System.out.println("交换后的情况:"+list);
}
}
8.2.2 查找替换
public class CollectionsTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
// 根据元素的自然排序,返回给定集合中的最大元素
Collections.max(list);
// 根据 Comparator 指定的顺序,返回给定集合中的最大元素
Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 返回长度最大的元素
return ((String)o1).length()-((String)o2).length();
}
});
// 根据元素的自然排序,返回给定集合中的最小元素
Collections.min(list);
// 根据 Comparator 指定的顺序,返回给定集合中的最小元素
Collections.min(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 返回长度小的元素
return ((String)o1).length()-((String)o2).length();
}
});
// 返回指定集合中指定元素的出现次数
int count = Collections.frequency(list,"tom");
// 将 list 中的元素复制到 dest 集合中
ArrayList<Object> dest = new ArrayList<>();
// 为了完成一个完整的拷贝,我们需要先给 dest 赋值,大小和 list.size() 一样
for(int i=0;i<list.size();i++){
dest.add("");
}
Collections.copy(dest,list);
System.out.println("dest="+dest);
// 使用 newTom 替换 list 集合中的 tom
Collections.replaceAll(list,"tom","newTom");
}
}