文章目录

  • 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、概述

java里val java中value是什么意思_System

  • 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的键值

java里val java中value是什么意思_java_02

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);
}

三种方式的运行结果一样:

java里val java中value是什么意思_java_03

4、哈希表的数据结构

哈希表是一个数组和单向链表的结合体。数组在查询方面效率很高,而随机增删效率低。单向链表则是随机增删效率高而查询效率低,哈希表则是融合两种数据结构,充分发挥二者的优点。

java里val java中value是什么意思_jvm_04

//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集合的存取

java里val java中value是什么意思_java_05

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,则哈希表变成了纯单向链表,这种情况称散列分布不均匀。形象的说就是全挂一根线上了。

java里val java中value是什么意思_System_06


😉 不当情况2:所有的hashCode方法返回值均不同,即数组下标都不同,则哈希表变成了一维数组,也是散列分布不均匀。

java里val java中value是什么意思_开发语言_07

6、hashCode()和equals()的重写

根据上面HashMap集合的存取,可知必须重写hashCode和equals方法。需要重写的是hashMap集合的key部分的元素,以及放在HashSet集合中的元素。

java里val java中value是什么意思_开发语言_08

  • HashMap集合的默认初始化容量是16,默认加载因子是0.75,即当HashMap集合底层数组的容量达到75%时,数组开始扩容
  • HashMap集合的初始化容量必须是2的次幂,a power of 2,这样可以提高HashMap集合的存取效率,达到散列均匀

定义一个Student类,并重写equals:

java里val java中value是什么意思_jvm_09


由此:

一个类的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);
}

java里val java中value是什么意思_jvm_10

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迭代器采用的是中序遍历的方式。

java里val java中value是什么意思_开发语言_11

例:自平衡二叉树:100、200、50、60、80、120、140、130、135、180、666、40、55

java里val java中value是什么意思_开发语言_12


分析:

java里val java中value是什么意思_System_13


总结:

从最顶端的根入手,分左右,以左为新树,找根分左右。就像循环的嵌套,内层循环完整一轮,算外层的一次循环。有子元素,子元素左根右一次,其父节点算是遍历了一个左节点。

//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

每种集合对象的创建、特点、添加元素、删除元素、遍历