day15_集合

今日内容

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更快

day15_集合_第1张图片

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 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[重点]

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集合

day15_集合_第2张图片

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

day15_集合_第3张图片

4.1.6 去重原理

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

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

    day15_集合_第4张图片
  2. 在向set中添加元素时,其实是向map的key上添加

    day15_集合_第5张图片

所以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

你可能感兴趣的:(#,Java2313,windows)