文章目录
- 1、概述
- 2、Map接口中的常用方法
- 3、Map集合的遍历
- 4、哈希表的数据结构
- 5、Map集合的存取
- 6、hashCode()和equals()的重写
- 7、一些小零散的东西
- 8、HashMap和Hashtable的区别
- 9、Properties类
- 10、TreeMap
- 11、自定义类实现Comparable接口
- 12、二叉树
- 13、Comparator接口---比较器
- 14、Collections工具类补充
1、概述
- Map和Collection没有继承关系
- Map集合以key和value的方式存储数据,即键值对,key和value都是引用数据类型,key和value都是存储对象的内存地址。key起主导地位,value是key的一个附属品。
2、Map接口中的常用方法
- V put (K key,V value) 向Map集合中添加键值对
- V get (Object key) 通过key获取value
- V remove(Object key) 通过key删除某键值对
- void clear() 清空Map集合
- boolean containsKey(Object key) 判断Map中是否包含某个key
- boolean containsValue(Object value) 判断Map中是否包含某个value
- boolean isEmpty() 判断Map集合中元素个数是否为0
- Set<k> keySet() 获取Map集合中所有的key,所有的键是一个Set集合
- V remove (Object key) 通过key删除键值对
- int size() 获取Map集合中键值对的个数
- Collection<V> values() 获取Map集合中所有的value,返回一个Collection
- Set<Map,Entry<k,v>> entrySet() 将Map集合转换成Set集合
注:
<V>即泛型+value的类型
<K>即泛型+key的类型
举例:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Map1 {
public static void main(String[] args) {
//即Key为Integer,Value为String
Map<Integer,String> map = new HashMap<>();
//这里的1自动装箱
map.put(1,"A");
map.put(2,"B");
map.put(3,"C");
//"A"
String value = map.get(1);
//3
System.out.println(map.size());
map.remove(2);
//false
System.out.println(map.containsKey(2));
//false
System.out.println(map.containsValue("B"));
//true
System.out.println(map.containsValue(new String("A")));
Collection<String> values = map.values();
//A、C
for(String s:values){
System.out.println(s);
}
Set<Integer> key = map.keySet();
//1、3
for(Integer s:key){
System.out.println(s);
}
map.clear();
//true
boolean e = map.isEmpty();
//Map转Set
Set<Map.Entry<Integer,String>> set = map.entrySet();
}
}
Set<Map.Entry<Integer,String>>中,Map.Entry<Integer,String>也是一种类型,和String一样看待就行,不同的是这里有静态内部类。
//理解一下最后这个Map转Set的类型
import java.util.HashSet;
import java.util.Set;
public class MyClass {
//静态内部类声明
private static class InnerClass{
public static void m1(){
}
public void m2(){
}
}
public static void main(String[] args) {
//调用静态内部类中的静态方法
MyClass.InnerClass.m1();
//创建静态内部类对象
MyClass.InnerClass mi = new MyClass.InnerClass();
mi.m2();
//类比
Set<MyClass.InnerClass> set =new HashSet<>();
//简单的看就和Set<String> set = new HashSet<>();一样
// 就是个类型
}
}
即,Map集合通过entrySet方法转换成Set集合后,Set集合中元素的类型是Map.Entry,再加个泛型<K,V>
//抛开泛型,就明晰很多:
Set set = map.entrySet();
3、Map集合的遍历
遍历方式一:获取所有的key,通过key来遍历value
Set<Integer> mySet = map.keySet();
Iterator<Integer> iterator = mySet.iterator();
//遍历获取Set集合的方式,也可下标和增强for
while(iterator.hasNext()){
Integer myKey = iterator.next();
String myValue = map.get(myKey);
System.out.println("Key:" + myKey + ",value:" + myValue);
}
//增强for
for(Integer myKey:mySet){
System.out.println("Key:" + myKey + ",value:" + map.get(myKey));
}
遍历方式二:把Map集合转化为Set集合
n个Map中的键值对,对应Set中n个Map.Entry<K,V>类型的对象。调用该对象的getKey()、getValue()方法获取原Map的键值
Iterator<Map.Entry<Integer,String>> iterator = set.iterator();
//迭代器遍历
while(iterator.hasNext()){
Map.Entry<Integer,String> node = iterator.next();
Integer myKey = node.getKey();
String myValue = node.getValue();
System.out.println(myKey + "=" + myValue);
}
//foreach
for(Map.Entry<Integer,String> node:set){
System.out.println(node.getKey() + "=" + node.getValue());
}
其中,foreach的遍历效率较高,因为获取key和value都是从node对象中获取属性值,适用与大量数据。
另外,直接打印Set集合也可以输出key=value的形式,应该是Map.Entry类重写了toString吧
Set<Map.Entry<Integer,String>> set = map.entrySet();
for(Map.Entry<Integer,String> s:set){
System.out.println(s);
}
三种方式的运行结果一样:
4、哈希表的数据结构
哈希表是一个数组和单向链表的结合体。数组在查询方面效率很高,而随机增删效率低。单向链表则是随机增删效率高而查询效率低,哈希表则是融合两种数据结构,充分发挥二者的优点。
//HashMap集合底层源码精简:
public class HashMap{
//HashMap底层实际是一个一维数组
Node<K,V>[] table;
//静态的内部类
static class Node<K,V>{
//哈希值,是此处key的hashCode()方法的执行结果
//hash值通过哈希函数可以转换为数组下标
final int hash;
//存到Map中的key
final K key;
//存到Map集合中的value
V value;
//下一个节点的内存地址
Node<K,V> next;
}
}
5、Map集合的存取
v = map.get(k)方法的实现原理
基本思路:
类比字典查”中“字,由zhong先锁定范围,再去一页页的找,而不是从第一页开始一页页的找
实现步骤:
- 先调用k的hashCode()方法获得哈希值,通过哈希算法转化为数组下标,由数组下标确定某位置(像门帘,先确定在哪串珠子)
- 若该位置啥也没有,则返回null;若该位置有单向链表,则拿k和此单项链表上每个节点的k进行equals,如果所有equals都返回false,则get返回null
- 若其中有一个节点的k和参数k进行equals返回true,则get返回这个节点的values
map.put(k,v)方法的实现原理
实现步骤:
- 先将k,v封装到Node对象当中
- 底层调用k的hashCode()方法得出hash值,由哈希算法将hash值转化为数组下标
- 若下标位置没有任何元素,则直接将Node添加上去
- 若下标位置有链表,此时拿k和链表上的每个节点的k进行equals,如果所有的equals均返回false,则将这个新节点添加到链表的末尾;如果有一个equals返回了true,则这个节点的value将被覆盖
从最后一步,也可以看出,HashMap集合的key部分是无序且不可重复。无序即写入时根据k,不一定会挂到哪个链表上(即不是齐刷刷挂过去的),这样写入和读取的时候就是两个顺序。不可重复即由equals和value的覆盖来保证。
复习Tip:
- 放到HashSet集合当中的元素,实际是放到HashMap集合的key部分了。
根据HashMap的结构图,HashMap集合使用不当时,不能发挥其性能。
😉 不当情况1:
所有的hashCode方法返回值均为固定值A,则哈希表变成了纯单向链表,这种情况称散列分布不均匀。形象的说就是全挂一根线上了。
😉 不当情况2:所有的hashCode方法返回值均不同,即数组下标都不同,则哈希表变成了一维数组,也是散列分布不均匀。
6、hashCode()和equals()的重写
根据上面HashMap集合的存取,可知必须重写hashCode和equals方法。需要重写的是hashMap集合的key部分的元素,以及放在HashSet集合中的元素。
- HashMap集合的默认初始化容量是16,默认加载因子是0.75,即当HashMap集合底层数组的容量达到75%时,数组开始扩容
- HashMap集合的初始化容量必须是2的次幂,a power of 2,这样可以提高HashMap集合的存取效率,达到散列均匀
定义一个Student类,并重写equals:
由此:
一个类的equals方法重写了,则hashCode方法也要重写,且"equals方法返回值为true时,hashCode方法的返回值必须一样"。
7、一些小零散的东西
🍁JDK8对HashMap集合的改进:
static final int TREEIFY_THRESHOLD = 8;
即JDK8之后,哈希表单向链表中的元素超过8个时,单向链表的数据结构变为红黑树
static final int UNTREEIFY_THRESHOLD = 6;
当红黑树上的节点数量小6时,红黑树重新变成单向链表,以上变化是为了提高检索效率
🍁哈希碰撞
- 若对象001和002的hash值相同,则一定是放到同一个单向链表上
- 当对象001和002的hash值不相同,但经过哈希算法转化成数组下标后可能相同,即发生了哈希碰撞
🍁HashMap的扩容
- 扩容之后的容量是原容量的2倍
🍁HashMap集合的key允许为null
Map map2 = new HashMap();
map2.put(null,null);
System.out.println(map2.size()); //1
map2.put(null,100); //key重复,value覆盖
System.out.println(map2.size()); //1
System.out.println(map2.get(null)); // 100
8、HashMap和Hashtable的区别
Map map3 = new Hashtable();
//java.lang.NullPointerException
map3.put(null,"123");
//java.lang.NullPointerException
map3.put("999",null);
Hashtable.class中的源码片段:
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
区别:
- Hashtable集合的key和value都不能为null,HashMap的都可以为null
- Hashtable类中的方法都带有synchronized,是线程安全的,但其对线程的处理导致了效率变低,使用较少了
- 二者底层都是哈希表数据结构,但Hashtable的初始化容量为11,默认加载因子0.75f,扩容是原容量*2+1(HashMap则是16、0.75,原容量*2)
9、Properties类
Properties类继承Hashtable,属于Map集合,Properties集合的key和value都是String类型。Properties被称为属性类对象,Properties是线程安全的。
Properties properties = new Properties();
//存:
//setProperty(String key,String value)方法
//取:
//getProperty(String key)方法
Properties properties = new Properties();
properties.setProperty("username","root");
properties.setProperty("password","123456");
String username = properties.getProperty("username");
//setProperty方法调用Hashtable的put方法
public synchronized Object setProperty(String key, String value) {
return put(key, value);
}
10、TreeMap
- TreeSet集合底层是一个TreeMap,TreeMap底层是一个二叉树
- 放到TreeSet集合中的元素,等同于放到了TreeMap集合的key部分了
- TreeSet集合中的元素,无序且不可重复,但可以按照元素大小顺序自动排序,称为可排序集合
//应用场景:数据库中取数据,页面上需要升序或者降序,放进TreeSet再拿出来
TreeSet<String> set2 = new TreeSet<>();
set2.add("zhangsan");
set2.add("lisi");
set2.add("wangwu");
for(String data:set2){
System.out.println(data);
}
//按字母升序输出
//String类已实现了Comparable接口
TreeSet集合排序的前提是元素的类已经实现了Comparable接口 ,对自定义的类,要注意这一点,否则java.lang.ClassCastException
//未实现Comparable
class Person{
int age;
public Person(){
}
public Person(int age){
this.age = age;
}
public String toString(){
return "age=" + this.age;
}
}
Person p1 = new Person(22);
Person p2 = new Person(30);
Person p3 = new Person(40);
TreeSet<Person> treeSet = new TreeSet<>();
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
for(Person p :treeSet){
System.out.println(p);
}
//运行报错:
java.lang.ClassCastException: class Person cannot be cast to class java.lang.Comparable
以上是因为:往TreeSet中存时,要排序,而Person这个自定义的类无法排序,因为没有指定Person对象之间谁大谁小的规则,即没有实现java.lang.Comparable接口
11、自定义类实现Comparable接口
实现Comparable接口,即重写其CompareTo方法,在CompareTo方法中编写比较的逻辑或者说比较的规则
class Person implements Comparable<Person>{
int age;
public Person(){
}
public Person(int age){
this.age = age;
}
public String toString(){
return "age=" + this.age;
}
//调用则是this.compareTo(p)
public int compareTo(Person p){
int age1 = this.age;
int age2 = p.age;
if(age1 == age2){
return 0;
}else if(age1 > age2){
return 1;
}else{
return -1;
}
}
}
以上是重写的思路,实际代码直接优化成:
public int compareTo(Person p){
return this.age-p.age;
}
//此时是按照age的数值大小升序排列
//想降序则直接return p.age-this.age;
例二:
class Vip implements Comparable<Vip>{
String name;
int age;
public Vip(){
}
public Vip(int age,String name){
this.age = age;
this.name = name;
}
public String toString(){
return "Vip{name="+ name + ",age=" + age + "}";
}
public int compareTo(Vip vip){
if(this.age == vip.age){
//age一样,则比较name,做为字符串直接返回String类的compareTo
return this.name.compareTo(vip.name);
}else{
return this.age-vip.age;
}
}
}
12、二叉树
- 自平衡二叉树,遵循左小右大原则存放。
- 遍历二叉树的三种方式:- 前序遍历(根左右)、中序遍历(左根右)、后序遍历(左右根),即前中后说的是根的位置
TreeSet集合、TreeMap集合中,是自平衡二叉树,均采用中序遍历,即Iterator迭代器采用的是中序遍历的方式。
例:自平衡二叉树:100、200、50、60、80、120、140、130、135、180、666、40、55
分析:
总结:
从最顶端的根入手,分左右,以左为新树,找根分左右。就像循环的嵌套,内层循环完整一轮,算外层的一次循环。有子元素,子元素左根右一次,其父节点算是遍历了一个左节点。
//TreeMap集合部分源代码:
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
Objects.requireNonNull(key);
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
13、Comparator接口—比较器
注意区分:
Comparable接口在java.lang包下,Comparator接口在java.util包下。
放到TreeSet或者TreeMap集合key部分的元素,要想做到排序,可选:
- 实现java.lang.Comparable接口
- 传一个比较器对象Comparator
当比较规则不会发生改变的时候,或者当比较规则只有一个的时候,选用Comparable。当比较规则多个需要频繁切换的时候,用Comparator,规则改变时,传比较器A,再变,传比较器B
使用了比较器后创建TreeSet集合时,要将比较器通过构造方法传进去。
//写比较器
class VipComparator implements Comparator<Vip>{
public int compare(Vip v1,Vip v2){
return v1.age-v2.age;
}
}
//规则有好几个的时候,再写比较器就好
class VipComparator2 implements Comparator<Vip>{
public int compare(Vip v1,Vip v2){
return ...;
}
}
//注意这里构造方法中要传入你的比较器
TreeMap<Vip,String> vips = new TreeMap<>();
//正确示范:
TreeMap<Vip,String> vips = new TreeMap<>(new VipComparator());
vips.put(new Vip(22,"llg"),"SVIP");
vips.put(new Vip(23,"ls"),"VIP");
以上也可以通过匿名内部类实现:
//这东西满是坑,最后少个封号报错
//这里new的比较器对象,类名只能Comparator
//忘记指定泛型,重写compare方法时,传个Vip对象它不认识,只能Object
TreeMap<Vip,String> vipMap = new TreeMap<>(new Comparator<Vip>(){
public int compare(Vip v1,Vip v2){
return v1.age-v2.age;
}
});
14、Collections工具类补充
java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作
集合工具类方法补充:
- list转为线程安全
List<String> list = new ArrayList<>();
Collections.synchronizedList(list);
- list集合排序
list.add("abc");
list.add("acq");
list.add("cba");
//注意这里我泛型指定了String
//如果是自定义类,则要实现Comparable
Collections.sort(list);
for(String s:list){
System.out.println(s);
}
- Set转List
Set<String> hashSet = new HashSet<>();
List<String> myList = new ArrayList<>(hashSet);
//也就说Set集合想排序可以转List,再排序,当然自带排序的就不用这么多此一举了
Collections.sort(myList);
集合的小提纲整理:
主要的集合类:
- ArrayList
- LinkedList
- HashSet
- TreeSet
- HashMap
- TreeMap
- Properties
每种集合对象的创建、特点、添加元素、删除元素、遍历