常用集合类

大家好呀!我是小笙!我学习了韩顺平老师的集合类的知识,收获颇丰!现在来和大家分享笔记!

集合类

集合框架体系

Collection接口框架

image-20220226113621627

补充框架图

image-20220303102523695

Map接口框架

image-20220226114159934

补充框架图

image-20220303102607310

Collection接口

Collection 实现 Iterable接口 : public interface Collection extends Iterable

遍历方式
遍历方式-迭代器

image-20220226131905068

Iterator接口 又称为 迭代器,主要用于遍历Collcection集合中的元素

所有实现了Collection接口的集合类都有一个Iterator()方法,用来返回一个迭代器

迭代器方法

变量和类型方法描述
default voidforEachRemaining(Consumer<? super E> action)对每个剩余元素执行给定操作,直到处理完所有元素或操作引发异常。
booleanhasNext()如果迭代具有更多元素,则返回 true
Enext()返回迭代中的下一个元素。
default voidremove()从底层集合中移除此迭代器返回的最后一个元素(可选操作)。
// Iterator方法的使用实例
public class Iterator01 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("三国演义",10));
        list.add(new Book("水浒传",20));
        list.add(new Book("西游记",15));

        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        // 退出循环之后,迭代器指向最后一个元素
        // iterator.next(); // 抛出异常 NoSuchElementException
        // 需要重置迭代器  iterator = list.iterator();
    }
    static class Book{
        private String name;
        private int price;

        public Book(String name, int price) {
            this.name = name;
            this.price = price;
        }

        @Override
        public String toString() {
            return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
        }
    }
}
遍历方式-for增强

特点:只能用于遍历集合和数组(简化版迭代器)

基本语法:

 for(元素类型 元素名:集合名或数组名){ 
     // 访问元素
 }

实例

public class ForS {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Toy("猛虎王",10));
        list.add(new Toy("霹雳火",20));
        list.add(new Toy("洛洛",15));

        // 增强for 本质仍然是迭代器
        // 集合
        for (Object b:list) {
            System.out.println(b.toString());
        }
        // 数组
        int[] num = {2,4,5,6};
        for (int n:num
             ) {
            System.out.println(n+"  ");
        }
    }
    static class Toy{
        private String name;
        private int price;

        public Toy(String name, int price) {
            this.name = name;
            this.price = price;
        }
        @Override
        public String toString() {
            return "Toy{" +
                    "name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    }
}
// debug 跳转 底层也是迭代器
// 跳转1
public Iterator<E> iterator() {
    return new Itr();
}
// 跳转2
public boolean hasNext() {
    return cursor != size;
}
// 跳转3
public E next() ......等等
遍历方式-普通for循环
public static void main(String[] args) {
    List list = new ArrayList();
    list.add("张三丰");
    list.add("秦天柱");
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
}

Collection接口特征

  1. 可以实现存放多个元素,每个元素可以是Object类
  2. 没有直接实现的子类,都是通过它的子接口Set和List来实现的
Collection的常用方法
List list = new ArrayList();
// add 添加单个元素
list.add("lns");
list.add(520); // 自动装箱 new Integer(520)
list.add("zlr");
System.out.println(list.toString()); // [lns, 520, zlr]
/*
*  父类AbstractCollection: toString方法
*  public String toString() {
*    Iterator<E> it = iterator(); // 创建迭代器进行遍历该集合
*    if (! it.hasNext())
*        return "[]";
*
*    StringBuilder sb = new StringBuilder();
*    sb.append('[');
*    for (;;) {
*        E e = it.next();
*        sb.append(e == this ? "(this Collection)" : e);
*        if (! it.hasNext())
*            return sb.append(']').toString();
*         sb.append(',').append(' ');
*    }
*  }
*/

// remove 删除指定元素
list.remove(1); // 等价于 list.remove(520);
System.out.println(list); // [lns, zlr]

// contains 查找元素是否存在
System.out.println(list.contains("lns")); // true

// size 获取元素个数
System.out.println(list.size()); // 2

// isEmpty 判读是否为空
System.out.println(list.isEmpty()); // false

// clear 清空
list.clear();
System.out.println(list); // []

// addAll 添加多个元素
List list2 = new ArrayList();
list2.add("lns");
list2.add("love");
list2.add("zlr");
list.addAll(list2);
System.out.println(list); // [lns, love, zlr]

// containsAll 查找多个元素是否存在
System.out.println(list.containsAll(list2)); // true

// removeAll 删除多个元素
list.add("!");
list.removeAll(list2);
System.out.println(list); // [!]
List接口

Collection接口的子接口 (队列数据结构)

特点

  • List集合类元素顺序有序,且可以重复
  • List集合类中的每个元素都有其对应的顺序索引
// List集合类元素顺序有序,且可以重复
List list = new ArrayList();
list.add("das");
list.add("tom");
list.add("tom");
System.out.println(list); // [das, tom, tom]
// List集合类中的每个元素都有其对应的顺序索引
System.out.println(list.get(0));  // das
List常用方法
public class ListMethod {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("张三丰");
        list.add("秦天柱");
        // add 插入一个对象
        list.add(1,"lns");
        System.out.println(list); // [张三丰, lns, 秦天柱]

        List list2 = new ArrayList();
        list2.add("风火轮");
        list2.add("大黄蜂");
        // addAll 插入所有元素
        list.addAll(1,list2);
        System.out.println(list); // [张三丰, 风火轮, 大黄蜂, lns, 秦天柱]

        // indexOf 返回该对象首次出现的索引位置
        System.out.println(list.indexOf("lns")); // 3

        // lastIndexOf 返回该对象最后一次出现的索引位置
        list.add(1,"张三丰");
        System.out.println(list ); // [张三丰, 张三丰, 风火轮, 大黄蜂, lns, 秦天柱]
        System.out.println(list.lastIndexOf("张三丰")); // 1

        // set 替换对象数据
        list.set(1,"妞妞");
        System.out.println(list); // [张三丰, 妞妞, 风火轮, 大黄蜂, lns, 秦天柱]

        // subList 返回范围为[fromIndex,toIndex)位置的子集合 该方法返回的是子串集合的地址索引
        list = list.subList(0, 3);
        System.out.println(list); // [张三丰, 妞妞, 风火轮]

    }
}
ArrayList类

是由数组实现数据存储

image-20220227132949556

特点:

  • 元素可以是null,并且可以有多个

  • ArrayList是线程不安全的,不能在多线程的情况下使用

    // 对比这两种源码 区别在于是否线程安全  synchronized
    // ArrayList源码
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    // Vector源码
    public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }
    
ArrayList源码分析

image-20220226184245606

  1. ArrayList类数据存储在Object类数组中(elementData)

    transient Object[] elementData; // transient 表示该属性不会被序列化
    
  2. 当使用无参构造方法创建该对象,初始elementData容量为0,第一次添加数据,扩容到10容量,以后每次扩容,则会扩大当前容量的1.5倍

  3. 如果使用指定大小的有参构造器,则初始elementData容量为指定大小,如果需要扩容,也是直接扩容1.5倍

扩容机制

无参构造器

1.设置断点

image-20220226200138044

2.debug跳转

// 跳转1
public boolean add(E e) {
    modCount++; // 记录集合被修改的次数  如果madCount的值因为线程原因意外改变,则抛出异常
    add(e, elementData, size); // e:传入的数据 elementDate:Object数组 size:数组元素数量
    return true;
}
// 跳转2
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length) // s:数组元素数量 elementDate.length:数组容量 
        elementData = grow(); // 只有当容量够用,不会调用该方法
    elementData[s] = e; // 数组添加数据
    size = s + 1; 
}
// 跳转3
private Object[] grow() {
    return grow(size + 1); 
}
// 跳转4
private Object[] grow(int minCapacity) { // minCapacity:当前元素个数+1
    int oldCapacity = elementData.length; // 记录原容量
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA){ //判断容量是否为0
        //  传入newLength方法的参数:原容量,超出容量,0.5倍容量大小
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                  minCapacity - oldCapacity, oldCapacity >> 1 ); // >> 1 相当于乘0.5
        return elementData = Arrays.copyOf(elementData, newCapacity); // 扩容,保留原数据
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }  // DEFAULT_CAPACITY 10   如果是容量为0,第一次扩容默认为10
}
// 当传入第11个数据时候跳转5
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // assert oldLength >= 0
    // assert minGrowth > 0

    int newLength = Math.max( minGrowth,prefGrowth ) + oldLength; // 当超出容量,则扩容1.5倍
    if (newLength - MAX_ARRAY_LENGTH <= 0) {
        return newLength;
    }
    return hugeLength(oldLength, minGrowth);
}

有参构造器

image-20220226202444863

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
Vector类

是由数组实现数据存储

image-20220227132833785

Vector的基本介绍

// Vector的类定义
public class Vector<E>extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
// Vector底层本质也是对象数组
protected Object[] elementData;

// Vector是线程安全
// 通常方法被 synchronized 关键字修饰

Vector源码分析

/*
 * The amount by which the capacity of the vector is automatically
 * incremented when its size becomes greater than its capacity.  If
 * the capacity increment is less than or equal to zero, the capacity
 * of the vector is doubled each time it needs to grow.
 * 当向量的大小变得大于其容量时,向量的容量自动增加的量。如果容量增量小于或等于零,
 * 则每次需要增长时,向量的容量都会增加一倍
 * @serial
 */
protected int capacityIncrement;

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, 							capacityIncrement > 0 ? capacityIncrement : oldCapacity);
    return elementData = Arrays.copyOf(elementData, newCapacity);
}

public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // assert oldLength >= 0
    // assert minGrowth > 0

    int newLength = Math.max(minGrowth, prefGrowth) + oldLength; // 2倍
    if (newLength - MAX_ARRAY_LENGTH <= 0) {
        return newLength;
    }
    return hugeLength(oldLength, minGrowth);
}

Vector和ArrayList的比较

底层结构版本线程安全扩容机制
ArrayList可变数组jdk1.2不安全,但效率高如果有参构造 扩容1.5倍;如果是无参1.第一次默认10,第二次扩容1.5倍
Vector可变数组jdk1.0安全,但效率不高如果有参构造 扩容2倍;如果是无参1.第一次默认10,第二次扩容2倍
LinkedList类

LinkedList类基本介绍

  • LinkedList类底层实现了双向链表和双端队列特点
  • 可以添加任意元素包括null,并且可以重复
  • 线程不安全没有实现同步

image-20220227133039410

LinkedList类的底层结构

  1. 该类底层是一个双向链表

  2. 其中含有两个属性:first 和 last 分别指向首节点和尾节点

    image-20220227134230977

  3. 每个节点里面含有prev,next,item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,用item来存储数据

    image-20220227142526922

  4. 进行添加和删除操作,效率比数组高

添加数据源码分析

image-20220227160330222

// 添加第一个数据
// 跳转1
public boolean add(E e) { // 增加数据
    linkLast(e);
    return true;
}
// 跳转2
void linkLast(E e) {
    final Node<E> l = last; // last:null 第一次添加last为null
    final Node<E> newNode = new Node<>(l, e, null); //l:null e:"lns"  说明prev和next指向null
    last = newNode; // last 指向尾节点 
    if (l == null) // 添加第一个节点
        first = newNode; //  first和last都指向同一个节点
    else
        l.next = newNode; 
    size++;
    modCount++;
}
// 添加第二个数据: 省略部分不重要的
void linkLast(E e) {
    // 总结: l这个变量可以当成连接器,连接新节点和原来最后一个节点
    final Node<E> l = last; // l:链表的最后一个节点
    final Node<E> newNode = new Node<>(l, e, null); // 创建连接上一个节点的新节点,e:"zlr"
    last = newNode; // last指向新的节点(该节点就是新的最后一个节点)
    if (l == null)
        first = newNode;
    else 
        l.next = newNode; // l指向新节点:节点变成l的下一个节点
    size++;
    modCount++;
}
// 删除数据还是更改数据...等等看源码

常用方法

// add 增加节点
LinkedList linkedList = new LinkedList();
linkedList.add("lns");
linkedList.add("zlr");
System.out.println(linkedList.toString()); // [lns, zlr]

// remove 删除节点
linkedList.remove(); // 默认删除第一个节点
System.out.println(linkedList); // [zlr]

// set 修改节点
linkedList.set(0,"奥里给");
System.out.println(linkedList); // [奥里给]

// get 根据索引获得某个节点数据
System.out.println(linkedList.get(0)); // 奥里给

ArrayList和LinkedList的比较

底层结构增删效率改查效率
ArrayList可变数组低,数组扩容
LinkedList双向链表高,动态扩容
Set接口

public interface Set extends Collection

Set接口基本介绍

  • 无序(添加和取出顺序不一致),没有索引,因此该接口不能再使用普通for循环索引的方式遍历
  • 不允许重复数据,因此可以有null 但是只能有一个null
  • Set接口实现了Collection接口,所以可以使用该接口的所有方法
Set set = new HashSet();
set.add("lns");
set.add("null");
set.add("null"); // 只会存入一个数据
set.add("zlr");
System.out.println(set); // [lns, zlr, null] 无序
HashSet类

HashSet的底层实际上就是HashMap

/**
 * Constructs a new, empty set; the backing {@code HashMap} instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

HashSet框架图

image-20220228105517461

特点:

  • 可以存放null值,但是只能存放一个

  • 不能存放重复元素

    public class HashSet01 {
        public static void main(String[] args) {
            Set set = new HashSet();
            System.out.println(set.add("lns")); // true
            System.out.println(set.add("lns")); // false
            System.out.println(set.add(new String("lns"))); // false 
            System.out.println("lns".hashCode() == new String("lns").hashCode()); // true
            System.out.println(set.add(new person("zlr"))); // true
            System.out.println(set.add(new person("zlr"))); // true
            // new String("lns") 的hashCode和"lns"相同
        }
    }
    class person{
        private String name;
    
        public person(String name) {
            this.name = name;
        }
    }
    
  • 元素是无序的,创建后顺序是固定的

HashSet底层机制

先介绍散列表:数组 + 链表

实例代码

public class HashSetStructure {
    public static void main(String[] args) {
        // 数组 + 链表
        Node[] table = new Node[16];
        // 创建节点
        Node node21 = new Node("lns", null);
        table[2] = node21;

        Node node22 = new Node("zlr", null);
        node21.next = node22;

        Node node23 = new Node("lp", null);
        node22.next = node23;

        Node node31 = new Node("cyj", null);
        table[3] = node31;

        System.out.println(Arrays.toString(table));
    }
}

class Node{ // 节点
    Object item; // 数据
    Node next; // 指向下个节点

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

图解

image-20220227201842892

用该图分析HashSet底层过程

  1. 添加一个元素时,先得到hash值 => 索引值(类似图中 0-16索引)

  2. 找到存储数据表table,看这个索引上是否有存放数据

    1. 如果没有找到,就直接加入(如图中的node21,node22,node23);如果有元素并且hashCode值相同,则调用equals方法进行比较,如果相同则不添加,反之添加
// 实例说明:如果有元素并且hashCode值相同,则调用equals方法进行比较,如果相同则不添加,反之添加
// 这就是为什么new String("lns")不会被添加以及为什么new person("zlr")会被添加2次
// 核心就在于equals方法
// 但是为什么"lns"的hashCode会和 new String("lns")相同呢(不是不同的地址吗)
// 关键就在String类重写了hashCode方法,字符串的hashCode是根据字符算出来的
/*
    String类计算hashCode的算法
    public static int hashCode(byte[] value) {
        int h = 0;
        for (byte v : value) { // value:传入的字符串
            h = 31 * h + (v & 0xff);
        }
        return h;
    }
*/
public class HashSet01 {
    public static void main(String[] args) {
        Set set = new HashSet();
        System.out.println(set.add("lns")); // true
        System.out.println(set.add("lns")); // false
        System.out.println(set.add(new String("lns"))); // false
        System.out.println(set.add(new person("zlr"))); // true
        System.out.println(set.add(new person("zlr"))); // true
    }
}

class person{
    private String name;

    public person(String name) {
        this.name = name;
    }
}
  1. 在jdk8中,一个链表的元素达到8个以及table数据表长度达到64.则将 数组+链表 => 红黑树
public class HashSet02 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("罗念笙");
        hashSet.add("张洛融");
        hashSet.add("罗念笙");
        System.out.println(hashSet); // [张洛融, 罗念笙]
/*
 *     分析源码
 *     假设传入"罗念笙"
 *     跳转1:add方法
 *     public boolean add(E e) {
 *         return map.put(e, PRESENT)==null; // PRESENT:占位 new Object()
 *     }
 *
 *     跳转2:put方法
 *     public V put(K key, V value) {
 *         return putVal(hash(key), key, value, false, true);
 *     }
 *
 *     跳转3:hash方法 计算hash值并返回给put方法中hash(key)
 *     static final int hash(Object key) {
 *         int h;
 *         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 *     }
 *
 *     跳转4:pubVal方法   传入形参:hash值, "罗念笙", PRESENT, false, true
 *     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
 *         Node<K,V>[] tab; Node<K,V> p; int n, i; // 定义辅助变量
 *         // transient Node<K,V>[] table; table:数组+链表形式
 *         if ((tab = table) == null || (n = tab.length) == 0) // 没有分配数组空间
 *             // 分配数组空间 newCap = DEFAULT_INITIAL_CAPACITY 默认16
 *             n = (tab = resize()).length; 
 *         if ((p = tab[i = (n - 1) & hash]) == null) // i = (n - 1) & hash 通过hash值计算索引
 *             tab[i] = newNode(hash, key, value, null); // 该索引下数组值为null,就直接添加节点
 *         else {
 *             Node<K,V> e; K k; // 定义辅助变量
 *             // 比较索引处首节点的 hash值 以及 指向地址是否相同 或者 比较值是否相同(重写的情况)
 *             if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
 *                 e = p;
 *             // 判断 索引p 指向的是否是红黑树
 *             else if (p instanceof TreeNode)
 *                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 *             // 遍历该索引的所有节点,依次比较;如果相同则不添加
 *             else {
 *                 for (int binCount = 0; ; ++binCount) {
 *                     if ((e = p.next) == null) {
 *                         p.next = newNode(hash, key, value, null);
 *                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 *                             treeifyBin(tab, hash);
 *                         break;
 *                     }
 *                     if (e.hash == hash &&
 *                         ((k = e.key) == key || (key != null && key.equals(k))))
 *                         break;
 *                     p = e;
 *                 }
 *             }
 *             if (e != null) { // existing mapping for key
 *                 V oldValue = e.value;
 *                 if (!onlyIfAbsent || oldValue == null)
 *                     e.value = value;
 *                 afterNodeAccess(e);
 *                 return oldValue;
 *             }
 *         }
 *         ++modCount;
 *         // threshold 阈值,用来提前给数组扩容 :threshold = (int)(DEFAULT_LOAD_FACTOR *                    DEFAULT_INITIAL_CAPACITY);
 *         // size 大小值得是加入的节点个数达到了阈值
 *         if (++size > threshold)
 *             resize();
 *         afterNodeInsertion(evict); // HashMap类留给子类继承使用的方法
 *         return null;
 *     }
 *
 *     // treeifyBin方法 一个链表的元素达到8个以及table数据表长度达到64.则将 数组+链表 => 红黑树
 *     final void treeifyBin(Node<K,V>[] tab, int hash) {
 *         int n, index; Node<K,V> e;
 *         // 如果表的长度(数组长度)小于64,则扩容
 *         // MIN_TREEIFY_CAPACITY:64  
 *         if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
 *             resize();
 *         // 如果表的长度(数组长度)大于等于64,则数组+链表 => 红黑树
 *         else if ((e = tab[index = (n - 1) & hash]) != null) {
 *             TreeNode<K,V> hd = null, tl = null;
 *             do {
 *                 TreeNode<K,V> p = replacementTreeNode(e, null);
 *                 if (tl == null)
 *                     hd = p;
 *                 else {
 *                     p.prev = tl;
 *                     tl.next = p;
 *                 }
 *                 tl = p;
 *             } while ((e = e.next) != null);
 *             if ((tab[index] = hd) != null)
 *                 hd.treeify(tab);
 *         }
 *     }
 *
 *  // 补充信息 非debug内容
 *  节点Node代码
 *  static class Node<K,V> implements Map.Entry<K,V> {
 *         final int hash;
 *         final K key;
 *         V value;
 *         Node<K,V> next;
 *
 *         Node(int hash, K key, V value, Node<K,V> next) {
 *             this.hash = hash;
 *             this.key = key;
 *             this.value = value;
 *             this.next = next;
 *         }
 *
 *         public final K getKey()        { return key; }
 *         public final V getValue()      { return value; }
 *         public final String toString() { return key + "=" + value; }
 *
 *         public final int hashCode() {
 *             return Objects.hashCode(key) ^ Objects.hashCode(value);
 *         }
 *
 *         public final V setValue(V newValue) {
 *             V oldValue = value;
 *             value = newValue;
 *             return oldValue;
 *         }
 *
 *         public final boolean equals(Object o) {
 *             if (o == this)
 *                 return true;
 *             if (o instanceof Map.Entry) {
 *                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
 *                 if (Objects.equals(key, e.getKey()) &&
 *                     Objects.equals(value, e.getValue()))
 *                     return true;
 *             }
 *             return false;
 *         }
 *     }
 */
    }
}
LinkedHahSet类

LinkedHashSet类底层是一个LinkedHashMap,是一个数组+双向链表的结构

// 初始化LinkedHashSet
// LinkedHashSet类构造器
public LinkedHashSet() {
    super(16, .75f, true); // 初始化容量16;负载因子0.75
}
// 调用父类HashSet构造器,初始化LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
// LinkedHashMap调用父类的HashMap的构造器
public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}
// 初始化HashMap
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY) // MAXIMUM_CAPACITY = 1 << 30
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

LinkedHashSet框架图

image-20220228105904095

特点:

  • 根据hashCode值来决定元素的存储位置,同时使用双向链表来维护元素的次序,所以在一定程度上是有序的
  • 不允许重复添加元素

LinkedHashSet类的底层分析

image-20220228170158121

// LinkedHashSet节点源码
// 继承Node节点(HashMap的静态内部类) 
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after; // 增加前驱节点和后继节点 => 双向链表
    // 构造器
    Entry(int hash, K key, V value, Node<K,V> next) {
        // Node节点构造器
        super(hash, key, value, next);
    }
}
// 实现Map接口里的Entry接口
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}
TreeSet类

基本介绍

TreeSet类本质就是调用TreeMap,源码,比较机制等会在TreeMap中详细述说

public TreeSet() {
    this(new TreeMap<>());
}

image-20220302162013910

Map接口

public interface Map<K, V> 与Collection接口并列存在

Map接口特点

  • 保存具有映射关系的数据:Key - Value
  • Map 中的Key - Value数据可以是任何的引用数据类型,会封装到HashMap$Node对象中
  • Map中的Key不允许重复,但是Value可以重复(并且数据都可以是null)
  • Key 和 Value存在一对一的关系,总是能通过Key找到对应得Value

image-20220228191506219

深入理解map接口的Node节点

// Node节点属于HashMap内部类,实现了Map.Entry<K,V>接口 ; 一个Node节点对象含有一个key和value
// Map.Entry<K,V> 为Map接口的内部接口
static class Node<K,V> implements Map.Entry<K,V> 
// key-value值是存储在Node节点对象中,Map.Entry中key-value指向Node节点对象的key-value值的引用(类似对象名对对象的引用)
final int hash;
final K key;
V value;
Node<K,V> next;
// 那么EntrySet又是什么呢
// Set接口都可理解就是单列集合,其实EntrySet就是存放Map.Entry数据类型的集合 
transient Set<Map.Entry<K,V>> entrySet;
// 代码示例
Map map = new HashMap();
map.put("name","罗念笙"); // key: name value: 罗念笙
map.put("name","张洛融"); // key值不能重复,如果重复会覆盖之前相同key值的value值
map.put("person","张洛融"); // value值能重复
System.out.println(map); // {person=张洛融, name=张洛融}
System.out.println(map.get("name")); // 张洛融 ; 能通过Key找到对应得Value
// 着重点看运行类型
Set set = map.entrySet();
System.out.println(set.getClass()); // class java.util.HashMap$EntrySet
for (Object obj: set
    ) {
    System.out.println(obj.getClass()); // class java.util.HashMap$Node
}

Map常用方法

// 常用方法
// put方法 添加key-value
Map map = new HashMap();
map.put("name","lns");
map.put("age",18);
map.put("grade",99);
map.put("grade",60); // 重复key,覆盖之前的value
System.out.println(map); // {grade=60, name=lns, age=18}
// remove方法 根据key值删除映射关系
map.remove("grade");
System.out.println(map); // {name=lns, age=18}
// get方法 根据key获得value值
System.out.println(map.get("age")); // 18
// size方法 获取元素个数
System.out.println(map.size()); // 2
// isEmpty方法 判断元素是否为空
System.out.println(map.isEmpty()); // false
// containsKey 查找该键是否存在
System.out.println(map.containsKey("name")); // true
// clear 清空键值对]
map.clear();
System.out.println(map); // {}

Map接口遍历方式

// Map接口遍历方式
Map map = new HashMap();
map.put("name","lns");
map.put("age",18);
map.put("grade",99);

//  第一组: 先取出key(keySet方法,通过key取出value (get方法)
Set set = map.keySet();
// 方式1: 增强for
for (Object key:set
     ) {
    // key: grade value: 99  key: name value: lns  key: age value: 18
    System.out.print("key: "+key+" value: "+map.get(key)+"  ");
}
System.out.println();
// 方式2: 迭代器
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    Object key = iterator.next();
    // key: grade value: 99  key: name value: lns  key: age value: 18
    System.out.print("key: "+key+" value: "+map.get(key)+"  ");
}
System.out.println();

// 第二组: 通过EntrySet来获取key-value
Set entrySet = map.entrySet();
// 方式3: 用getKey方法 和 getValue方法
for (Object obj: entrySet
     ) {
    Map.Entry entry = (Map.Entry)obj;
    // key: grade value: 99  key: name value: lns  key: age value: 18
    System.out.print("key: "+entry.getKey()+" value: "+entry.getValue()+"  ");
}
System.out.println();
// 方式4: 迭代器
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
    Map.Entry entry = (Map.Entry)iterator1.next(); 
    // key: grade value: 99  key: name value: lns  key: age value: 18
    System.out.print("key: "+entry.getKey()+" value: "+entry.getValue()+"  ");
}
HashMap类

HashMap底层是数组+链表+红黑树

HashMap类特点

  • 保存具有映射关系的数据:Key - Value

  • Key不允许重复,但是Value可以重复(并且数据都可以是null); 如果重复,将会替换掉value值

    image-20220301171734335

  • Key 和 Value存在一对一的关系,总是能通过Key找到对应得Value

  • HashMap没有实现线程同步,是线程不安全的

HashMap框架图

image-20220301171028407

注意 HashMap扩容机制等价于HashSet扩容机制,如上述

Hashtable类

Hashtable类特点

  • 保存具有映射关系的数据:Key - Value

  • Hashtable的key和value都不允许是null,如果是,将会抛出空指针异常

    image-20220302085244120

  • Hashtable是线程安全的,与HashMap不同

    // Hashtable的put方法
    public synchronized V put(K key, V value)
    

Hashtable框架图

image-20220302135647398

Hashtable扩容机制

//Hashtable构造器初始化容量11
public Hashtable() {
    this(11, 0.75f); 
} 

// put方法
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Hashtable.Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length; // 索引值的计算方式: 散列码(hash)% 散列表的长度(tab.length)
    @SuppressWarnings("unchecked") // 抑制警告
    Hashtable.Entry<K,V> entry = (Hashtable.Entry<K,V>)tab[index]; // 创建entry节点
    // 判断是否有相同的key的entry节点,如果有,就替换掉原来的value值;反之则添加entry节点
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }
    // 添加entry节点
    addEntry(hash, key, value, index);
    return null;
}

// addEntry方法
private void addEntry(int hash, K key, V value, int index) {
    Entry<?,?> tab[] = table; // 成员变量table: 用来存储之前添加的键值对
    if (count >= threshold) { // 判断是否需要扩容
        // Rehash the table if the threshold is exceeded
        rehash();

        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
    modCount++;
}

// rehash方法 用于扩容
protected void rehash() {
    int oldCapacity = table.length; // 记录原来的散列表的长度(table.length)
    Entry<?,?>[] oldMap = table; // 记录原来的散列表(table)

    // overflow-conscious code
    int newCapacity = (oldCapacity << 1) + 1; // 新容量 = 旧容量 * 2 + 1
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; // 数组扩容

    modCount++;
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;
    // 添加原来的数组数据
    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

细节说明

image-20220302155704520

// 源码说明
// addEntry方法
private void addEntry(int hash, K key, V value, int index) {
    Entry<?,?> tab[] = table; // 成员变量table: 用来存储之前添加的键值对
    if (count >= threshold) { // 判断是否需要扩容
        // Rehash the table if the threshold is exceeded
        rehash();

        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) tab[index]; // 该索引该的引用节点的引用赋值给e
    tab[index] = new Entry<>(hash, key, value, e); // 加入当前的节点,并且指向下一个节点e
    count++;
    modCount++;
}
// Entry内部类的构造器 说明e 为当前节点的下一个节点
protected Entry(int hash, K key, V value, Entry<K,V> next) {
    this.hash = hash;
    this.key =  key;
    this.value = value;
    this.next = next;
}

Hashtable类和HashMap类的区别

版本线程(安全)效率允许(null键 ,null值)
HashMap1.2不安全允许
Hashtable1.0安全较低不允许
TreeMap类

基本介绍

使用TreeMap时,如果是调用无参构造器,则放入的Key对象必须实现Comparable接口。StringInteger这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法

TreeMap类的有序是按一定规则的有序,而非LinkedHashSet的插入和取出顺序一致

注意:红黑树的具体结构,我会放在数据结构里详细介绍

TreeMap源码分析

  1. 无参构造器
// debug 代码示例
public class TreeMap01 {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap();
        treeMap.put("name","罗念笙");
        treeMap.put("age",18);
        System.out.println(treeMap); // {age=18, height=192, name=罗念笙}
    }
}

// 无参构造器: new TreeMap();
public TreeMap() {
    comparator = null; // comparator:TreeMap中的属性(用来存储comparator内部类的对象)
}

// 传入第一个参数:put("name","罗念笙");
// 跳转1
public V put(K key, V value) { //key: name  value: 罗念笙
    return put(key, value, true);
}
// 跳转2
private V put(K key, V value, boolean replaceOld) {
    Entry<K,V> t = root; // t指向根节点
    if (t == null) { // 如果根节点没数据
        addEntryToEmptyMap(key, value); // 增加Entry节点
        return null;
    }
    // 删除的一部分代码,在添加第一个节点不会遍历
}
// 跳转3 
private void addEntryToEmptyMap(K key, V value) {
    compare(key, key); // type (and possibly null) check
    root = new Entry<>(key, value, null); // 创建新的节点
    size = 1;
    modCount++;
}
// 跳转4
final int compare(Object k1, Object k2) {
    // 如果构造器没有传入comparator内部类的对象,则k1对象对应的类必须实现了compareTo方法
    // 就是实现了Comparable接口并且重写compareTo方法,并且k1和k2是可以比较的
    return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
        : comparator.compare((K)k1, (K)k2);
}
// 跳转5  跳转到String类的compareTo方法
public int compareTo(String anotherString) {
    byte v1[] = value;
    byte v2[] = anotherString.value;
    byte coder = coder();
    if (coder == anotherString.coder()) {
        return coder == LATIN1 ? StringLatin1.compareTo(v1, v2)
            : StringUTF16.compareTo(v1, v2);
    }
    return coder == LATIN1 ? StringLatin1.compareToUTF16(v1, v2)
        : StringUTF16.compareToLatin1(v1, v2);
}

// 传入第二数:put("age",18);
// 跳转1
public V put(K key, V value) { // key: age  value: 18
    return put(key, value, true);
}
// 跳转2
private V put(K key, V value, boolean replaceOld) {
    Entry<K,V> t = root;
    if (t == null) { // 跳过
        addEntryToEmptyMap(key, value);
        return null;
    }
    // 辅助变量
    int cmp; 
    Entry<K,V> parent;
    // split comparator and comparable paths
    // 无参构造器中没有是实现该匿名内部类,所以comparator = null
    Comparator<? super K> cpr = comparator;
    if (cpr != null) { // 跳过
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else {
                V oldValue = t.value;
                if (replaceOld || oldValue == null) {
                    t.value = value;
                }
                return oldValue;
            }
        } while (t != null);
    } else {
        Objects.requireNonNull(key); // 检查key是否为null
        @SuppressWarnings("unchecked") // 抑制警告
        // 强转key为Comparable接口(接口多态):判断key是否实现了Comparable接口,如果不是将会抛出               ClassCastException异常
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t; // 根节点变成了父节点
            cmp = k.compareTo(t.key); // 比较规则
            if (cmp < 0) 
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else {
                V oldValue = t.value;
                if (replaceOld || oldValue == null) {
                    t.value = value;
                }
                return oldValue;
            }
        } while (t != null);
    }
    addEntry(key, value, parent, cmp < 0);
    return null;
}
// 跳转3 
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
// 跳转4 跳转到String类的compareTo方法
public int compareTo(String anotherString) {
    byte v1[] = value;
    byte v2[] = anotherString.value;
    byte coder = coder();
    if (coder == anotherString.coder()) {
        return coder == LATIN1 ? StringLatin1.compareTo(v1, v2)
            : StringUTF16.compareTo(v1, v2);
    }
    return coder == LATIN1 ? StringLatin1.compareToUTF16(v1, v2)
        : StringUTF16.compareToLatin1(v1, v2);
}
// 跳转5
private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
    Entry<K,V> e = new Entry<>(key, value, parent); // 创建新节点
    if (addToLeft)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
}
// 跳转6:设置节点颜色:红和黑
/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;

    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}

2.有参构造器

public class TreeMap02 {
    public static void main(String[] args) {
        // 通过key中的字符大小进行排序 降序: name > height > age
        TreeMap treeMap1 = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((car)o2).compareName((car)o1);
            }
        });
        treeMap1.put(new car("e"),"LNS");
        treeMap1.put(new car("da"),18);
        treeMap1.put(new car("dwes"),185);
        // {com.Al_tair.map_.treeMap_.car@27d6c5e0=185,
        //  com.Al_tair.map_.treeMap_.car@4f3f5b24=18,
        //  com.Al_tair.map_.treeMap_.car@15aeb7ab=LNS}
        System.out.println(treeMap1);
    }
}

class car{
    String name;
    public car(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public int compareName(Object o) {
        return this.getName().length()-((car)o).getName().length();
    }
}

Properties类

基本介绍

image-20220302141600899

Properties类特点

  • 保存具有映射关系的数据:Key - Value
  • Hashtable的key和value都不允许是null,如果是,将会抛出空指针异常

常用方法

// 常用方法
Properties properties = new Properties();
// put方法 添加数据,修改数据
properties.put("name","罗念笙");
properties.put("age",18);
properties.put("grade",99);
properties.put("grade",98); //修改数据
System.out.println(properties); // {grade=98, name=罗念笙, age=18}

// 通过key获取value
System.out.println(properties.get("name")); // 罗念笙

// remove方法 删除数据
properties.remove("grade");
System.out.println(properties); // {name=罗念笙, age=18}

如何选择集合实现类

  1. 先判断存储的数据类型(一组对象或者一组键值对)
  2. 一组对象【单列集合】:Collection接口
    • 允许重复并且有序:List接口
      • 线程安全:Vector【底层是一个Object类型的可变数组】
      • 线程不安全:
        • 增删多:LinkedList【底层是一个双向链表】
        • 改查多:ArrayLIst【底层是一个Object类型的可变数组】
    • 不允许重复:Set接口
      • 无序并且线程不安全:HashSet【底层就是HashMap,数组+链表+红黑数】
      • 定制排序并且线程不安全:TreeSet
      • 插入和取出顺序一致,并且线程不安全: LinkedHashSet 【数组+双向链表】
  3. 一组键值对:Map接口
    • 线程不安全:
      • 键无序:HashMap类 【底层就是:数组+链表+红黑数】
      • 定制排序:TreeMap类
      • 键插入和取出顺序一致:LinkedHashMap类
    • 线程安全:读取配置文件:Properties类

Colleactions工具类

Collections是一个操作Set,List,Map等集合的工具类,提供了一系列静态的方法对集合元素进行排序,查找和修改等操作

// Collections工具类中常用方法
// 创建测试类
ArrayList arrayList = new ArrayList();
arrayList.add("cyj");
arrayList.add("lns");
arrayList.add("zlr");
System.out.println(arrayList); // [cyj, lns, zlr]
// reverse(List集合) 反转List中元素的顺序
Collections.reverse(arrayList);
System.out.println(arrayList); // [zlr, lns, cyj]
// shuffle(List集合) 对List集合元素进行随机排序
for (int i = 0; i < 3; i++) {
    Collections.shuffle(arrayList);
    System.out.println(arrayList); // 随机出现
}
// shuffle方法的源码
public static void shuffle(List<?> list) {
    Random rnd = r;
    if (rnd == null)
        r = rnd = new Random(); // harmless race.
    shuffle(list, rnd);
}
//  sort(List集合) 根据元素的自然排序对List集合进行升序 比如:字符串比较的是字符大小
arrayList.add("aaa");
Collections.sort(arrayList);
System.out.println(arrayList); // [aaa, cyj, lns, zlr]
// 用比较器Comparator自定义规则进行排序 sort(arrayList,new Comparator(){});
Collections.sort(arrayList,new Comparator(){
    @Override
    public int compare(Object o1, Object o2) {
        return ((String)o2).compareTo((String)o1);
    }
});
System.out.println(arrayList); // [zlr, lns, cyj, aaa]
// swap(List集合,int i,int j) 交换集合索引为i和j的位置
Collections.swap(arrayList,1,2);
System.out.println(arrayList); // [zlr, cyj, lns, aaa]
// max(Collection集合) 返回给定集合中自然排序最大的元素
System.out.println(Collections.max(arrayList)); // zlr
// max(Collection集合,new Comparator(){}) 返回给定集合中自治排序最大的元素
System.out.println(Collections.max(arrayList,new Comparator(){
    @Override
    public int compare(Object o1, Object o2) {
        return ((String)o2).compareTo((String)o1);
    }
})); // aaa
// frequency(Collection集合,集合中的元素) 返回该元素在集合中出现的频率
System.out.println(Collections.frequency(arrayList,"aaa")); // 1
// copy(List dest,List src) 复制src的元素到dest集合中
// 注意: 目标集合元素个数必须大于等于原来集合元素的个数
ArrayList dest = new ArrayList();
for (int i = 0; i < 6; i++) {
    dest.add(i);
}
Collections.copy(dest,arrayList);
System.out.println(dest); // [zlr, cyj, lns, aaa, 4, 5]

相关面试题

// 补充测试题
// 注意remove方法 是否能删除
HashSet hashSet = new HashSet();
Person person = new Person(1, "lns");
Person person2 = new Person(1, "zlr");
hashSet.add(person);
hashSet.add(person2);
System.out.println(hashSet); // [Person{id=1, name='lns'}, Person{id=1, name='zlr'}]
person.setName("4"); // 当重新设置名字,改变了hash值间接改变了索引,会导致接下来的删除不成功
hashSet.remove(person);
System.out.println(hashSet); // [Person{id=1, name='4'}, Person{id=1, name='zlr'}]

Java中有哪些容器(集合类)?
Java中的集合类主要由Collection和Map这两个接口派生而出,其中Collection接口又派生出三个子接口,分别是Set、List、Queue。所有的Java集合类,都是Set、List、Queue、Map这四个接口的实现类,这四个接口将集合分成了四大类,其中

Set代表无序的,元素不可重复的集合;

List代表有序的,元素可以重复的集合;

Queue代表先进先出(FIFO)的队列;

Map代表具有映射关系(key-value)的集合。

这些接口拥有众多的实现类,其中最常用的实现类有HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap等。

Java中的容器,线程安全和线程不安全的分别有哪些?

java.util包下的集合类大部分都是线程不安全的,例如我们常用的HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap,这些都是线程不安全的集合类,但是它们的优点是性能好。如果需要使用线程安全的集合类,则可以使用Collections工具类提供的synchronizedXxx()方法,将这些集合类包装成线程安全的集合类。

java.util包下也有线程安全的集合类,例如Vector、Hashtable。这些集合类都是比较古老的API,虽然实现了线程安全,但是性能很差。所以即便是需要使用线程安全的集合类,也建议将线程不安全的集合类包装成线程安全集合类的方式,而不是直接使用这些古老的API。

从Java5开始,Java在java.util.concurrent包下提供了大量支持高效并发访问的集合类,它们既能包装良好的访问性能,有能包装线程安全。这些集合类可以分为两部分,它们的特征如下:

  • 以Concurrent开头的集合类:

    以Concurrent开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。

  • 以CopyOnWrite开头的集合类:

    以CopyOnWrite开头的集合类采用复制底层数组的方式来实现写操作。当线程对此类集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对此类集合执行写入操作时,集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。

描述一下Map put的过程

如上述我的HashMap和Hashtable的与添加源码分析

img

如何得到一个线程安全的Map?

  1. 使用Collections工具类,将线程不安全的Map包装成线程安全的Map;
  2. 使用java.util.concurrent包下的Map,如ConcurrentHashMap;
  3. 不建议使用Hashtable,虽然Hashtable是线程安全的,但是性能较差。

说一说你对LinkedHashMap的理解

LinkedHashMap使用双向链表来维护key-value对的顺序(其实只需要考虑key的顺序),该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。

LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可),同时又可避免使用TreeMap所增加的成本。

LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能。但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。

请介绍TreeMap的底层原理

TreeMap基于红黑树(Red-Black tree)实现。映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操作containsKey、get、put、remove方法,它的时间复杂度是log(N)。

TreeMap包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据根据Key排序,包含的内容是value。Entry中key比较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Al_tair

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

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

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

打赏作者

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

抵扣说明:

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

余额充值