Java 基础-集合

272 阅读5分钟

概述

  1. 集合主要有两组,单列和双列
  2. Collection 接口主要分为 List Set, 他们的实现子类都是单列集合
  3. Map 接口实现子类是双列集合,存放的 <K,V>
WeChatba866856142f1ebf92919c51224b1362.png WeChatd48e856b9397fb85ea2a89f9f9001d8d.png

Collection: Interface

  1. Collection 实现子类可以存放多个元素,每个元素都可以是 Object
  2. 有些 Collection 的实现类,可以存放重复的元素,有些不可以
  3. 有些 Collection 的实现类,List是有序的 ,Set是无序的
  4. Collection 接口没有直接的实现子类,是通过它的子接口SetList来实现的
api说明
add添加
remove移除
contains查找元素是否存在
size获取元素个数
isEmpty判断是否为空
clear清空
addAll添加多个
containsAll判断是否存在多个
removeAll添加多个
Collection接口继承了Iterable迭代器
 public interface Collection <E> extends Iterable <E> {}
  1. Iterable接口迭代器,主要用于遍历Collection 集合中的元素。
  2. 所有实现 Collection 接口的集合类都有一个 iterable()方法,用来返回一个实现了Iterable接口的对象,即可以返回一个迭代器
  3. Iterable 接口中的hasNext判断是否还有下一个元素 next 负责移动,超出则会抛出异常 NoSuchElementException 无此类元素

List: Interface

List集合中的元素是有序的(添加的顺序和取出的顺序一致,且可以重复),集合中的每个元素都有其对应的顺序索引,即支持索引(底层数一个数组),索引是从0开始的

继承了 Iterablen: Interface

List list = new ArrayList();

list.add("小文");
list.add("小明");
list.add("小军");

Iterator iterator = list.iterator();

// 循环方式一:使用 iterator
while (iterator.hasNext()){
    Object next = iterator.next();
    System.out.println(next);
}

// Object next = iterator.next(); // NoSuchElementException
// 重置游标
// list.iterator();

// 循环方式二:增强for循环,增强for也可以用作数组使用
for (Object next :list){
    System.out.println(next);
}

// 循环方式三: 普通for循环
for (int i = 0; i < list.size(); i++) {
    Object o = list.get(i);
    System.out.println(o);
api说明
get获取索引
indexOf返回第一个匹配到的索引
lastIndexOf返回最后匹配到的索引
set替换
subList返回前闭后开的区间

ArrayList: Class

  • 说明
    ArrayList 底层是由游数组实现,不建议在多线程中使用,线程不安全(效率高)

  • 扩容机制

  1. ArrayList中维护了一个 Object 类型的数组 transient Object[] elementData,transient关键之表示不会被序列化
  2. elementData 无参构建,初始为0,第一次扩容为10,再次扩容为当前elementData的1.5倍
  3. 如果初始指定 elementData 大小,再次扩容为当前elementData的1.5倍

Vector: Class

  • 说明
    Vector 线程安全,方法上都加了 sybchronized(效率不高)

  • 扩容机制

  1. Vector中维护了一个 Object 类型的数组 protected Object[] elementData,transient关键之表示不会被序列化
  2. elementData 无参构建初始为10,再次扩容为当前elementData的2倍
  3. 如果初始指定 elementData 大小,再次扩容为当前elementData的2倍

LinkedList: Class

  • 说明 LinkedList底层实现双向链表和双端队列特点,线程不安全

WeChat5acfc2e9db4bf3b06aef11258a06011f.png

可以看出在 LinkedList 中有 prev,item,next 三个属性 来表示链的关系

Set: Interface

  • 说明
  1. 不允许重复元素,所以对多包含一个null
  • 循环机制
  1. 迭代循环
  2. 增强for循环
  3. 没法使用 for循环,因为没有 .get(i)方法,因为是无序的

HashSet

  • 说明
  1. 添加和取出的顺序不一致
  2. 底层是 new HashMap()实现, V 使用 PRESENT常量填充,<K- PRESENT>,实现逻辑看后面 HashMap重点和扩容机制

LinkedHashSet

  • 说明 \
  1. 这是一个双向链表结构
  2. 添加和取出的顺序一致
  3. 扩容机制和 HashSet 一致

TreeSet

  • 说明
  1. 当使用无参构造创建实例的时候,是无序的
  2. new TreeSet(Comparator<T>),按照Comparator的逻辑排序
  3. 底层是 TreeMap,底层是匿名内部类的 comparator 方法
  4. 如果有相同的值无法加入也就是 cmp = 0,查看TreeMap的排序机制

Map: Interface

  • 说明
  1. MapCollection并列存在,用于保存具有映射关系的数据 Key-Value(双列数据)
  2. Map中的Key和Value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的Key不允许重复,原因和HashSet一样
  4. MapValue可以重复
  5. MapKey可以是null,Value也可以是null,注意Keynull只能有一个,Valuenull可以是多个
  6. 常用 String类作为MapKey
  7. KeyValue之间存在单向一对一关系,即通过指定的Key总能找到对应的Value
api说明
put添加
remove根据键删除映射关系
get根据key获取值
size获取元素个数
isEmpty判读是否为空
clear清除
containsKey查找键是否存在
  • 循环方式

HashMap 为例


Map map = new HashMap();

for (int i = 0; i < 10; i++) {
    Car car = new Car();
    map.put("key"+i, car);
}


//  ----------第一组 先取出所有的key,通过Key取出对应的 Value ----------
Set keySet = map.keySet();
// 1. 增强 for
System.out.println("1. 增强 for-------");
for (Object key : keySet) {
    System.out.println(key + ":" +map.get(key));
}

// 2. 迭代器
System.out.println("2. 迭代器-------");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
    Object key =  iterator.next();
    System.out.println(key + ":" +map.get(key));
}

//  ---------- 第二组 把所有的 Value 取出 ----------
Collection values = map.values();
// 这里可以使用 Collection 使用的遍历方式
// 1. 增强 for
for (Object value : values) {
    System.out.println(value);
}

// 2. 迭代器
Iterator iterator2 = keySet.iterator();
while (iterator2.hasNext()) {
    Object value =  iterator2.next();
    System.out.println(value);
}

//  ---------- 第三组 通过EntrySet 来获取 Key Value  ----------
Set entrySet = map.entrySet();
// 1. 增强 for
for (Object o : entrySet) {
    Map.Entry e = (Map.Entry) o;
    System.out.println(e.getKey() +":"+ e.getValue());
}

System.out.println("第三组 2. 迭代器-------");
// 2. 迭代器
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
    // 向下转转型 Map.Entry
    Map.Entry e = (Map.Entry) iterator3.next();
    System.out.println(e.getKey() +":"+ e.getValue());
}

HashMap

  • 说明
  1. 线程不安全,方法没有做同步互斥操作(synchronized
  2. 不保证顺序
  3. 如果put已经存在的 key 会覆盖
  • *重点
  1. entrySet 中存储的是 HashMap$Node 无序所有的实体类的引用值
  2. keySet 中存储的是 HashMap$Node 中无序所有的 key (Set 接口实现)
  3. values 中存储的是 HashMap$Node 中无序所有的 value (Collection 接口实现)
  4. 添加一个元素时,先到hash值-随机转成一个索引
  5. 找到存储数据表table,看这个索引位置是否已经存放了有元素,如果没有直接加入
  6. 如果有,调用 equals比较,如果相同,则添加到最后
  7. 在Java 1.8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8), 并且table大小 >= MIN_TREEIFY_CAPACITY(默认64),会进行树化(红黑树)
  8. key会按照字节码的大小排序(但不严谨)
    // HashMap Source Code
 public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable{
    
    
    transient Set<Map.Entry<K,V>> entrySet;
    
    public Set<Map.Entry<K,V>> entrySet() {
        /** 
           存储 entrySet 中存储 Node<K,V> implements Map.Entry<K,V>
        */
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }
    
    static class Node<K,V> implements Map.Entry<K,V>{
        // some code ...
    }

    final class EntrySet extends AbstractSet<Map.Entry<K,V>>{}
         // some code ...
    }
  • *扩容机制
  1. 初始扩容为 1 <<< 4 => 16
  2. 扩容临界值为0.75,如果到达临界值12就会扩容 16 * 2=32,新的临界值就是32*0.75,以此类推 重点:临界值扩容: 是统计的所有数值,不管是添加在链表还是在树化后的树中
  3. hashCode 决定了table的索引的位置(可以根据逻辑改写)
  4. equals 决定了是否相等,如果相等则无法添加(可以根据逻辑改写)
  5. 当在树化中的数据不断减少,减少到一定数量后,会重新变回链表(这个过程叫减枝

HashTable

  • 说明
  1. 存放的是键值对 <K-V>
  2. 线程安全,方法实现了synchronized,效率不高
  3. 键值对都不能为null,否则会抛出 NullPointerException
  4. 操作方式和 HashMap 基本一致
  • *扩容机制
  1. 底层是数组 HashTable$Entry[] 初始化大小为 11
  2. 临界值 threshold 8 = 11* 0.75
  3. 执行方法 addEntry(); 添加 K-V 到 HashTable$Entry[]
  4. if (count >= threshold) 满足时,就进行扩容(count 是HashTable$Entry[]的长度
  5. 按照 int newCapacity = (oldCapacity << 1) + 1 的大小扩容

LinkedHashMap

  • 说明
    双向链表

TreeMap

  • 说明 \
  1. 双向链表
  2. 如果key, 相同无法加入
  • 排序机制 \
  1. 可以通过 compare 的机制 cmp < 0 ,cmp > 0, cmp = 0,来修改排序机制
while (p != null) {
    int cmp = cpr.compare(k, p.key);
    if (cmp < 0)
        p = p.left;
    else if (cmp > 0)
        p = p.right;
    else
        return p;
}

Properties

  • 说明
  1. 键值对都不能为null,否则会抛出 NullPointerException

Collections

  • 说明
    集合的工具方法
常用 API说明
sort不传Comparator的时候按照Ascll自然排序,
传入Comparator可以按照逻辑排序
reverse反转顺序
shuffle随机排序
swap交换集合中指定元素的索引位置
max根据元素自然顺序,返回给定集合中最大的元素,
可以传入Comparator可以按照逻辑筛选
min根据元素自然顺序,返回给定集合中最小的元素,
可以传入Comparator可以按照逻辑筛选
frequency集合中出现的次数
copy(dest,list)dest 的数据要 >= list
replaceAll替换集合中需要提替换的值

总结

  1. 先判断存储的类型是否是一组对象(单列)或一组键值对(双列)
  2. 一组对象(单列): Collection
    • 允许重复 List:
      • 增删多 -> LinkedList (底层维护了一个双向列表)
      • 改查多 -> ArrayList (底层卫华 Object 类型的可以变数组)
    • 不允许重复: Set:
      • 无序 -> HashSet (底层是HashMap,卫华了一个哈希表,即 数组+链表+红树)
      • 排序-> TreeSet 按照逻辑排序
      • 插入和取出顺序需要一致 -> LinkedHashSet 维护了数组+双向链表
  3. 一组键值对(单列): Map
    • 键无序-> HashMap(底层是:哈希表,数组+链表+红黑树)

    • 键排序-> TreeMap 按照逻辑排序

    • 键插入和取出顺序一致-> LinkedHashMap

    • 读取文件-> Properties