【Java】韩顺平Java学习笔记 第14章 集合

第14章 集合

1.框架体系 ( 背下来 ! )

  • 难点:
    • 原理
    • 实现方法
    • 什么时候用?
  • 单列集合——Collection
    • List
      • ArrayList
      • LinkedList
      • Vector
    • Set
      • HashSet
      • TreeSet
  • 双列集合——Map
    • HashMap
      • LinkedHashMap
    • TreeMap
    • Hashtable
      • Properties
  • 单列:一个个、单个单个的对象,双列:键—值对
  • 这些集合存储的都是对象的引用,而不是对象的副本,因此修改原始对象,集合里的对象内容也会被修改!

2.Collection接口

常用方法(Collection不能直接被实例化)

  • add
  • remove (可以按索引,也可以按对象)
  • contains
  • size
  • isEmpty
  • clear
  • addAll
  • containsAll
  • removeAll
  • 注:最后三个方法传入实现Collection接口的对象

遍历方式1:iterator 迭代器

  • 得到iterator:调用Collection实例化对象的iterator方法

    Iterator iterator = col.iterator()

  • iterator方法

    • next
    • hasNext**(用next前必须用hasNext检测!否则会抛出异常)**
    • 快捷指令itit
    • 显示所有的快捷指令Ctrl + J
    • 再次遍历,需要重置迭代器

遍历方式2:增强for循环

  • 增强for等于简化版的迭代器iterator
  • 只能用于数组和Collection集合
  • 底层还是迭代器
  • 快捷指令 I

3.List 接口(List特有,Set没有——细化)

  • 特点
    • 元素有序
    • 可以重复
    • 支持索引
  • 常用方法
    • add ( int index , Object ele )
    • addAll ( int index, Collection eles )
    • get**(注:不能使用 [ ] 随机访问!)**
    • indexOf
    • lastIndexOf
    • remove( int index )
    • set ( 替换,索引必须存在 )
    • subList
  • 遍历方式
    • iterator
    • 增强for
    • 普通for

4.ArrayList 类

  • 注意事项

    • 可以放所有类型的元素,包括NULL
    • 底层是用数组实现的
    • 线程不安全(执行效率高),多线程时不适用
  • 扩容机制

    • ArratList维护了一个Object类型的数组 -> ArrayList可以存放所有类型数据

      transient Object[] elementData;

      transient 瞬间,短暂的;表示该属性不会被序列化

    • 创建ArrayList用无参构造器 -> 初始容量为零 -> 第一次扩容容量为10 -> 之后每次扩容是原来的1.5倍

    • 有参构造器 -> 初始容量为设定值 -> 之后每次扩容是原来的1.5倍

    • 扩容过程**(看源码!!!)**

      • 创建element数组

        transient Object[] elementData; // non-private to simplify nested class access
        
      • 赋默认初值为零

            public ArrayList() {
                this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
            }
        
      • 加入数据的add方法,modCount是操作数组的次数

          public boolean add(E e) {
                modCount++;
                add(e, elementData, size);
                return true;
            }
        
      • 另一个add方法,如果size大小和length长度相等,则用grow方法进行扩容,否则正常存入数据

          private void add(E e, Object[] elementData, int s) {
              if (s == elementData.length)
                  elementData = grow();
              elementData[s] = e;
              size = s + 1;
          }
      
          private Object[] grow() {
              return grow(size + 1);
          }
      
      • oldCapacity是原来的长度

        传入的参数是size + 1,若使用无参构造器,第一次size就是0,这里的minCapacity就是1

        如果是之后的拓展,minCapacity就是现在的容量+1,minCapacity就是“至少需要的容量”的意思

        如果原来的长度不是零,即不是默认初值时,进入if语句

        newCapacity接收新的容量大小,用copyOf复制原来的数组,以免丢失数据

        如果原来的长度是0,则返回一个新建的Object数组,这个数组就是elementData

            private Object[] grow(int minCapacity) {
                int oldCapacity = elementData.length;
                if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                    int newCapacity = ArraysSupport.newLength(oldCapacity,
                            minCapacity - oldCapacity, /* minimum growth */
                            oldCapacity >> 1           /* preferred growth */);
                    return elementData = Arrays.copyOf(elementData, newCapacity);
                } else {
                    return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
                }
            }
        
      • 接下来看if语句块里的newLength方法

        这里传入了三个参数:oldLength是旧的长度,传入了oldCapacity,对应;minGrowth是最少增加的长度,传入了minCapacity - oldCapacity;prefGrowth是想要增加的长度,传入了oldCapacity >> 1,这里是位运算,向右移动一位,等于原来大小的二分之一,这就是之后每次增加的大小是原来的1.5倍的原因

        prefLength 计算了最终增加的长度

        SOFT_MAX_ARRAY_LENGTH 是在不溢出前提下的最大值

        如果可以满足想要的prefLength长度,那么新的长度就是prefLength;否则进入处理方法hugeLength

         public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
                // preconditions not checked because of inlining
                // assert oldLength >= 0
                // assert minGrowth > 0
        
                int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
                if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
                    return prefLength;
                } else {
                    // put code cold in a separate method
                    return hugeLength(oldLength, minGrowth);
                }
            }
        
      • hugeLength代码如下,如果增加的长度为负,抛出异常。如果现在总长度加上最小增加的场长度正好达到最大值,则返回最大值。否则只能增长minGrowth,即需要的最小长度

           private static int hugeLength(int oldLength, int minGrowth) {
                int minLength = oldLength + minGrowth;
                if (minLength < 0) { // overflow
                    throw new OutOfMemoryError(
                        "Required array length " + oldLength + " + " + minGrowth + " is too large");
                } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
                    return SOFT_MAX_ARRAY_LENGTH;
                } else {
                    return minLength;
                }
            }
        
      • 有参构造时,建立elementData数组的代码如下:

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

        只有创建的过程不一样,接下来添加数据的过程与无参构造是一样的

    • 注意:IDEA 在默认情况下,Debug显示的数据是简化的


5.Vector 类

  • 注意事项
    • Vector底层也是用Object 对象数组实现的
    • Vector 是线程同步的(效率不高),线程安全(方法基本都有 synchronized 关键字),在开发中需要线程同步安全的考虑用Vector
  • 扩容机制
    • 无参,默认大小是10,之后每次按2倍来扩容
    • 有参,大小是传入的参数,之后每次按2倍来扩容
    • (追源码的过程和ArrayList相似,不再写出)

6.LinkedList 类

  • LinkedList 底层维护了一个双向链表
  • 维护了两个属性 first 和 last,分别指向头节点和尾节点
  • 有内部类Node,里面又有prev next item三个属性
  • 注:remove方法默认删除的是第一个元素
  • LinkedList 使用了 List 接口,所以可以使用迭代器和增强 for 循环遍历

7.ArrayList 和 LinkedList 的选择

  • 如果改查的操作比较多,用 ArrayList
  • 如果增删操作较多,用 LinkedList
  • 在实际开发中,八九成的时间都是查询,因此大部分时间用ArrayList
  • 项目中可能一个模块用ArrayList , 另一个模块用LinkedList

8.Set 接口

  • 注意事项

    • 不允许重复元素或对象,因此最多一个 NULL。添加重复元素时,加入失败,add方法返回false

      set.add("lucy");	//可以添加
      set.add("lucy");	//都是常量池中的数据,相同,不能添加
      set.add(new Dog());
      set.add(new Dog());	//虽然都是狗对象,但却是不同的对象,因此两个都可以加入
      set.add(new String("hsp"));
      set.add(new String("hsp"));	//加入不了-> 看源码做分析
      
    • 存放元素无序,添加和取出元素的顺序并不一样,但是取出的顺序是固定的,不会改变

    • Set 接口对象 实际上指 “Set接口的实现类的对象”

  • **常用方法和遍历方式和Collection接口一样,但注意不能用随机访问的方式遍历(没有get方法)-> 不能用普通for循环遍历 **


9.HashSet 类

  • 底层是HashMap -> HashMap底层是数组、链表、红黑树

  • add底层源码,不能加入重复元素 / 对象的真正含义

    • 底层 HashMap 用的是拉链法(存储效率高)

    • 添加步骤

      1. 添加一个元素,先得到 hash 值,再转化为索引值
      2. 在 table 表中查找该索引值,看该位置是否有元素。如果没有,就直接添加。
      3. 如果有元素,就用equals方法(可以重写)进行比较,相同就放弃添加,不同就放到该索引值对应的链表的最后面
      4. table 创建时初始值是16,到临界值(threshold)12时,就扩容到 16*2=32,新的临界值是32 * 0.75(临界因子) = 24,依此类推**(注:threshold临界值计算时也包括链表里面的那些节点,加了一个节点,size就加一)**
      5. Java8中:一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且 table 大小 >= MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树)。如果table大小没达到要求,就将table大小翻倍

add过程分析

按以下代码添加:

    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");	//第一次添加
        hashSet.add("php");		//第二次添加
        hashSet.add("java");	//第三次添加
    }
  1. 第一次添加分析
   //add方法
   //里面为map的添加方法,PRESENT为一个参数,先不用管它
      public boolean add(E e) {
           return map.put(e, PRESENT)==null;
       }
   //如果返回为null,则返回true,即添加成功,否则返回false,即添加失败
   //put方法
   //hash(key)能求出传入添加元素的索引值,key为要添加的值
       public V put(K key, V value) {
           return putVal(hash(key), key, value, false, true);
       }
   //hash方法,求出索引值
       static final int hash(Object key) {
           int h;
           return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
       }
   //如果值为空,则返回0
   //否则,求出索引值的算法:(h = key.hashCode()) ^ (h >>> 16)
   // h >>> 16 表示h逻辑右移16位,将key的hashCode(哈希值)与其按位异或,就得到了索引值
   //putVal方法
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                      boolean evict) {
        //创建一个tab来保存哈希表,table就是系统已经创建好的哈希表
           Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
           if ((tab = table) == null || (n = tab.length) == 0)
               //第一次创建时哈希表为空,进入if语句里
               //if语句表示如果哈希表为空,就进行第一次扩容,到16个空间
               n = (tab = resize()).length;//resize方法在下一个代码块里
        //返回了一个新建的哈希表,被tab接收
        //计算对应的索引,并把这个位置的对象给辅助变量p
           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 {
                   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;
        //如果大小超过临界值(目前为12),就用resize进行扩容
           if (++size > threshold)
               resize();
        //HashMap留给子类实现的方法,比如设置各种特殊链表,这里是一个空方法
           afterNodeInsertion(evict);
        //创建成功的话,就返回null
           return null;
       }
//resize 方法
final Node<K,V>[] resize() {
     Node<K,V>[] oldTab = table;
 //oldCap为table现在的长度,第一次添加数据时为0
     int oldCap = (oldTab == null) ? 0 : oldTab.length;
 //oldThr为临界值,目前为0
     int oldThr = threshold;
     int newCap, newThr = 0;
 //经过判断,这里进入了最后的else代码块里
     if (oldCap > 0) {
         if (oldCap >= MAXIMUM_CAPACITY) {
             threshold = Integer.MAX_VALUE;
             return oldTab;
         }
         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                  oldCap >= DEFAULT_INITIAL_CAPACITY)
             newThr = oldThr << 1; // double threshold
     }
     else if (oldThr > 0) // initial capacity was placed in threshold
         newCap = oldThr;
     else {               // zero initial threshold signifies using defaults
         //开辟了默认的空间,默认为16位,定义如下(1左移4位,翻了16倍):
     //    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
         newCap = DEFAULT_INITIAL_CAPACITY;
         //提前扩容,这里DEFAULT_LOAD_FACTOR为加载因子0.75,当容量到达newThr = 0.75 * 16 = 12时就扩容,防止突然涌入大量数据导致溢出
         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
     }
 //这里跳过
     if (newThr == 0) {
         float ft = (float)newCap * loadFactor;
         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                   (int)ft : Integer.MAX_VALUE);
     }
     threshold = newThr;
     @SuppressWarnings({"rawtypes","unchecked"})
 //table 即哈希表的创建在这里
     Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
     table = newTab;
 //以下代码忽略
     if (oldTab != null) {
         for (int j = 0; j < oldCap; ++j) {
             Node<K,V> e;
             if ((e = oldTab[j]) != null) {
                 oldTab[j] = null;
                 if (e.next == null)
                     newTab[e.hash & (newCap - 1)] = e;
                 else if (e instanceof TreeNode)
                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                 else { // preserve order
                     Node<K,V> loHead = null, loTail = null;
                     Node<K,V> hiHead = null, hiTail = null;
                     Node<K,V> next;
                     do {
                         next = e.next;
                         if ((e.hash & oldCap) == 0) {
                             if (loTail == null)
                                 loHead = e;
                             else
                                 loTail.next = e;
                             loTail = e;
                         }
                         else {
                             if (hiTail == null)
                                 hiHead = e;
                             else
                                 hiTail.next = e;
                             hiTail = e;
                         }
                     } while ((e = next) != null);
                     if (loTail != null) {
                         loTail.next = null;
                         newTab[j] = loHead;
                     }
                     if (hiTail != null) {
                         hiTail.next = null;
                         newTab[j + oldCap] = hiHead;
                     }
                 }
             }
         }
     }
 //最终返回建立的哈希表
     return newTab;
 }
  1. 第二次添加分析,直接到putVal方法:

        //putVal方法
           final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                           boolean evict) {
                Node<K,V>[] tab; Node<K,V> p; int n, i;
               //已经创建了table,所以这里跳过
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
               //算得的hash值不重复,进入if语句块,创建新节点
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
               //创建完了跳过接下来的部分,直接到++modCount,最后返回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 {
                        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;
                if (++size > threshold)
                    resize();
                afterNodeInsertion(evict);
                return null;
            }
    
  2. 第三次添加分析,也是直接进入putVal方法

    //putVal方法
       final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
           //跳过
            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);
           //计算得hash值相同,进入else语句块
            else {
                //开发小技巧:用到什么变量再在这个语句块里创建,不要一开始就在开头创建一大堆变量,这样方便查询,也比较好看
                Node<K,V> e; K k;
                //判断内容是否一样,这里内容一样,都是“Java”,故进入if语句块
     //首先判断hash值是否相同,相同并且:
     //1.新加入的节点和原来节点是同一个对象
     //2.或者用equals方法(可重写,不能简单地认为就是内容相等)判断相等
     //两个条件成立一个,就判断相同,进入if语句块
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    //新创建的节点 e 等于 节点 p
                    e = p;
                //如果链表的头节点(哈希表)里的节点已经是红黑树,则用红黑树的判断方法,此处略过不讲
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
     //最后一种情况为判断链表各个节点
                else {
     //for循环为死循环,只有在找到重复节点或者建立新的节点后才会用break退出
                    for (int binCount = 0; ; ++binCount) {
      //链表的节点已经遍历完,没有重复的节点,则在链表末尾创建新节点存储数据
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
      //判断是否超过了创建红黑树的临界值,这里TREEIFY_THRESHOLD为8,
      //如果超过了,就用treeifyBin方法建立红黑树
      //但是要满足table大小大于MIN_TREEIFY_CAPACITY(默认是64),这个判断在treeifyBin方法里,如果没有到,则用resize方法扩容
                            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;
                    }
                }
                // e有内容,进入if语句块
                if (e != null) { // existing mapping for key
                    //取出该位置的值
                    V oldValue = e.value;
                    //onlyIfAbsent为传入putVal的参数,为false,进入
                    if (!onlyIfAbsent|| oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    //返回不为null,创建节点失败
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    

10.LinkedHashSet 类

  • 是HashSet的子类
  • 底层是数组+双向链表的方式维护(HashSet是单项链表),所以支持顺序取出数据
  • pre指针(这里称为before)指向前一个存入的元素,next指针(这里称为after)指向后一个存入的元素(无论每个双向链表的第一个元素在table的什么位置,指针都可以跨链表指向
  • 不可以存重复元素
  • 是否添加新元素的比较和HashSet一样
  • LinkedHashSet 有 head 和 tail 属性
  • 源码分析和扩容机制和HashSet类似,这里不再详细分析
  • table表的数据类型是HashSet$Node,但存入的元素类型是HashSet#Entry -> 多态数组,HashSet#EntryHashSet$Node 的子类

11.Map 接口

  • HashSet 实际上也用了 HashMap , 但其 value 值是系统自定的PRESENT,而Map里value值是自定的(Map 保存具有映射关系的数据 key - value,双列元素)

  • key 和 value 可以是任意数据类型,封装到 HashMap$Node 对象中

  • key不可以重复(原因同HashSet),有相同的key的元素加入时,等价于替换

  • value 可以重复(key需不同)

  • key 可以为 null,但只能有一个(不能重复),但value可以有多个null

  • 常用 String 类对象作 Map 的 key(但只要是Object的子类都可以作key)

  • key 和 value 存在单向对应关系

  • k-v 最后存放的数据类型为 HashMap$Node node = newNode(hash, key, value, null) [在putVal方法中]

    k-v 为了方便程序员的遍历,还会创建三个数组

    key 存放在数据类型为 Set 的对象数组 keySet 中,value 存放在数据类型为 Collection 的对象数组 values 中,HashMap$Node 存放在数据类型为 Set 的对象数组 Entry 中

    对于EntrySet:在 HashMap N o d e 中存放的含 k e y 和 v a l u e 的元素会先被转化为 M a p . E n t r y ( ∗ ∗ 做了向上转型的处理 ∗ ∗ ,所以有些人也说一个 k − v 就是一个 E n t r y ,实际上不够准确, ‘ H a s h M a p Node 中存放的含 key 和 value 的元素会先被转化为 Map.Entry (**做了向上转型的处理**,所以有些人也说一个k-v就是一个Entry,实际上不够准确,`HashMap Node中存放的含keyvalue的元素会先被转化为Map.Entry做了向上转型的处理,所以有些人也说一个kv就是一个Entry,实际上不够准确,HashMapNode` 实现了Map.Entry接口),再放进EntrySet中

    而一个Entry对象就有k,v,EntrySet<Entry<K,V>> , 即 transient Set<Map.Entry<K,V>> entrySet

    EntrySet 中定义的类型是 Map.Entry, 实际上存放的是 HashMap$Node,这是因为static class Node<K,V> implements Map.Entry<K,V>

    把 HashMap$Node 对象放到 EntrySet 的目的:方便遍历,因为Map.Entry 提供了重要方法,可以分别获取key 和 value

    EntrySet 里存放的 Entry 对象只是一个指向,指向 table 中的 HashMap$Node,实际上数据还是存放在 HashMap$Node

    EntrySet 里有 getValue 、getKey、setValue、setKey 方法

常用方法

  • put ( key , value)
  • get(key)(返回对应的value)
  • remove
  • size
  • isEmpty
  • clear
  • containsKey

遍历方式(基于上面讲的三个数组)

  1. keySet方法(Set 的遍历方法)

    • 先取出 key ,再取出 key 对应的 value

              //使用keySet,返回的是一个Set数组
      		Set set = hashMap.keySet();
              for (Object key : set) {
                  //注意key取出来是Object的形式,但可以直接输出,不用向下转型成String的形式
                  //但如果取出来是一个自定义的对象,要使用对象里面的属性或者方法,就需要向下转型
                  //底层:当使用‘+’连接时,Java会自动调用对象的toString()方法将其转换成字符串。对于类型为Object的变量,如果它实际上是String类型,toString()方法会直接返回字符串本身
                  System.out.println( key + "-" + hashMap.get(key));
              }
      
    • 迭代器

              Set set = hashMap.keySet();
              Iterator iterator = set.iterator();
              while (iterator.hasNext()) {
                  //iterator.next()是set中的key元素,用Object类来接收
                  Object next =  iterator.next();
                  System.out.println(next + "-" + hashMap.get(next));
              }
      
  2. values方法(Collection 的遍历方法)

    • 增强for循环

              Collection values = hashMap.values();
              for (Object value : values) {
                  System.out.println(value);
              }
      
    • iterator迭代器

              Collection values = hashMap.values();
              Iterator iterator = values.iterator();
              while (iterator.hasNext()) {
                  Object next = iterator.next();
                  System.out.println(next);
              }
      
  3. 通过entrySet方法获取

    • 增强for(转换成Map.Entry类型,在用getKey和getValue方法)

              Set set = hashMap.entrySet();
              for (Object o : set) {
                  //注意要转换成Map.Entry类型,才能使用特有的方法getKey()、getValue()等
                  //如果直接输出o,结果是 “key=value” 的形式
                  Map.Entry entry = (Map.Entry) o;
                  System.out.println(entry.getKey() + "-" + entry.getValue());
              }
      
    • 迭代器

              Set set = hashMap.entrySet();
              Iterator iterator = set.iterator();
              while (iterator.hasNext()) {
                  Object next =  iterator.next();
                  //如果直接输出next,结果是 “key=value” 的形式
                  Map.Entry entry = (Map.Entry) next;
                  System.out.println(entry.getKey() + "-" + entry.getValue());
              }
      
  • 注:如果 value (不是values) 为一个自定义对象,那么遍历时需要先把 Object 类型的对象向下转型为这个自定义的对象类型,这样才能获取这个对象的属性和方法,进行遍历

12.HashMap 类

  • 是Map接口使用频率最高的实现类
  • 和HashSet一样不保证映射的顺序,因为底层是以HashSet的方式存储的
  • 没有同步机制,线程不安全
  • table 表是一个数组,数组里面的链表元素(k,v) 是一个Node, 实现了Map.Entry<K,V>接口
  • 扩容机制:和HashSet完全一样,只不用HashMap是 key-value 的形式
  • 链表树化后若删除节点,节点少到一定阶段时,会恢复成链表,称为剪枝

13.Hashtable 类

  • key 和 value 都不能为null,否则会抛出控制真异常
  • 使用方法基本和HashMap一样
  • 是线程安全的,但效率较低
  • 底层有数组 Hashtable$Entry [ ] , 初始化大小为11
  • threshold 为 8 (11*0.75)
  • 扩容:不是按两倍来扩容,而是原来的容量乘以2再加1

14.Properties 类

  • 是 Hashtable 的子类(key 和 value 不能为空)
  • 使用和 Hashtable 类似
  • 一般是读取外部配置文件(格式为xxx.properties)时用,加载数据到 Properties 类对象,再用这个类中的方法操作文件
  • 工作时,properties文件是常见的配置文件(具体见IO流的内容)
  • 常用方法

15.TreeSet & TreeMap 类

  • 底层是 TreeMap
  • 最大特点:可以排序
  • 当使用无参构造器是,用的是默认按字母顺序排序
  • 使用 TreeSet 提供的构造器,可以传入一个比较器 Comparator 参数(接口,匿名内部类形式使用),使用方法compare
  • 注意:当compare比较时,若比较结果相同,则无法将新的元素添加进去
  • 第一次添加元素时compare是自己和自己比较,也不返回内容,目的是检测传进来的是不是空值
  • 若没有传入Comparable,则以添加的对象实现的Comparable接口的compareTo去重**(若自定义类型没有实现Comparable接口的compareTo方法,则会报出异常)**
  • 与HashSet的去重机制不同:HashSet用hashCode() + equals() 去重
  • TreeMap 类操作基本和 TreeSet 相同,只是 TreeMap 存储的是唯一元素,TreeMap 存储的是键值对

16.开发中如何选择集合实现类

  1. 先看存储的数据是一组对象(单列)还是键值对(双列)
  2. 一组对象:Collection 接口
    • 允许重复:List 接口
      • 增删多:LinkedList (底层维护了一个双向链表)
      • 改查多:
        • 线程不安全:ArrayList (底层维护 Object 类型的可变数组)
        • 线程安全:Vector
    • 不允许重复:Set 接口
      • 无序:HashSet (底层是 HashMap , 维护了一个哈希表,即数组+链表+红黑树)
      • 排序:TreeSet
      • 插入和取出顺序一致:LinkedHashSet (底层维护数组+双向链表)
  3. 键值对:Map 接口
    • 键无序:
      • 线程不安全:HashMap (底层是:哈希表 jdk7:数组+链表 jdk8:数组+链表+红黑树)
      • 线程安全:Hashtable
    • 键排序:TreeMap
    • 键插入和取出顺序一致:LinkedHashMap
    • 读取文件:Properties

17.Collection 工具类

  • 功能:提供一系列静态方法实现对 Set、Map、List 的操作
  • 常用方法
    • reverse
    • shuffle
    • sort ( List )
    • sort( List, Comparator)
    • swap( List, int, int)
    • Object max (Collection)
    • Object max (Collection, Comparator)
    • Object min (Collection)
    • Object min (Collection, Comparator)
    • frequency
    • copy
    • replaceAll (注意需要先给目的集合 dest 赋任意值,否则会抛出异常)
  • 32
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值