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

韩顺平Java自学笔记 集合 笔记有很多缺陷,还有再完善 阅读详情

第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#Entry 是 HashSet$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中存放的含key和value的元素会先被转化为Map.Entry(∗∗做了向上转型的处理∗∗,所以有些人也说一个k−v就是一个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 赋任意值,否则会抛出异常)
韩顺平Java学习笔记:从基础到实战 Java程序由类和对象组成,其中类是对象的蓝图。一个Java程序至少包含一个类,通常包含一个主类,其包含main方法作为程序入口点。Java文件命名通常与公共类名相同,并以.java后缀结束。示例代码:");在Java语言中,类是创建对象的蓝图,它定义了将要创建的对象的状态(属性)和行为(方法)。类的定义使用关键字class,后跟类名,以及类体(用大括号{}包围的部分)。// 类的属性// 类的构造方法// 类的方法// 对象的创建对象是类的实例,通过关键字。 阅读详情

相关推荐

韩顺平Java编程完全手册

Java语言是一种静态类型语言,这意味着所有的变量在使用之前都必须声明其类型。Java的数据类型可以分为两大类:基本数据类型和引用数据类型。基本数据类型包括数值类型(整型、浮点型)、字符类型(字符型)和布尔类型。这些类型的数据直接存储在栈中,它们的值直接表示数据本身,而不是指向数据的引用。整型:包括byte、short、int和long四种类型,它们分别占用8、16、32和64位的空间。浮点型:包括float和double两种类型,分别表示单精度和双精度浮点数。字符型。

weixin_30838971的博客 1120

韩顺平老师-java集合介绍

了解什么情况下用什么集合 前面我们保存多个数据使用的是数组,那么数组有不足的地方 1)长度开始时必须指定,而且一旦指定,不能更改 2)保存的必须为同一类型的元素 3)使用数组进行增加或者删除元素的示意代码-比较麻烦 数组管理对象比较麻烦,灵活性不够 集合:多种数据放到一起的数据结构/数据类型 集合的好处 1)可以动态保存任意多个对象,使用比较方便 2)提供了一系列方便的操作对象的方法:add、remove、set,get等 3)使用集合添加,删除新元素的示意代码-简洁了 集合框架体系 Collection.

想握住此生辽阔 1万+

Ubuntu24.04LTS上Ollama GPU加速配置指南:从驱动安装到多卡均衡负载

本文详细介绍了在Ubuntu24.04LTS系统上配置Ollama GPU加速的完整指南,包括NVIDIA驱动安装、CUDA Toolkit配置、Ollama在线与离线安装方法,以及多GPU负载均衡优化策略。通过实战案例和性能调优建议,帮助开发者充分利用硬件资源提升大型语言模型的运行效率。

weixin_29255731的博客 760

韩顺平 2021零基础学Java 学习笔记

韩顺平 2021零基础学Java 学习笔记(1)(自用)_丨shallow丨的博客-优快云博客_韩顺平java笔记韩顺平 2021零基础学Java 学习笔记(2)(自用)_丨shallow丨的博客-优快云博客 韩顺平 2021零基础学Java 学习笔记(3)(自用)_丨shallow丨的博客-优快云博客 韩顺平 2021零基础学Java 学习笔记(4)(自用)_丨shallow丨的博客-优快云博客 韩顺平 2021零基础学Java 学习笔记(5)(自用)_丨shallow丨的博客-优快云博客

大雾起了清晨 8920

Win11下Android模拟器报HAXM未安装?三步搞定虚拟化配置(附终端命令)

本文详细解析了Win11下Android模拟器HAXM报错的解决方案,从虚拟化配置到终端命令实战。通过检查BIOS设置、调整Windows功能及执行PowerShell命令,三步搞定虚拟化配置问题,有效提升模拟器性能。特别针对Win11与HAXM的兼容性问题提供了专业指导。

weixin_29215815的博客 143

扫地机器人视觉+IMU融合导航路径规划技术详解

本文介绍了基于视觉-惯性里程计(VIO)的扫地机器人自主导航系统。系统硬件由RGB摄像头、IMU传感器和编码器组成,软件架构采用分层设计,包括传感器数据采集层、数据融合层、SLAM定位层和路径规划层。核心技术包括:1)融合视觉特征提取(ORB算法)与IMU预积分的VIO;2)基于栅格地图的SLAM构建;3)结合A*全局规划和动态窗口法(DWA)局部避障的路径规划。文章提供了完整的Python实现代码,涵盖传感器数据融合、特征匹配和运动估计等关键功能模块。该系统实现了扫地机器人的精准定位和环境感知能力。

电脑管家 1460

韩顺平老师坦克大战优化版

目录一、项目介绍1、前言2、涉及知识点3、关键代码4、运行数据二、主要优化方面1、系统常量2、用线程池管理线程3、采用线程安全集合类4、控制坦克发射频率5、敌方自动追击我方6、避免坦克重叠7、生成一组不重复随机整数8、统计数量时采用原子类一、项目介绍 1、前言 基于韩顺平老师坦克大战的框架和思路,进行了一些优化。编码上尽量按照阿里的代码规约;有非常详尽的注释;引入了线程池,线程安全集合类,原子类...

土味儿 2847

Shopee前端反爬三要素:x-sap-Ri、x-sap-Sec与af-ac-enc-dat深度解析

电商前端反爬机制本质是运行时环境可信验证,其核心在于动态生成的请求指纹、会话签名与行为加密载荷。这类机制不依赖静态Token,而是基于浏览器真实交互(如鼠标轨迹、页面可见性、时间精度)实时计算,体现JS运行时环境不可模拟性。技术价值在于将风控前移至客户端,大幅提升自动化绕过成本;典型应用场景包括商品价格监控、竞品数据采集与合规爬虫开发。本文聚焦Shopee实战案例,深入拆解x-sap-Ri(设备+行为哈希指纹)、x-sap-Sec(动态密钥签名)及af-ac-enc-dat(AES加密行为载荷)三大字段的生

weixin_29015127的博客 401

韩顺平Java高级课程【超全资料】——深入浅出的编程艺术

我非常兴奋地在这里分享一个宝贵的资源——韩顺平老师的Java全部课程资料。对于正在学习Java的同学们,这无疑是一个不容错过的机会。

m0_71672987的博客 1万+

别再说Fortran过时了!手把手教你用VS Code + gfortran搭建现代开发环境(2024版)

本文打破Fortran过时的刻板印象,详细介绍如何利用VS Code和gfortran搭建现代开发环境。从编译器安装、VS Code插件配置到项目结构标准化,提供完整的2024版跨平台解决方案,并展示Fortran 2023标准的协程、并行计算等现代特性,助力科学计算与高性能开发。

weixin_42629815的博客 241

解决Docker拉取镜像报错:Get https://registry-1.docker.io/v2/: 报错

解决Docker拉取镜像报错: Get “https://registry-1.docker.io/v2/”: net/http: request canceled while waiting for connection问题描述Get "https://registry-1.docker.io/v2/"是docker官方的镜像源,下载很慢的,一般会自己指定国内映射的加速镜像源。

qq_33650333的博客 4172

【免费下载】 Linux-韩顺平学习笔记 - PDF-PPT

Linux-韩顺平学习笔记 - PDF-PPT 【下载地址】Linux-韩顺平学习笔记-PDF-PPT Linux-韩顺平学习笔记 - PDF-PPT本仓库提供了一份详尽的Linux课程学习笔记,由韩顺平老师编写 项目地址: ht...

gitblog_06685的博客 1942

Kubernetes多网卡环境下Calico网络配置避坑指南:如何精准指定业务网卡

本文针对Kubernetes多网卡环境下Calico网络配置的常见痛点,提供了精准指定业务网卡的实战指南。通过深入分析Calico的IP自动检测机制,详细解读并对比了五种IP_AUTODETECTION_METHOD配置方法(如interface、cidr、can-reach等),帮助运维人员从根源上解决网络分区和Pod通信失败问题,确保生产集群网络的稳定与可靠。

weixin_28835551的博客 730

P148-P153 0147-0152_韩顺平Java_本章作业1-6

/定义一个求三次方的函数cube,Math.pow虽能求但是是double类型的 public static int cube(int number) {System . out . println("是否想判断一个整数是否为水仙花数?y/n");break;} System . out . println("请输入一个整数:");System . out . println("该数是水仙花数");} else {

weixin_44041958的博客 1906

5.3-集群电源模式解析

DSU-120 DynamIQ™集群支持9种电源模式,包括正常运行(ON)、完全断电(OFF)、三种数据保持模式(FUNC_RET、FULL_RET、MEM_RET)、两种调试模拟模式(OFF_EMU、MEM_RET_EMU),以及系统恢复模式(WARM_RST、DBG_RECOV)。这些模式通过智能管理电源状态,在保持数据完整性的同时实现功耗优化,并为系统调试和故障恢复提供支持。不同模式在RAM保持、逻辑断电和调试能力方面各有侧重,满足从正常运行到深度调试等不同场景需求。

baron-周贺贺-代码改变世界ctw 116

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

韩顺平教育java高级课程

如何实现“韩顺平教育java高级课程” 作为一名经验丰富的开发者,我很高兴能够帮助刚入行的小白实现“韩顺平教育java高级课程”。在这个过程中,我会详细介绍整个实现流程,并提供代码示例和注释,以确保你能够理解并掌握每个步骤。 实现流程 首先,让我们通过一个表格来了解实现“韩顺平教育java高级课程”的整个流程: 步骤...

weixin_41574285的博客 867

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

韩顺平 教学资料下载

传智播客_韩顺平ajax技术教程 http://dl.dbank.com/c0o7uab17y# 传智播客_韩顺平_php从入门到精通 http://115.com/folder/fdm0n1x9#韩顺平php入门到精通 下载密码:dxsong0207 传智播客PHP培训 韩顺平 PHP视频教程 zend framework http://d...

疯狂.NET 2892

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

韩顺平Java从入门到精通教学PPT:助力Java学习之路

韩顺平Java从入门到精通教学PPT:助力Java学习之路 【下载地址】韩顺平Java从入门到精通教学PPT 这套由知名Java讲师韩顺平精心制作的Java教学PPT,涵盖了从入门到精通的全面知识点,适合初学者和进阶者使用。内容详实、步骤清晰,帮助用户深入理解Java编程的基础与高级特性。无论你是刚接触Java,还是希望...

gitblog_06729的博客 1062

韩顺平java作业题_韩顺平java作业

接口体现了程序设计的多态和高内聚低耦合的思想第十四课 final 作业评讲 1.实现接口和继承父类的区别: 2.java 是单继承,一个类只允许继承一个父类,这种单继承......韩顺平java坦克大战1.0版本_源代码_工学_高等教育_教育专区。韩顺平java坦克大战1.0版本_源代码 /**画坦克 1.0 */ import java.awt.Color; import java.awt......

weixin_29711471的博客 921

集合——韩顺平java

Iterator 对象称为迭代器,主要用于遍历 Collection 集合中的元素。所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现 iterator 接口的对象,即可以返回一个迭代器。iterator 仅用于遍历集合,iterator 本身并不存放对象。iterator 基本使用定义 ArrayList 并初始化。得到 col 对应的迭代器使用 while 循环 hasNext()、next() 方法。快捷键itit。

xijiancui的博客 1133

Java集合深入剖析【韩顺平老师版】

List集合中元素有序、且可重复,集合中的每个元素都有其对应的顺序。存取顺序一致。List容器中的元素都对应一个整数型的序号(索引index)记录位置。从0开始。常用的实现类有:ArrayList、LinkedList 和 VectorSet接口是Collection接口的子接口Set有些是无序的,有些是有序的(存取顺序一致,内部数组中仍是散列分布,无序的)HashSet是无序的,存入和取出的顺序不保证一致,不能通过索引index来获取元素;

2401_85379544的博客 1044

【免费下载】 韩顺平30天学Java资源下载

韩顺平30天学Java资源下载 【下载地址】韩顺平30天学Java资源下载 《韩顺平30天学Java》是一套系统的Java学习资源,旨在帮助初学者在30天内掌握Java编程的基础知识和核心概念。该资源包含了详细的教程、练习题和项目实践,适合所有希望快速入门Java编程的学习者 ...

gitblog_09729的博客 1595

java韩顺平学习笔记8 (集合)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QaOinhBs-1692940599498)(https://gitee.com/lu_zhao_wen/md-image-library/raw/master/img/image-20230822120447967.png)]

weixin_48170312的博客 284

【狂神说JavaWeb】【韩顺平Servlet】一些重要内容自补解释笔记

根据该笔记进行的大致补充,这位作者写的很详细~【狂神说Java】JavaWeb入门到实战(学习笔记) 学习视频来自B站 狂神说 JavaWeb视频: https://www.bilibili.com/video/BV12J411M7Sj?p=1 目录第1章 基本概念1. 基本介绍1.3 Web 的访问过程(P1-14'30) 第1章 基本概念 1. 基本介绍 1.3 Web 的访问过程(P1-14’30) 怎么在网页中查看header之类的呢? 静态WEB的访问过程: ...

一只活活泼泼快快乐乐的萨摩耶的博客 1341

探索Java编程之旅 —— "韩顺平_循序渐进学Java零基础"全面解析

探索Java编程之旅 —— "韩顺平_循序渐进学Java零基础"全面解析 【下载地址】韩顺平_循序渐进学Java零基础完整笔记1分享 韩顺平_循序渐进学Java零基础【完整笔记】1欢迎来到“循序渐进学Java零基础”笔记资源库 ...

gitblog_06678的博客 958

韩顺平java课件资料

如何实现“韩顺平Java课件资料” 在学习Java的过程中,有许多优秀的学习资源和课件可供参考,而“韩顺平Java课件资料”就是其中比较受欢迎的一种。本文将为你详细解析如何获取和实现这些课件资料。我们将采用简单易懂的方式,结合代码示例,帮助你迅速上手。 整体流程概述 以下是实现“韩顺平Java课件资料”的基本步骤: ...

weixin_42135073的博客 244
上一篇: 【LeetCode刷题笔记】7.删除有序数组中的重复项(26)8.移动零(283)9.比较含退格的字符串(844)
下一篇: 【LeetCode 刷题笔记】10.有序数组的平方 11.长度最小的子数组
COTAS
博客等级 码龄7年 369粉丝 30原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值