Java集合类(Collection)详解及源码分析

本文深入剖析Java集合类,包括Collection接口的常用方法、List接口及其具体实现如ArrayList、LinkedList和Vector的异同、Set接口的HashSet、LinkedHashSet、TreeSet特点以及Map接口的HashMap、LinkedHashMap、TreeMap和Properties的实现原理。详细分析了源码,探讨了添加元素、遍历、扩容和排序等关键操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java集合类

两耳不闻窗外事,一心只读圣贤书。

1.概述

Java集合类主要是Collection接口下的单列集合和Map接口下的K-V键值对类型的集合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HcTlpSSU-1644035373984)(F:\StudyNotepad\img\image-20211208170010452.png)]

2.Collection

2.1 Collection 中的常用方法

contains(Object o)

contains方法的作用是查询当前集合中是否存在所传入的值。

查询的方式是查询传入对象的hashcode。

举例:

Collection coll = new ArrayList();
// 对比测试实例化一个对象的hashCode
Person per1 = new Person("Tom");
Person per2 = new Person("Tom");
System.out.println(per1.hashCode());
System.out.println(per2.hashCode());
// 对比测试一个字符串对象的hashCode
String jack1 = new String("Jack");
String jack2 = new String("Jack");
System.out.println(jack1.hashCode());
System.out.println(jack2.hashCode());

coll.add(new Person("Tom"));
coll.add(new String("Jack"));
// 返回结果为false,hashCode不同。
System.out.println(coll.contains(new Person("Tom")));
// 这里返回的结果是True,说明contains方法查询的是hashCode值。
System.out.println(coll.contains(new String("Jack")));

结论:String类已经重写了equals方法,因此最后的hashCode是相同的。(重写的比较的是值来定义hashcode)

但是普通的类用的Object默认的equals,直接比较的内存地址,因此对于用户自定义的类,使用contains方法的结果是false。

本质是调用传入对象的equals方法来比较查询。

如果需要调用contains方法时,传入的对象需要重写equals方法。

containsAll(Collection coll)

传入的集合中的数据是否都存在需要查询的集合中。

注意:只有全部都在才会返回true。

remove(Object o)

通过equals来进行判断是否存在元素,如果存在就进行删除,删除成功返回true。

删除失败返回false。

removeAll(Collection coll)

从当前集合中移除传入的集合中的元素。

移除的是两个集合的交集。

retainAll(Collection coll)

获取两个集合的交集,并修改当前的集合。

使当前集合中只存在两个集合共同存在的。

equals(Object o)

比较两个集合是否相同。

注意:ArrayList是有序的,因此比较时顺序会影响结果,如果顺序不同也会返回false。

hashCode()

返回hashCode值,如果没有重写就是返回内存地址。

如果重写了hashCode方法,则返回的结果自定义。

toArray()

返回相应类型的数组。

如果没有使用泛型,那么默认是Object[]型数组。

补充:如果将数组转换为集合?

通过Arrays.asList([对象数组]),来进行数组到集合的转换。

注意:

​ 如果传入的是基本数据类型的数组(int、boolean等),那么会将传入的整个数组当成一个对象进行处理,只有转换为相应的包装类对象才能达到想要的结果。

List<int[]> ints = Arrays.asList(new int[]{1, 2, 3});
// 虽然我们上面定义的数组含有多个元素,但是因为是基本数据类型,list中需要存放的是
// 对象数据类型,那么就会将整个数组当成一个对象进行处理,最后输出的结果:长度为1。
System.out.println(ints.size());
// 解决办法:将基本数据类型的数组转换为包装类对象进行传入 ==》 new Integer[]
// 通过包装类对象以后就能正常将传入的数组转换为list
List<Integer> integers = Arrays.asList(new Integer[]{1, 2, 3});
System.out.println(integers.size());
iterator()

返回一个迭代器实例。

每次调用方法都会返回新的一个迭代器实例对象。

迭代器实例就是用于帮助我们遍历集合。

在Java设计模式中有详细的介绍。

注意:

​ Map中不是通过迭代器进行遍历,迭代器遍历主要引用与Collection中的集合。

  • hasNext()

    判断是否含有写一个

  • next()

    先指针下移,然后再输出元素

  • remove()

    移除当前遍历到的元素,先next以后才能remove,如果没有next直接remove那么会爆出:IllegalStateException

    原因:next开始是-1,那么无法删除,还有一种情况也会爆出这个异常,next以后进行了两次remove。

    同理,remove是删除当前元素,删除以后指针并没有发生改变,如果在remove就会爆出异常。

集合的遍历除了迭代器以外,还有一种是forEach,这也是比较常用的一种方式。

语法:for([Object类型] [集合中具体的元素]:[集合]){}

本身也是通过迭代器实现。

不能通过修改遍历出的值,来修改集合对象中的值。

原因:

​ 本身foreach是通过将集合中的值取出然后赋值到一个对象上,那么如果我们通过foreach来修改遍历出的值,是对本身集合对象是没有任何影响的。

forEach(Consumer action)

Collection类中默认是可以通过这个方法进行forEach遍历循环。

coll.forEach(System.out ::println);	

2.2 List

Collection中的一个子类接口。

存储有序的、可重复的数据。

2.2.1 概述
  • ArrayList
  • LinkedList
  • Vector
2.2.2 ArrayList、LinkedList、Vector的异同
相同点
  • 三个类都实现了List接口
  • 存储的数据都是有序、可重复的
不同点
  • ArrayList是作为List主要实现类(首选)

    线程不安全,效率高

    底层是通过Objec[]数组存储,动态扩展

  • Vector是List接口的古老实现类

    推出Java就已经存在的一个类(V 1.0)出现,List(V 1.2)才出现。

    线程安全,效率低

    底层是通过Objec[]数组存储,动态扩展

  • LinkedList通过链表实现和另外两种不同实现方式不同

    双向链表实现

    线程不安全

    对于增加/删除效率比数组实现的List类效率更高,同理查找没有数组实现的List类高

2.3 List中的实现类源码分析

2.3.1 ArrayList

构造方法,无参构造默认大小为10,但是不会再实例化对象时创建数组,而是创建一个空数组,当进行第一次执行add()方法时才会进行创建。

扩容为1.5倍加1。

  • Object[] elementData实现,动态扩容

    transient Object[] elementData; 
    
  • new实例对象时,可以指定大小,如果没有指定,那么默认创建大小为10

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
常用方法

下面演示add()方法。

其他方法类似在进行操作之前都会进行一些数组上面的判断。

add()

先判断,再添加。数组长度不够,扩容,每次扩容原先数组的1.5倍。

不是无止境的扩容,最大值为int类型最大值。

结论:建议在创建ArrayList时指定大小。

源码

public boolean add(E e) {
    // 添加元素前会进行容量判断如果容量够进行添加
    // 如果不够会进行扩容
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
// 扩容判断
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 判断当加入这个元素以后是否会超过数组的长度
    if (minCapacity - elementData.length > 0)
        // 如果加入元素超过了数组的长度,进行扩容
        grow(minCapacity);
}
// 进行扩容
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 位运算,右移一位,缩小2倍,加上本身 ==》 扩容1.5倍 + 1
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 老数组到新数组的复制
    elementData = Arrays.copyOf(elementData, newCapacity);
}
2.3.2 LinkedList

双向链表实现。

默认add()方法添加到链表尾部。

类中维护一个头结点和尾结点。

Node源码

Node中添加的Object通过泛型指定。

private static class Node<E> {
    E item;
    // 双向指针
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
2.3.3 Vector

Java8中默认实例时,就已经创建。

扩容时,扩大的倍数为原先的2倍。

一般不用,如果线程不安全也是通过Collection中的方法实现线程安全。

2.4 List中的常用方法

add(int index, Oject o)

将对象插入到下标为index的上面。

addAll(Collection coll)

将集合中的所有数据依次加入到目标集合中。

get(int index)

获取指定下标元素。

indexOf(Object o)

对象在数组中第一次出现的下标。

如果没有在集合中查找到,那么返回-1。

lastIndexOf(Object o)

对象最后出现的位置。

不存在,返回-1。

remove(int index/Object o)

根据下标或对象来删除。

返回删除的对象。

set(int index,Object o)

将指定下标上的值修改传入的对象。

subList(int fromIndex,int toIndex)

返回一个List。

区间为左闭右开。

对本身List不会造成影响。

2.5 Set

Set:无序、不可重复。

HashSet、LinkedHashSet、TreeSet

Set接口本身没有额外定义新的方法,使用的都是Collection中声明的方法。

HashSet、LinkedHashSet底层是数组,默认大小为16,加载因子为0.75,每次扩容2倍

加载因子:当数组现有元素的大小超过了容量的加载因子大小(size > 容量 * 加载因子)以后会进行扩容。

  • HashSet:作为set接口的主要实现类,线程不安全,可以存储null值(首选),底层实现HashTable(数组+链表)

  • LinkedHashSet:是HashSet的子类,添加了指向使在遍历时按照添加的顺序输出。(伪有序,本身不是有序的)

  • TreeSet:使用红黑树(二叉树的一种)存储结点。可以按照结点中的某些属性进行排序。

2.5.1 对于无序、不可重复的理解

以HashSet说明。

无序性

  • 不等于随机性,每次遍历出的结果都相同
  • 数据在底层的存储顺序不是按照数组索引添加,而是无序添加,而是根据数据的hashCode进行排序添加

不可重复性

  • 调用的是集合中对象的equals方法进行判断是否在集合中重复

    需要重写equals和hashCode方法,不然添加对象直接判断内存地址,无法进行重复性的判断。(有些类本身就已经重写了这两个方法,比如:String等)

2.5.2 添加元素到Set的过程

以HashSet说明。

  1. 计算到需要添加到集合对象的hashCode
  2. 根据计算的hashCode确定这个元素在数组的位置(某种算法),位置为空,直接添加
  3. 如果存放的位置发现这个数组位置已经有元素了(只能说明存放的位置相同,不能说明hashCode值相同)
    • 先比较hashCode,如果相同,那么再进行equals比较
      • 调用新添加数据的equals方法,如果返回是true,那么添加失败;如果返回false,添加成功,形成链表
    • 比较的hashCode不同,那么就会和当前数组位置上已有的数据形成一个链表

通过上面的源码分析,也可以知道Set的无序性,本身添加不是按照顺序添加下去,而是通过某种算法进行添加元素到集合中。

只有hashCode和equals方法都相同才会添加失败。

补充:hashCode和equals方法的重写
为什么重写hashCode方法会把31当做系数?
  • 选择系数要尽可能的大,系数越大计算得到hashCode越大,那么可能发生冲突的可能性越小(减少冲突)

  • 31只占用4bits,相乘造成数据溢出的概念较小

  • 31可以用2的5次方-1,现在很多虚拟机都有相关的优化(提高算法效率)

  • 31是一个素数(减少冲突)

现在的编译器一般都有相关的重写,直接调用即可。

结论:推荐重写hashCode和equals方法,对象中的所有属性都应该出现在hashCode的计算中。

2.5.3 LinkedHashSet

遍历是顺序的,但是本身存储还是无序的,通过链表实现有序遍历。

在HashSet的基础上添加了链表结构,通过LinkedHashMap实现。

优点:如果需要频繁的遍历,效率更高

2.5.4 TreeSet

TreeMap实现。添加的元素必须是相同类的对象。

可以根据对象的指定属性进行排序。

默认为从小到打进行排序。

如果添加到TreeSet中的是自定义对象,一定要有排序方法。

结论:TreeSet中的保存的对象一定是相同类,并且这个类必须要有排序方法(实现相关接口 – Comparable,重写相关方法)

注意:TreeSet中比较添加元素是否重复不是通过hashCode和equals方法,而是通过compareTo方法。如果compareTo返回的结果为0,那么添加失败。

优点:查询速度比List更快。(红黑数)

定制排序

可以在创建TreeSet时,可以添加排序规则。

Comparator com = new Comparator(){
	[重写排序规则]
}

TreeSet treeSet = new TreeSet([排序规则])

2.6 总结

  1. 使用Collection类保存的对象需要重写equals方法,推荐hashCode方法一同重写(直接使用编译器的相关方法即可)
  2. Collection的很多方法都是调用集合中对象的equals和hashCode方法进行判断
  3. List类添加元素会进行数组大小判断(动态扩容,LinkedList通过链表实现)
  4. Set元素的添加需要判断是否重复,判断重复不需要遍历整个集合,通过相关算法实现元素的添加(hashCode和equals方法共同实现)
  5. HashSet、LinkedHashSet底层通过hashTable实现(HashMap),LinkedHashSet在HashSet的基础上添加链表实现有序的遍历,但本身都是无序的存储
  6. TreeSet底层通过数组+红黑树实现(TreeMap),保存的元素要求必须是相同类,添加元素的不可重复性通过添加对象的compareTo方法比较是否重复,如果添加的元素没有实现相关方法,也可以在创建TreeSet时传入一个比较规则
  7. TreeSet如果添加元素比较的结果为0则添加失败,其他Set的实现类,只有hashCode和equals完全相同才会添加失败
  8. 遍历的方法,获取到相关迭代器或者使用增强FOR循环(foreach)
  9. TreeSet的遍历按照排序规则输出
  10. HashSet底层是HashMap,但是不能设定Key值,只有value,是因为,所有HashSet的Key都是固定相同的。是有的,只是被固定了。通过后面的分析,也可以知道为什么Set不能重复,因为在Map中Key本身就是不可重复的。

3.Map

双列数据,存储K-V键值对数据。

3.1 概述

  • HashMap:主要的实现类,线程不安全,效率高;可以存储null的K/V值

    HashMap的线程安全使用CurrentHashMap

    • LinkedHashMap:保证在遍历时,是有序的,在原有的HashMap基础上添加了一对指针(前、后),频繁的遍历操作,使用它
  • TreeMap(V 1.0):古老的实现类,比Map(V 1.2)还早,可以保证按照添加的K-V对进行排序,实现排序遍历(按照Key的规则排序);使用红黑树实现

  • Hashtable(注意:API中的t是小写):线程安全,效率低;不能存储null的K/V值

    • Properties:常用来保存配置文件;K和V必须为String类型

HashMap、Hashtable在JDK1.7之前为数组加链表,在JDK1.8以后的实现为数组加红黑树。

3.2 对K-V键值对结构的理解

  • Key不能重复且无序(理解为通过Set存储)

    • 对于HashMap中Key所在的类需要重写hashCode和equals方法,同理TreeMap根据排序规则存储
  • Value可重复且无序(因为Key是无序的)

    • Value需要重写equals方法

      Value更多是用在查询中,所以只用重写equals方法,但是Key多用于在存储中,为了提高效率需要重写hashCode和equals方法。

  • K-V通过Entry包装存储(Map是包装为Node结点)

  • Entry不可重复且无序(Key的特点)

3.3 HashMap

JDK1.8版本。

####3.3.1 概述

  • 实例化以后,底层不会创建数组,会在第一次添加以后才会创建数组 – Node[] node
  • 存入数据 – map.put(key1,value1);
    • 首先调用value所在类的hashCode方法,得到hashCode值,通过某种算法(与或运算)得到在数组中的位置
    • 如果数组上没有数据,直接添加成功
    • 如果数组上有数据(一个或多个),多个数据以链表的形式存在
      • 和已经存在的数据进行hashCode比较
      • 如果和数据都不相同(hashCode值),那么直接添加成功
      • 如果hashCode值和已经存在的数据的某一个hashCode相同,继续比较equals方法(调用传入对象的equals)
        • 如果equals方法返回false,则对象添加到链表上
        • 如果是true,则用传入的数据替换已经存在的(修改)
  • 在添加数据时的扩容,当触发到加载(负载)因子且要添加的位置非空,进行扩容,默认扩容为原来的2倍
    • 加载因子为0,75
    • 最大支持容量:2的30次方

JDK1.8中结构为:数组 + 链表 + 红黑树。

当数组上的某一个索引上的链表存在的数据个数,大于8且当前数组的长度大于64,此时此索引位置上的所有数据改为红黑树存储。

红黑树优点:遍历块,方便查找。

3.3.2 源码分析
1.封装结点 – Node<K,V>

所有需要存储在HashMap中的数据都是将传入的Key-Value封装到结点中,然后HashMap保存这些结点。

  1. 通过静态内部类在HashMap中实现
  2. 重写了hashCode和equals方法
  3. 结点本身具有下一个指向,链表结构
  4. 可以修改Key上的Value值,但是不能修改Key的值 – setValue(V newValue)
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;
   }
}
2.构造器
  1. 无参构造器,创建时不会创建数组,使用HashMap中定义的默认负载因子(0.75f)
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}
  1. 有参构造器有两种,一种直接传递初始HashMap大小,另外一种除了可以设置大小以外还可以设置负载因子
    • 我们可以指定初始大小,但是HashMap中的大小一定是2的次方,所以可能我们设置的大小不是最终的大小(比如:初始值为15,就不会创建大小为15的HashMap,而是16)
    • 有参构造1,实际也是调用的有参构造2
// 有参构造1
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 有参构造2
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);
    this.loadFactor = loadFactor;
    // 真实大小设置
    this.threshold = tableSizeFor(initialCapacity);
}
// 有参构造3 -- 传入一个Map
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
// 3.1 调用的putMapEntries(m, false)方法
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}
3.添加元素 – put(K key, V value)

在调用put方法时,就会计算出key的hash值。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
实际实现方式 – putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
  1. Node[],HashMap的底层实现之一,管理着一个Node数组
  2. 当容量为null或者长度为0时,就会对HashMap进行创建一个容量为16的数组,这就是第一次添加元素时,HashMap才会创建的数组的原因 – resize()
  3. i = (n - 1) & hash,计算添加元素在HashMap中的位置
  4. 链表满足一定条件以后,变为红黑树 – treeifyBin(tab, hash)
  5. 添加完以后,还会进行一次HashMap大小判断
  6. 添加结点的过程,看前面概述
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
   Node<K,V>[] tab; Node<K,V> p; int n, i;
   // 首次添加才会进行HashMap的创建
   if ((tab = table) == null || (n = tab.length) == 0)
      n = (tab = resize()).length;
   // 如果当前数组上的位置为空,直接添加
   if ((p = tab[i = (n - 1) & hash]) == null)
      tab[i] = newNode(hash, key, value, null);
   else {
      // 不为空进行判断
      Node<K,V> e; K k;
      if (p.hash == hash &&
          ((k = p.key) == key || (key != null && key.equals(k))))
         e = p;
      // 红黑树的判断,链表变为红黑树,添加结点
      else if (p instanceof TreeNode)
         e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
      else {
         // 和链表上的所有数据进行比较hashCode
         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;
         }
      }
      // 修改老的值 -- hashCode值相同,并且equals方法返回为真,满足这两个条件,认为就是完全相同的,修改老数据的值
      if (e != null) { // existing mapping for key
         V oldValue = e.value;
         if (!onlyIfAbsent || oldValue == null)
            e.value = value;
         afterNodeAccess(e);
         return oldValue;
      }
   }
   ++modCount;
   if (++size > threshold)
      resize();
   afterNodeInsertion(evict);
   return null;
} 
链表变为红黑树 – treeifyBin(Node<K,V>[] tab, int hash)
  1. 一条链表上的结点数超过8,在添加新的元素时,进入这个方法,但不会直接变为红黑树,进行下一步的判断
  2. 如果当前HashMap的容量小于64,不会变红黑树,而是使HashMap进行扩容
  3. 如果当前链表上的结点超过8且HashMap的容量大于64,变为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
   int n, index; Node<K,V> e;
   if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
      resize();
   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);
   }
}
补充

关于负载因子的大小问题?

为什么负载因子为0.75?通过上面的分析,我们可以看到HashMap有扩容的问题,而扩容的临界值就与负载因子有关。

如果负载因子过小,就会频繁的进行扩容,就是会导致数组中的利用率不高。

如果负载因子过大,扩容会减少,但数组上的每一条链表的长度就会过长,不利于查找,所以HashMap默认为0.75,是一个比较好的结果。

3.4 LInkedHashMap

HashMap的一个子类,可以实现遍历结果为添加顺序。

大部分方法(如:put())都是使用的父类 – HashMap中的方法,但是重写了newNode方法,给添加的Node中添加了两个指向,因此可以在遍历的时候按照添加顺序输出。

优点:频繁遍历,效率更高。

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) {
        super(hash, key, value, next);
    }
}

3.5 TreeMap

和TreeSet相同原理。但是通过K-V键值对添加,通过Key指定规则进行排序。

3.6 Properties

Hashtable的一个子类。Key和Value被固定为String类型,通常用于配置文件。

Properties pro = new Properties();
// 创建IO流读取指定文件
FileInputStream is = new FileInputStream("xxx.properties");
// 加载IO流
pro.load(is);

// 获取配置文件中的value
String key = pro.getProperty("key");

3.7 Map中的常用方法

put(Object key,Object value)

将指定Key-Value添加到当前map中

相同的Key添加时,变为修改

putAll(Map map)

将指定Map添加到Map中

remove(Object key)

删除指定Key-Value,返回值为value,不存在返回null

clear()

清空map中的数据,并不是将 map = null。大小还存在,只是没有数据了。

get(Object key)

返回指定Key的Value,不存在返回null

containsKey(Object key) / containsValue(Object value)

查询是否包含指定的Key/Value,不存在返回null

因为Value是可以重复的,找到第一个指定的返回true不能继续往下寻找

isEmpty()

空,返回true;不为空,返回false。

equals(Object o)

Object一定也需要是一个Map,判断是否相同。

keySet()

返回一个所有Key构成的Set集合

values()

返回一个所有Value构成的Collection

遍历的顺序是按照Key的遍历顺序

entrySet()

返回一个所有entry构成的Set

通过得到的Entry,可以调用每一个Entry中的getKey和getValue方法,得到需要的数值

遍历

1.通过for循环遍历,但是一定要通过泛型指明Key-Value的数据类型,不然无法遍历。

2.通过entrySet获得到的Set集合,然后获得集合的迭代器进行遍历。

HashMap<Integer,Integer> map = new HashMap();

map.put(1,1);
map.put(2,2);
map.put(3,3);
// 方式一
for (Map.Entry<Integer,Integer> entry : map.entrySet()){
   System.out.println(entry.getKey() + entry.getValue());
}
// 方式二
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
Iterator<Map.Entry<Integer, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
    Map.Entry<Integer, Integer> next = iterator.next();
    System.out.println(next.getKey() + next.getValue());
}

推荐使用第一种

4.Collections

Collections是一个操作Set、List和Map等集合的工具类。

4.1 常用的方法

reverse(List list)

反转有序的List(list本身就是有序的),将本身的List给修改。

shuffle(List list)

对List集合中的数据进行随机排序。

sort(List list,[定制排序])

如果没有传入排序规则就根据存储数据类型的compare进行排序(存储数据需要实现相关接口),如果传入排序规则,则按照排序规则进行排序。

swap(List list,int i,int j)

将指定下标的元素进行交换。

max(Collection coll)/min(Collection coll,[指定排序])

返回最大传入集合中的最大/最小元素。

copy(List dest,List src)

将src中的内容复制到dest中。

src数组大于dest的长度,复制失败,报出异常。

解决方式:List dest = Arrays.asList(new Object[src.size()]);

replaceAll(List list,Object oldVal,Object newVal)

所有的旧值改为新值。

转换为线程安全的方法

Collections.synchronizedxxx([传入需要转换的集合])。

调用相关方法转换为线程安全的。

List<Object> list = Collections.synchronizedList(list);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lkWlkifS-1644035373986)(F:\StudyNotepad\img\image-20211210195545208.png)]

5.其他

1.Arrays.asList()

问题:通过数组工具类中的asList方法将数组转换为List以后,使用相关如:add、remove等方法,报错

public static void main(String[] args) {
   int[] arr = {1, 2, 3, 4};

   List<int[]> asList = Arrays.asList(arr);
   asList.add(new int[]{5, 6});
}

问题分析:这是因为通过asList方法得到的List是Arrays的内部类。

而这个内部类中并没有相关方法,因此会报错。

解决方法:将得到的集合成为新实例集合的参数即可。

public static void main(String[] args) {
   Integer[] arr = {1, 2, 3};
   List<Integer> integerList = Arrays.asList(arr);
   // 解决
   List<Integer> list = new ArrayList<>(integerList);
   list.add(4);

   System.out.println(list);
}

底部

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值