集合-Set、Map

今日内容

1.复习
2.LinkedList
3.Set(HashSet、TreeSet)
4.Map(HashMap)

一、复习

List集合的特点?

  • 有序,允许重复

ArrayList的底层实现原理,以及特点

  • 数组,初始10,扩容1.5倍
  • 查询更新快,删插入慢
  • 解释为什么快,慢?

增强for循环语法

写出以下几个集合的方法签名

  • 向集合添加元素 boolean add(E e)
  • 向集合指定下标处添加指定元素 void add(int index,E e)
  • 根据下标查询集合中元素 E get(int index)
  • 查询集合的大小 int size()

二、LinkedList[熟悉]

LinkedList是List的实现类,那么LinkedList也是允许重复,有序
且LinkedList集合也有关于下标操作集合的方法,但是还提供了一些关于操作开头和结尾的方法

底层是使用链表实现.

2.1 演示方法

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>( );
        list.add(3);
        list.add(1);
        list.add(1);
        list.add(4);
        list.add(4);
        list.add(2);
        // 有序,允许重复
        System.out.println(list );
        // 且也有关于的下标的操作方法
        list.add(0,0);
        System.out.println(list );
        Integer e = list.get(1);
        System.out.println(e );
        // 也可以遍历
        for (Integer i : list) {
            System.out.println(i );
        }

        // 专门提供了关于头尾的操作
        list.addFirst(-1);
        list.addLast(8);

        System.out.println(list );

        /**
         * 类似还有
         * getFirst
         * getLast
         * removeFirst
         * removeLast
         */
    }

2.2 底层原理

底层是双向链表实现,空间不连续

虽然有下标,但是不能直接定位元素,是通过位运算判断下标离左边还是右边近,然后再从左边或者右边一个个的挨个查的 —> 所以查询慢

但是又因为空间不连续,插入删除时不影响其他元素,相对于ArrayList更快
在这里插入图片描述

2.3 特点

  1. 有序
  2. 允许重复
  3. 查找,更新时效率比较低
  4. 插入,删除时效率比较高

三、Set

Set是Collection集合的子接口,主要特性是不允许重复元素

Set接口中的操作集合的API与Collection中一模一样

Set接口有两个主要的实现类:HashSet,TreeSet

3.1、HashSet[重点]

HashSet类实现了Set接口,也是不允许重复元素

HashSet集合底层是HashMap(哈希表),存储的元素无序,无序是指迭代顺序和插入顺序不一致

不保证线程安全,线程不同步

3.1.1 方法演示

构造方法

  • HashSet() 构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16加载因子是 0.75
  • HashSet(Collection<? extends E> c) 构造一个包含指定 collection 中的元素的新 set
  • HashSet(int initialCapacity) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
  • HashSet(int initialCapacity, float loadFactor) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。

方法

HashSet类中的方法与父接口Set接口中的方法一致,即又跟Collection接口中方法一致,无特殊方法,没有关于的下标的方法

    public static void main(String[] args) {
        HashSet<Integer> set = new HashSet<>( );
        boolean r1 = set.add(31);
        System.out.println(r1 );

        boolean r2 = set.add(11);
        System.out.println(r2 );

        // set集合不允许重复
        boolean r3 = set.add(11);
        System.out.println(r3 );

        set.add(41);
        set.add(41);
        set.add(21);

        // 遍历顺序和插入顺序不一致
        for (Integer i : set) {
            System.out.println(i );
        }

        // 自习演示
        // 添加,删除,大小,判断(空,包含),遍历
    }

3.2.2 扩容机制[面试]

HashSet底层HashMap,其实是Hash表,存储数据是散列存储
可以理解为存储进去是随机的

默认初始容量16,加载因子0.75 —> 扩容的阈值= 容量 * 因子 = 16 * 0.75 = 12
即超过12个元素时就要触发扩容,扩容成原来的2倍

(ps: 初始容量和加载因子是可以通过构造方法创建时修改的…)

3.2.3 去重原理[面试]

练习1: 将学生存储到HashSet集合中,如果属性一致则去重

  1. 调用add(E e)方法时,会在底层调用元素e的hashcode方法来获得对象的地址值
  2. 如果地址值不一样,直接存储
  3. 如果地址值一样时,会再调用元素的equals方法判断元素的内容是否一样
  4. 如果equals为false,那么存储 但是如果equals判断值为true,那么去重

总结: 以后只需要使用工具生成hashcode和equals就可以再HashSet中去重!

package com.qf.set;

import java.util.HashSet;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class TestHashSet2 {

    public static void main(String[] args) {

       /**
         * 调用hashcode获得地址值
         *  如果地址值不一样,直接存储
         *  如果地址值一样,也没有直接舍弃不存储,而是再调用
         * equals判断对象内容
         *  如果内容判断不一样,存储
         *  如果内容一样,舍弃不存储
         */
        HashSet<Student> set = new HashSet<>( );
        set.add(new Student(18,"张三"));
        System.out.println("------------------" );
        set.add(new Student(18,"张三"));
        System.out.println("------------------" );
        set.add(new Student(19,"李四"));
        System.out.println("------------------" );
        set.add(new Student(19,"李四"));
        System.out.println("------------------" );
        set.add(new Student(20,"王五"));
        System.out.println("------------------" );

        for (Student student : set) {
            System.out.println(student );
        }
    }
}
// Student类要重写hashcode和equals
package com.qf.set;

import java.util.Objects;

public class Student {
    private int age;
    private String name;
    // idea 生成hashcode和equals方法
    @Override
    public boolean equals(Object o) {
        System.out.println("equals()...." );
        if (this == o) return true;
        if (o == null || getClass( ) != o.getClass( )) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }

     // setget toString 构造...
}

3.2 TreeSet[了解]

TreeSet基于TreeMap,TreeMap底层是红黑树(一种平衡二叉树),会实现对存储的元素排序

TreeSet是Set集合的实现,也是不允许重复

   public static void main(String[] args) {

        TreeSet<Integer> set = new TreeSet<>( );
        set.add(33); 
        set.add(3);
        set.add(3);// 3 存储不了,去重
        set.add(41);
        set.add(11);
        set.add(12);
        System.out.println(set );// 迭代有顺序,默认是升序
    }

四、Map<K,V>[重点]

Map代表双列集合,一次存储一对键值对(K,V)

Map是接口,代表是键映射到值的对象,一个Map不能包含重复的键,值允许重复

每个键最多只能映射到一个值,

可以通过键找到值,但是不能通过值找键.

方法都是非常常见的方法,但是Map是接口无法演示

Map有两个常用实现类

  • HashMap
  • TreeMap

4.1 HashMap[重点]

HashMap是Map的实现类,现在JDK8及以后底层是由数组+链表+红黑树实现
并允许使用 null 值和 null

HashMap存储的元素是不保证迭代顺序,存储的键不允许重复,值允许重复


除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同


补充: Hashtable是线程安全的map集合(Hashtable是不允许null值null键),效率低 ; HashMap是线程不安全的,效率高
ConcurrentHashMap 即安全又高效的Map集合

在这里插入图片描述

HashMap的容量和扩容: 初始容量16,加载因子0.75 阈值是 16 * 0.75,达到阈值扩容至原来的2倍
ps: 昨天学习的HashSet所有特性,其实就是HashMap的特性,包括去重原理

4.1.1 方法演示

构造方法

HashMap()
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity)
构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor)
构造一个带指定初始容量和加载因子的空 HashMap。
HashMap(Map<? extends K,? extends V> m)
构造一个映射关系与指定 Map 相同的新 HashMap。

方法

每个都很重要!!!

    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>( );
        map.put("A",1);
        map.put("B",2);
        map.put("C",3);
        map.put("D",4);
        System.out.println("初始map:"+map );

        // 通过键获得值
        Integer v = map.get("E");// 找不到返回null
        Integer v2 = map.get("B");
        System.out.println("键找值: "+ v2 );

        // 根据键移除整个键值对,返回值
        Integer a = map.remove("A");
        System.out.println("移除键A,返回值 " + a );

        System.out.println("移除后"+map );

        // 大小
        int size = map.size( );
        System.out.println("size:"+size );

        // 判断是否为空
        System.out.println("是否为空: "+map.isEmpty() );
        // 清空
        map.clear();
        System.out.println("是否为空: "+map.isEmpty() );

        // boolean containsKey(Object key)
        // 判断集合是否包含键
        //  boolean containsValue(Object value)
        // 判断集合是否包含值
    }

    private static void show1() {
        HashMap<String, Integer> map = new HashMap<>( );
        System.out.println(map );
        // 存储数据 V put(K k,V v)
        // 返回值是该键之前的值
        Integer old = map.put("A", 1);
        // 存储相同的键,键不保留,但是值会替换
        Integer old2 = map.put("A", 2);
        System.out.println(old +"----"+old2 );

        // 值允许重复,值2可以保留
        map.put("C",2);
        map.put("D",4);
        map.put("B",2);

        // 无序,键不能重复,值允许重复
        System.out.println(map );
    }

4.1.2 迭代/遍历

Map集合本身并没有设计独立的迭代Map的方法,但是

Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个Map的内容

  • Set keySet() 键集,返回一个Set集合,其中只有键
  • Collection values() 值集,返回一个Collection集合,其中只有值
  • Set<Map.Entry<K,V>> entrySet() 键值映射集,返回一个Set集合,其中放着key-value对象

4.1.3 键集

 		// 键集,返回一个集合,只有键
        Set<Integer> keySet = map.keySet();
        Iterator<Integer> iterator = keySet.iterator( );
        while (iterator.hasNext( )) {
            System.out.println(iterator.next() );
        }
        System.out.println("------------" );
        for (Integer key : keySet) {
            System.out.println(key );
        }

4.1.4 值集

       // 值集,返回一个集合,只有值
        Collection<String> values = map.values();
        Iterator<String> iterator1 = values.iterator( );
        while (iterator1.hasNext()) {
            System.out.println(iterator1.next() );
        }
        System.out.println("-------------" );
        for(String value : values) {
            System.out.println(value );
        }

4.1.5 键值映射集 [非常重要]

Entry是Map接口中的内部接口,代表是一个键值对,即包含键和值.
且该Entry接口中提供了关于操作单个键,值的方法

  • K getKey()
  • V getValue()
        // 调用entrySet方法返回Set集合,集合中存储的是Map.Entry
        // Entry是Map的内部接口,代表的是一项(键值对)
        Set<Map.Entry<Integer,String>> entrySet =  map.entrySet();
        Iterator<Map.Entry<Integer,String>> iterator2 = entrySet.iterator();
        while (iterator2.hasNext()) {
            // 从迭代器取出来的是Entry对象
            Map.Entry<Integer,String> entry = iterator2.next();
            // 通过entry对象可以获得键和值
            Integer key = entry.getKey( );
            String value = entry.getValue( );
            System.out.println(key +"-->" +value);
        }

        System.out.println("===============" );
        // 增强for
        for(Map.Entry<Integer,String> entry : entrySet) {
            Integer key = entry.getKey( );
            String value = entry.getValue( );
            System.out.println(key +"-->" +value);
        }

在这里插入图片描述

4.1.6 去重原理

HashMap的键去重其实就是之前讲的HashSet的去重,因为HashSet底层就是HashMap

  1. 在创建HashSet时,其实在底层创建了HashMap

    在这里插入图片描述

  1. 在向set中添加元素时,其实是向map的key上添加

    在这里插入图片描述

所以HashMap的键的去重原理就是

  • 向键存储数据时,先调用键的hashcode()方法
  • 如果hashcode值不一样则直接存储
  • 如果hashcode值一样,再调用元素的equals()方法
    • 如果equals方法返回false,则存储
    • 如果equals方法返回true,则不存储

4.2 HashMap的应用

场景一: 适合有关联映射的场景

  • 电话 110 --> 警察
  • 行政区划 0371 --> 河南
  • 简称 豫 --> 河南
  • 身份 41011111 —> zhangsan
  public static void main(String[] args) {
        HashMap<String, String> m = new HashMap<>( );
        m.put("河南","豫");
        m.put("河北","冀");
        m.put("山西","晋");
        m.put("陕西","陕");
        m.put("安徽","皖");
        
  }

设计方法,传入字符串,输出该字符串中每个字符出现的次数,使用HashMap实现
例如: “abcHelloabcWorld”,输出 a出现2次,b出现2次,l出现3次,H出现1次

    // a --> 2
    // b --> 2
    public static void cishu(String str) {
        // key存字符,value存次数
        HashMap<String, Integer> map = new HashMap<>( );
        // 方案1: split("")
        // 方案2: toCharArray()
        // 方案3: 遍历字符串
        char[] chars = str.toCharArray( );
        for (char c : chars) {
            String s = String.valueOf(c);
            // 判断map是否有改字符
            boolean b = map.containsKey(s);
            if (!b) {// 不包含,则第一次存入
                map.put(s,1);
            }else{ // 之前有过该字符,次数+1
                Integer count = map.get(s);
                count++;
                map.put(s,count);
            }
        }
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet( );
        for (Map.Entry<String, Integer> entry : entrySet) {
            String key = entry.getKey( );
            Integer value = entry.getValue( );
            System.out.println("字符:"+key+"-->"+value+"次" );
        } 
    }

场景二:

Map --> java对象

public class User{
	private int id;
       private String username;
 //,,,
}
User user = new User();
user.setId(1);
user.setUsername("zs");
sout(user); // User{id=1,username=zs}

HashMap map = new HashMap();
map.put("id",1);
map.put("username","zs");
sout(map); // {id=1,username=zs}

五、集合总结

重点

  • ArrayList: 方法(创建,crud,遍历),实现原理,特点
  • HashSet: 方法,实现原理,特点

  • 集合遍历(迭代) 增强for循环一定要熟悉

熟悉了解

  • LinkedList 方法和实现原理
  • TreeSet 会排序

以后如何选择集合

  • 没有任何要求的,只是需要存储多个元素的 --> ArrayList
  • 如果要求插入顺序和遍历顺序一致 --> ArrayList
  • 如果要求保留重复元素 --> ArrayList
  • 如果要求去重 --> HashSet
  • 如果要求排序 --> TreeSet

以后存储的数据有关联的,用HashMap


其实经验上: 最最常用就俩: ArrayList,HashMap

HashMap map = new HashMap();
map.put(“id”,1);
map.put(“username”,“zs”);
sout(map); // {id=1,username=zs}

六、自学TreeMap,Collections

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Su sir~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值