首页 > 技术文章 > JavaSE学习笔记17:集合(三)

Sona-36D 2020-11-09 17:34 原文

集合(三)

Set

HashSet集合:无序不可重复

package se3.set;

import java.util.HashSet;
import java.util.Set;

public class HashSetTest01 {
    public static void main(String[] args) {
        //演示一下HashSet集合的特点
        Set<String> strs = new HashSet<>();
        //添加元素
        strs.add("娑娜");
        strs.add("娑娜");
        strs.add("迦娜");
        strs.add("莫甘娜");
        strs.add("娑娜");
        strs.add("凯尔");
        strs.add("奇亚娜");
        strs.add("卡莎");
        strs.add("凯尔");
        //遍历
        /**
         * 1.存储时顺序和取出的顺醋不同
         * 2.不可重复
         * 3.放到HashSet集合中的元素实际上是放到HashMap集合的key部分了
         */
        for (String s : strs){
            System.out.println(s);
        }
        /**
         * 运行结果:
         * 娑娜
         * 奇亚娜
         * 莫甘娜
         * 迦娜
         * 卡莎
         * 凯尔
         */
    }
}

TreeSet集合

package se3.set;

import java.util.Set;
import java.util.TreeSet;
/**
 * TreeSet集合存储元素特点:
 * 1.无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合
 * 2.注意:这里的“无序”指的是存进去的顺序和取出来的顺序不同,并且没有下标
 */
public class TreeSetTest01 {
    public static void main(String[] args) {
        //创建集合对象
        Set<String> strs = new TreeSet<>();
        //添加元素
        strs.add("A");
        strs.add("G");
        strs.add("F");
        strs.add("E");
        strs.add("D");
        strs.add("C");
        strs.add("B");
        //遍历
        //从小到大自动排序
        for (String s : strs){
            System.out.print(s + " ");
        }
        //运行结果:A B C D E F G
    }
}

Map

java.util.Map

  1. Map和Collection没有继承关系
  2. Map集合以key和value的方式存储数据:键值对
  3. key和value都是引用数据类型,都是存储对象的内存地址
  4. key起到主导的地位,value是key的一个附属品
Map接口中的常用方法:
  1. V put(K key,V value) 向Map集合中添加键值对

  2. V get(Object key) 通过key获取value

  3. void clear(); 清空Map集合

  4. boolean containsKey(Object key) 判断Map中是否包含某个key

  5. boolean containsValue(Object value) 判断Map中是否包含某个value

  6. boolean isEmpty() 判断Map集合中元素个数是否为0

  7. V remove(Object Values) 通过key删除键值对

  8. int size() 获取Map集合中键值对的个数

  9. Collection values() 获取Map集合中所有的Value,返回一个Collection

  10. Set keySet() 获取Map集合所有的key(所有的键是一个set集合)

  11. Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合

package se3.themap;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MapTest01 {
    public static void main(String[] args) {
        //创建Map集合对象
        Map<Integer,String> map = new HashMap<>();
        //添加键值对
        map.put(1,"张三");//1在这里进行了自动装箱
        map.put(2,"李四");
        map.put(3,"王五");
        map.put(4,"赵六");
        //通过key获取value
        String value = map.get(2);
        System.out.println(value);//李四
        //获取键值对的数量
        System.out.println("键值对的数量:" + map.size());//4
        //通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量:" + map.size());//3

        //判断是否包含某个key
        //contains方法底层调用的都是equals进行比对的,所以自定义的类型是需要重写equals方法的
        //System.out.println(map.containsKey(new Integer(4)));//true
        System.out.println(map.containsKey(4));//true
        //判断是否包含某个value
        //System.out.println(map.containsValue(new String("王五")));//true
        System.out.println(map.containsValue("王五"));//true

        //获取所有的value
        Collection<String> values = map.values();
        for (String s : values){
            System.out.print(s + " ");
        }//输出:张三 王五 赵六
        System.out.println();

        //清空map集合
        map.clear();
        System.out.println("键值对的数量:" + map.size());//0
        //判断是否为空
        System.out.println(map.isEmpty());//true
    }
}
Map集合的遍历
第一种方式:获取所有的key,通过遍历key来获取value
package se3.themap;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MapTest02 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");
        map.put(4,"赵六");
        //遍历集合
        //获取所有的key,所有的key是一个set集合
        Set<Integer> keys = map.keySet();
        //遍历key,通过key获取value
        //迭代器
        Iterator<Integer> it = keys.iterator();
        while (it.hasNext()){
            //取出其中一个key
            Integer key = it.next();
            //通过key获取value
            String value = map.get(key);
            System.out.print(key + "=" + value + " ");
        }//输出结果:1=张三 2=李四 3=王五 4=赵六
        System.out.println();

        //foreach也可以
        for (Integer key : keys){
            System.out.print(key + "=" + map.get(key) + " ");
        }//输出结果:1=张三 2=李四 3=王五 4=赵六
    }
}
第二种方式:Set<Map.Entry<K,V>> entrySet()
package se3.themap;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MapTest03 {
    public static void main(String[] args) {
        Map<Integer,String> map2 = new HashMap<>();
        map2.put(1,"张三");
        map2.put(2,"李四");
        map2.put(3,"王五");
        map2.put(4,"赵六");
        Set<Map.Entry<Integer,String>> set = map2.entrySet();
        //遍历集合,每次取出一个Node
        //迭代器
        Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while (it2.hasNext()){
            Map.Entry<Integer,String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.print(key + "=" + value + " ");
        }//输出结果:1=张三 2=李四 3=王五 4=赵六 
        System.out.println();
        
        //foreach
        //这种方式效率比较高,因为获取key和value都是直接从node对象中获取
        //这种方式比较适合于大数据量
        for (Map.Entry<Integer,String> node : set){
            System.out.print(node.getKey() + "=" + node.getValue() + " ");
        }//输出结果:1=张三 2=李四 3=王五 4=赵六 
    }
}

HashMap

HashMap集合

  1. HashMap集合底层是哈希表/散列表数据结构

  2. 哈希表是一个数组和单向链表的结合体

    数组:在查询方面效率很高,随机增删方面效率很低

    单向链表:在随机增删方面的效率较高,在查询方面的效率很低

    哈希表将以上两种数据结构融合在一起,充分发回来它们各自的优点

  3. HashMap集合底层的源代码:

    //HashMap底层实际上就是一个数组(一维数组)

    public class HashMap{

    ​ Node<K,V>[] table;

    }

//静态的内部类HashMap.Node

static class Node<K,V>{

​ final int hash;//哈希值是key的hashCode()方法的执行结果,hash值通过哈希函数/算法,可以转换存储成数组下标

​ final k key;//存储到Map集合中的key

​ V value;//存储到Map集合中的value

​ Node<K,V> next;//下一个节点的内存地址

​ }

哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表(数组和单向链表的结合体)

  1. 最主要掌握的是:

    map.put(K,V)

    v = map.get(k)

    以上两个方法的实现原理,是必须要掌握的

  2. HashMap集合的key部分特点:无序不可重复

    为什么无序?因为不一定挂到哪个单向链表上

    不可重复怎么保证?equals方法来保证HashMap集合的key不可重复

    如果key重复了,value会覆盖

放在HashMap集合key部分的元素其实就是放到HashSet集合中了

所有HashSet集合中的元素也需要同时重写hashCode和equals方法

  1. 哈希表HashMap使用不当时无法发挥性能!

  2. 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成单向链表。这种情况我们称为:散列分布不均匀。

  3. 假设将所有的hashCode()方法返回值都设定为不一样的值,会导致底层哈希表成为一维数组,没有链表的概念了,也是散列分布不均匀。

map.put(K,V)实现原理:

第一步:先将k,v封装到Node对象当中

第二步:底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals,如所有的equals方法返回都是false,那么这个新节点将被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。

v = map.get(k)实现原理:

先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals,如果所有equals方法返回false,那么get方法返回null,只要其中有一个节点的k和参数k进行equals的时候返回true,那么此时这个节点的value就是我们要找的value,get方法最终返回这个要找的value。

注意:同一个单向链表上所有节点的hash值相同,因为他们的数组下标是一样的,但同一链表上k和k的equals方法肯定返回的是false,都不相等

结论:

HashMap集合的key,会先调用两个方法,一个是hashCode()方法,一个是equals()方法,那么这两个方法都需要进行重写

package se3.themap;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapTest01 {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap<>();
        map.put(1111,"张三");
        map.put(6666,"李四");
        map.put(7777,"王五");
        map.put(2222,"赵六");
        map.put(2222,"阿波");//key重复的时候,value会自动覆盖
        System.out.println(map.size());//4
        //遍历Map集合
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        for (Map.Entry<Integer,String> entry : set){
            System.out.print(entry.getKey() + "=" + entry.getValue() + " ");
        }//无序不可重复
        //输出结果:7777=王五 1111=张三 6666=李四 2222=阿波
    }
}

同时重写equals和hashCode

package se3.themap;

import java.util.HashSet;
import java.util.Set;
/*
注意:1.向Map集合中存和取都是先调用key的hashCode方法,然后再调用equals方法,equals方法有可能调用,也有可能不调用
       equals什么时候不调用?
       拿put(k,v)和get(k,v)举例:key的hashCode方法返回哈希值,哈希值经过算法转换成数组下标,数组下标位置上如果是null,equals不会调用。
      2.如果一个类的equals方法重写了,那么hashCode方法必须重写,并且equals方法返回如果是true,hashCode方法返回的值必须一样
       equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,它们的哈希值都是相同的,
所有hashCode方法的返回值也应该相同
     3.hashCode和equals方法不用研究了,直接用IDEA生成(ALT + INS 选择equals()andhashCode())
结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
 */
public class HashMapTest02 {
    public static void main(String[] args) {
        Student s1 = new Student("张三");
        Student s2 = new Student("张三");
        System.out.println(s1.equals(s2));//重写equals之前是false
        System.out.println(s1.equals(s2));//重写equals之后是true(s1和s2表示相等 )
        //HashCode重写之前
        //System.out.println("s1的hashCode=" + s1.hashCode());//s1的hashCode=22307196
        //System.out.println("s2的hashCode=" + s2.hashCode());//s2的hashCode=10568834
        //HashCode重写之后
        System.out.println("s1的hashCode=" + s1.hashCode());//s1的hashCode=774920
        System.out.println("s2的hashCode=" + s2.hashCode());//s2的hashCode=774920
        //s1.equals(s2)结果已经是true了,表示s1和s2是一样的,那么往HashSet集合中放的话,按说只能放进去一个(HashSet集合特点:无序不可重复)
        Set<Student> students = new HashSet<>();
        students.add(s1);
        students.add(s2);
        System.out.println(students.size());//没有重写hashCode之前,结果是2(错误),重写之后是1(正确)
    }
}
package se3.themap;

import java.util.Objects;

public class Student {
    private String name;

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    //IDEA自动生成的重写后的equals方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }
    //IDEA自动生成的重写后的hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

推荐阅读