JavaLearn#(11)MapIterator迭代器Collections集合总结泛型

Posted LRcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn#(11)MapIterator迭代器Collections集合总结泛型相关的知识,希望对你有一定的参考价值。

1. Map概述

存储的键值对映射关系,根据 key 找到value

1.1 HashMap

  • 采用 哈希表 (hashtable)存储结构
  • 查询、存储、删除速度快
  • key无序
  • key 和 value 都可以为 null

1.2 LinkedHashMap

  • 采用 哈希表 存储结构,同时用链表维护次序
  • key 有序(添加顺序)
  • key 和 value 都可以为 null

1.3 TreeMap

  • 采用红黑树的存储结构
  • key有序(自然顺序),查询速度比 List 快,没有 HashSet 快
  • key 和 value 都不能为 null

2. Map的使用

2.1 map的常用操作

// 声明对象
Map<String, String> map = new HashMap<>();

// 放入数据
map.put("cn", "China");

// 获取数据,通过 key 获取数据,若没有,返回 null
map.get("cn");

// 遍历 map
Set<Map.Entry<String, String>> entries = hashMap.entrySet();

2.2 存储国家简称和全称

// 创建一个 map 对象
Map<String, String> hashMap = new HashMap<>();  // key唯一,无序
Map<String, String> linkedHashMap = new LinkedHashMap<>();  // key唯一,有序(添加顺序)
Map<String, String> treeMap = new HashMap<>();  // key唯一,有序(自然顺序)

// 添加数据
hashMap.put("cn", "China");
hashMap.put("jp", "Japan");
hashMap.put("us", "the United States");
hashMap.put("us", "America");  // 同名的 key,第二次添加的会将第一次的覆盖

hashMap.put("uk", "the united kingdom");
hashMap.put("en", "the united kingdom");  // 同名的 value,不同的 key,都会存在

// 输出数据
System.out.println(hashMap);
System.out.println(hashMap.size());

// 遍历1: 获取所有的key ==>  keySet() 返回结果是 Set类型  【不推荐使用】
Set<String> keySets = hashMap.keySet();
Iterator<String> iterators = keySets.iterator();
while (iterators.hasNext()) {
  String key = iterators.next();
  // 通过 key获取 value
  System.out.println(key + "---->" + hashMap.get(key));
}

// 遍历2:得到所有的 key-value 组成的 set,直接遍历 set
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
  System.out.println(entry.getKey() + "---->" + entry.getValue());
}

2.3 存储学号和学生的映射

// map 的其他方法
map.remove(3);       // 根据 key删除内容
map.clear();         // 清空 map
map.isEmpty();       // map 是否为空
map.containsKey(5);  // map中的 key是否包含 5

Map<Integer, Student> map = new HashMap<>();
Student stu1 = new Student(1, "zhangsan", 87);
Student stu2 = new Student(2, "lisi", 90);
Student stu3 = new Student(3, "wangwu", 87);
Student stu4 = new Student(1, "zhaoliu", 87);

map.put(stu1.getSno(), stu1);
map.put(stu2.getSno(), stu2);
map.put(stu3.getSno(), stu3);
map.put(stu4.getSno(), stu4);

System.out.println(map.size());  // 关键字相同的覆盖
System.out.println(map.get(2));

Set<Map.Entry<Integer, Student>> entries = map.entrySet();
Iterator<Map.Entry<Integer, Student>> iterators = entries.iterator();
while (iterators.hasNext()) {
  Map.Entry<Integer, Student> next = iterators.next();
  System.out.println(next.getValue());
}

3. HashMap 的源码

JDK1.7 之前, HashMap底层就是一个 table数组 + 链表实现的哈希表存储结构

由源码(JDK1.7)知,链表中,每个节点都是一个 Entry(包含:键key值value键的哈希码hash指向下一个节点的next

// Entry 为 HashMap的内部类
static class Entry<K, V> implements Map.Entry<K, V> {
    final K key;  //key
    V value;  //value
    Entry<K, V> next;  //指向下一个节点的指针
    int hash;  //哈希码
}

JDK1.7HashMap的主要成员变量:

public class HashMap<K, V> implements Map<K, V> {
    // 哈希表主数组的默认长度 !!!
    static final int DEFAULT_INITIAL_CAPACITY = 16;
  
    // 默认的装填因子(链表中存的结点数 / 数组的长度)
    static final float DEFAULT_LOAD_FACTOR = 0.75f; 
  
    // 主数组的引用 !!!
    transient Entry<K, V>[] table; 
  
    // 【链表】中结点的数量
    transient int size;
  
    // 界限值、阈值(数组的长度 * 装填因子)
    int threshold;
  
    // 装填因子(可以使用默认的,也可以指定)
    final float loadFactor;
    
    // 无参构造方法
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); // 16   0.75
    }
    
    // 【重点】有参构造方法
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity !!!!! 长度一定是 2^n
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        init();
    }
}

注意:主数组的长度,一定是 2^n (所以当我们new 对象时,new HashMap(7),虽然指定了长度为7,但是实际长度为8

3.1 put的源码

调用 put方法添加键值对,采用哈希表添加数据的原理(计算 key的哈希码,和 value无关)

  • 第一步计算哈希码时,不仅调用了 key的 hashCode(),还进行了更复杂的操作,目的是尽量保证不同的key,得到不同的哈希码
  • 第二步根据哈希码计算存储位置时,使用位运算提高效率(length必须是2^n)
  • 第三步添加 Entry时,添加到链表的第一个位置,而不是链表末尾
  • 如果在添加 Entry时,发现了相同的 key 已经存在,就使用新的 value 替代旧的 value,并且返回旧的 value

3.2 get的源码

  • 调用 get方法根据 key获取 value
    • 哈希表三步查询数据原理的具体实现
    • 其实是根据 key找 Entry,再从 Entry中获取 value

3.3 JDK1.8的改变

链表存储的数据个数,>= 8的时候,不再采用链表,而是采用了红黑树存储结构,那么查询的时候,时间复杂度就由 O(n) 转变为 O(logn)

4. TreeMap 的源码

底层是一棵红黑树(二叉树、二叉查找树、二叉平衡树)

每个结点的结构:

static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;
    V value;
    Entry<K,V> left = null;
    Entry<K,V> right = null;
    Entry<K,V> parent;
    boolean color = BLACK;
}

TreeMap的主要成员变量:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
    // 外部比较器(优先级高)
	private final Comparator<? super K> comparator;
    
    // 红黑树的结点数量
    private transient int size = 0;
    
    // 指向根结点的一个引用(存储的根节点的地址)
    private transient Entry<K,V> root = null;
    
    // 无参构造方法, 没有指定外部比较器
    public TreeMap() {
        comparator = null;
    }
    
    // 有参构造方法,指定外部比较器!!!
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
}

4.1 put 的源码

  • 根节点开始比较
  • 添加的过程,就是构造二叉平衡树的过程,会自动平衡
  • 平衡离不开比较:外部比较器优先,然后是内部比较器,如果两个都没有,则抛出异常

4.2 get 的源码

5. HashSet 和TreeSet 的源码

5.1 HashSet的源码

HashSet 的底层使用的是 HashMap,所以底层结构也是哈希表

add 方法:

5.2 TreeSet的源码

TreeSet的底层是 TreeMap,所以底层结构是 红黑树

add方法,同理是将新增的元素作为 key,放入map

6. Iterator 介绍及源码

  • 专门用来遍历集合(Collection、List、Set,Map不可以

  • 实际上是迭代器设计模式的实现

  • 常用方法:

    • boolean hasNext():判断是否还有可以访问的元素
    • Object next():返回要访问的下一个元素
    • void remove():删除上次访问的元素(lastRet 指向的元素)
  • 实现了 Iterable接口的集合类,都可以使用迭代器遍历,集合类中要提供 iterator()方法

  • for-each 遍历集合时,底层使用的还是 Iterator

    • for-each 遍历数组时,底层使用的是 for循环

ListIterator

  • 只针对于 List及其子类型
  • 可以顺序遍历,还可以逆序遍历
  • 在遍历过程中,可以 remove()、add()set()
  • 可以定位当前的索引位置 nextIndex()previousIndex()

7. Collections 工具类

  • 唯一的构造方法为 private的,不允许在类的外部创建对象
  • 提供了大量的 static方法,可以通过类名直接调用
 // 1.添加元素 (一次性添加多个元素)
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 45, 56, 23, 78, 98);

// 2.排序
Collections.sort(list);

// 3.查找:必须自然有序
Collections.binarySearch(list, 78);  // 折半查找(必须有序)
// 查找底层的实现,判断 list instanceof RandomAccess(接口,无任何元素,用于判断)

// 4.最大最小值
Collections.max(list);
Collections.min(list);

// 5.填充(将 list中的元素都填充为 100)
Collections.fill(list, 100);

// 6.复制(源list的长度,必须大于等于被拷贝list的长度)
List<Integer> list1 = new ArrayList<>();
Collections.addAll(list1, 1,2,3,4,5,6);
Collections.copy(list1, list); // list1: 45, 56, 23, 78, 98, 6

/** 7.同步【具体细节在多线程处!!!!】
  * 第一代集合类 Vector、Hashtable   线程安全、效率低,适用多线程
  * 第二代集合类 ArrayList、HashMap  线程不安全、效率高,适用单线程
  */

// 既希望效率高,又要线程安全?
// 方法1:如果是多线程的情况下,对第二代集合类加锁
list = Collections.synchronizedList(list); // 相当于 list = new SynchronizedRandomAccessList<>(list)

// 方法2:使用第三代的并发集合类 CopyOnWriteArrayList、 ConcurrentHashMap

8. 旧的集合类和新集合类

8.1 旧集合类

  • Vector
    • 实现原理和 ArrayList相同,都是长度可变的数组结构,可以互用
    • 区别:
      • Vector 是早期 JDK接口,ArrayList 是替代 Vector 的新接口
      • Vecort 线程安全,效率低。 ArrayList 线程不安全
      • 扩容时,Vector 增长 1倍,ArrayList 增长 1.5倍
  • Hashtable
    • 实现原理和 HashMap相同,底层都是哈希表,可以互用
    • 区别:
      • Hashtable 是早期JDK提供,HashMap 是新的
      • Hashtable 继承 Dictionary类,HashMap 实现 Map接口
      • Hashtable 线程安全,HashMap 线程不安全
      • Hashtable 不允许 null 值,HashMap 允许 null 值

8.2 新一代并发集合类

在大量并发情况下,提高集合的效率和安全,使用 Lock锁,位于 java.util.concurrent 包下(JUC)

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
List<String> copy = new CopyOnWriteArrayList();
// 其他方法相同的使用方式

9. 集合的概念辨析

9.1 集合和数组的比较

数组不是面向对象的,集合有多种类型,可适用与不同场合

  • 数组容量固定,且无法动态改变 ==》 集合类容量动态增长
  • 数组可以存放基本数据类型和引用数据类型 ==》 集合只能存放引用数据类型(基本类型通过包装类)
  • 数组无法判断其中实际存了多少元素,length 只是数组的容量 ==》 集合可以通过 size() 知道实际存了多少元素
  • 数组仅使用顺序表 ==》 集合有顺序表、链表、哈希表、树等
  • 集合以类的形式存在,具有封装、继承、多态等特征

9.2 ArrayList 和 LinkedList 的联系和区别

联系

  • 都实现了 List 接口
  • 有序,不唯一(元素可以重复)

区别:

  • ArrayList 底层是数组, LinkedList 底层是链表(双向链表)
  • ArrayList 在内存中分配连续的空间,LinkedList 在内存中随意分配空间
  • ArrayList 适用于遍历元素和随机访问元素多的情形,LinkedList 适用于添加和删除元素多的情形(前提也是必须先低效率查询才行)

9.3 哈希表的原理

HashMap 的底层原理(数组长度默认为16、必须为2^n,装填因子默认0.75,扩容2倍),查询快、添加快

哈希表的结构:

  • JDK1.7 :数组 + 链表
  • JDK1.8 :数组 + 链表/红黑树(链表长度>8时,转为红黑树)

哈希表添加原理:

  • 计算哈希码( hashCode() )
  • 计算存储位置(数组的索引)
  • 存入指定位置(要处理冲突,借助 equals 进行比较)

哈希表查询原理:

  • 同哈希表的添加原理

9.4 TreeMap的底层原理

底层为红黑树(二叉树、二叉查找树、二叉平衡树)

每个结点的结构:

添加原理:

  • 从根节点开始比较
  • 添加过程就是构造二叉平衡树的过程,自动平衡
  • 平衡离不开比较:外部比较器优先,然后是内部比较器,否则报错

9.5 Collection 和 Collections 的区别

  • Collection 是 Java 提供的集合接口,存储一组无序、不唯一的对象,有两个子接口,List 和 Set
  • Collections 专门用来操作集合类

9.6 Vector 和 ArrayList 的区别

  • Vector 和 ArrayList 实现原理相同,都是长度可变的数组
  • Vector 线程安全,效率低。 ArrayList 线程不安全,效率高
  • 扩容时,Vector 增长 1 倍。 ArrayList 增长 1.5 倍

9.7 Hashtable 和 HashMap 区别

  • Hashtable 和 HashMap 实现原理相同,都是哈希表
  • Hashtable 线程安全,效率低。 HashMap 线程不安全,效率高
  • Hashtable 不允许 null 值。 HashMap 允许 null 值(ConcurrentHashMap 不允许为 null )

10. 集合总结

存储结构是否有序是否唯一查询效率添加/删除效率
ArrayList顺序表有序(添加)不唯一索引查询最高
LinkedList链表(双向链表)有序(添加)不唯一
HashSet哈希表无序唯一最高最高
HashMap哈希表key 无序key 唯一最高最高
LinkedHashSet哈希表 + 链表有序(添加)唯一最高最高
LinkedHashMap哈希表 + 链表key 有序(添加)key 唯一最高最高
TreeSet红黑树有序(自然)唯一中等中等
TreeMap红黑树有序(自然)key 唯一中等中等
CollectionMap
无序、不唯一collectionMap.values()
无序、唯一HashSetHashMap 的 key
有序、不唯一ArrayList、LinkedList
有序、唯一TreeSet、LinkedHashSetTreeMap的key、LinkedHashMap 的 key
  • 使用 LinkedList ,遍历时,使用 iterator迭代器效率高

11. 泛型

11.1 为什么使用泛型

没有使用之前:

  • 不安全:添加元素时,不进行检查(宽进)
  • 繁琐:获取元素时,需要强制类型转换 (严出)

使用之后:

  • 安全:检查是否为该类型的(严进)
  • 简单:直接获取即可 (宽出)

11.2 什么是泛型 geneic

JDK 1.5引入,即“参数化类型”

public class ArrayList<T> implements List<T> {} // 定义处,为类型【形参】

List<String> list = new ArrayList<>(); // 使用处,为类型【实参】

11.3 泛型接口

List<E>         Collection<E>        Iterable<T>       Comparable<T>       Comparator<T>

// 定义一个泛型接口
public interface List<T> {
    public void add(E obj);
}

11.4 泛型类

ArrayList<E>    LinkedList<E>    HashMap<K, V>
    
// 定义一个泛

以上是关于JavaLearn#(11)MapIterator迭代器Collections集合总结泛型的主要内容,如果未能解决你的问题,请参考以下文章

JavaLearn # Java的常用类

JavaLearn # 异常

JavaLearn # 面向对象案例:猜丁壳

JavaLearn # 数据结构和算法

JavaLearn # 面向对象编程_4

JavaLearn Jenkins the hard way - Jenkins的存储模型