Java 集合学习笔记

@

目录

    • 二、Collection 接口方法
    • 三、Iterator 迭代器接口
      • 1. 使用 Iterator 接口遍历集合元素
      • 2. Iterator 接口的方法
      • 3. 使用 foreach 循环遍历集合元素
    • 四、Collection 子接口一:List
      • 3. List 实现类之一:ArrayList(主要)
      • 4. List 实现类之二:LinkedList
      • 5. List 实现类之三:Vector
      • 6. List 三种实现类的异同
    • 五、Collection 子接口二:Set
      • 2. Set 实现类之一:HashSet
      • 3. Set 实现类之二:LinkedHashSet
      • 4. Set 实现类之三:TreeSet
      • 2. Map 实现类之一:HashMap
      • 3. Map 实现类之二:LinkedHashMap
      • 4. Map 实现类之三:TreeMap
      • 5. Map 实现类之四:Hashtable
      • 6. Map 实现类之五:Properties
    • 七、Collections 工具类
    • 八、关于HashSet的一道面试题

Java 集合(容器)

一、Java 集合框架概述

  • 一方面,面向对象语言对事务的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用 Array 存储对象放面具有 一些弊端 ,而 Java 集合就像一种容器,可以动态的把多个对象的引用放入容器中。

    • 数组在内存存储方面的特点:
      • 数组初始化以后,长度就确定了
      • 数组声明的类型,就决定了元素初始化时的类型
    • 数组在存储数据方面的弊端:
      • 数组初始化以后,长度不可变,不便于扩展
      • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储的元素的个数
      • 数组存储的数据是有序的、可重复的。(存储数据的特点单一)
    • Java 集合类可用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
  • Java 集合可分为 Collection 和 Map 两种体系

    • Collection 接口:单列数据,定义了存取一组对象的方法和集合
      • List:元素有序、可重复的集合
      • Set:元素无需、不可重复的集合
    • Map 集合:双列数据,保存具有映射关系 “ key – value 对” 的集合
  • Collection 接口继承树

  • Map 接口继承树

二、Collection 接口方法

Collection 接口中文文档(JDK 11)

  1. 添加
    • add(Object obj)
    • addAll(Collection coll):将集合 coll 中的所有元素添加到当前集合中
  2. 获取有效元素的个数
    • int size()
  3. 清空合集
    • void clear()
  4. 是否为空集合
    • boolean isEmpty()
  5. 是否包含某个元素
    • boolean contains(Object obj):是通过 元素的 equals 方法 来判断是否是同一个对象
    • boolean containsAll(Collection c):也是调用 equals 方法来比较,用两个集合的元素挨个比较。
  6. 删除
    • boolean remove(Object obj):通过 equals 方法定位,并删除, 只会删除找到的第一个元素
    • boolean removeAll(Collection coll):取当前集合的差集
  7. 取两个集合的交集
    • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响集合 c
  8. 集合是否相等
    • boolean equals(Object obj)
  9. 转成对象数组
    • Object[] toArray()
    • 数组 转化成 集合:调用 Arrays 类的静态方法 asList()。
      • 但要注意该方法直接写入 new int[](123, 456) 只是把整体当成一个元素存入,此时用包装类的方式即可解决。
  10. 获取集合对象的哈希值
    • hashCode()
  11. 遍历
    • iterator():返回迭代器对象,用于遍历集合

三、Iterator 迭代器接口

1. 使用 Iterator 接口遍历集合元素

  • Iterator 对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
  • GOF 给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需要暴露该对象的内部细节。迭代器模式,便是为容器而生。类似于”公交车上的售票员”、”火车上的乘务员“、”空姐“。
  • Collection 接口继承了 java.lang.Iterator 接口,该接口有一个 Iterator() 方法,那么所有实现了 Collection 接口的集合类都有一个 Iterator() 方法,用以返回一个实现了 Iterator 接口的对象。

2. Iterator 接口的方法

boolean hasNext(): Return true if the iterator has more elements.
E next(): Return the next element int the iteration.
void remove(): Removes from the underlying collection the last element returned by the iterator(optional operation).
  • 可采取 hasNext() 和 next() 方法配合遍历集合

    在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。

    while(iterator.hasNext) {
    iterator.next();
    }

    两种常见的错误遍历方式:

    //方式一:
    Iterator iterator = coll.iterator();
    while(iterator.next() != null) {
    System.out.println(iterator.next());
    }
    //while的条件会使 集合指针 移动一次,导致输出结果跳跃
    //方式二:
    while(coll.iterator().hasNext()) {
    System.out.println(coll.iterator().next());
    }
    //集合对象每次调用 iterator() 方法都会得到一个全新的迭代器对象,默认指针 都会从集合的第一个元素开始。
  • 有关 remove()

    Iterator iter = coll.iterator();
    while(iter.hasNext()) {
    Object obj = iter.next();
    if(obj,equals("Tom")) {
    iter.remove();
    }
    }
    • 注意
      • Iterator 可以删除集合的元素,但是 是通过遍历过程中通过迭代器对象的 remove 方法,不是集合对象的 remove 方法。
      • 如果还未调用 next() 或在上一次调用 next 方法之后已经调用了 remove 方法,再调用 remove 都会报IllegalStateException

3. 使用 foreach 循环遍历集合元素

  • Java 5.0 提供了 foreach 循环迭代访问 Collection 和 数组

  • 遍历操作不需要获取 Collection 或数组的长度,无需使用索引访问元素

  • 遍历集合和底层调用 Iterator 完成操作

  • foreach 还可以用来遍历数组

四、Collection 子接口一:List

1. List 接口概述

  • 鉴于 Java 中数组用来存储数据的局限性,我们通常使用List替代数组
  • List 集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  • JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。

2. List 接口方法

  • List 除了从 Collection 集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
    • void add(int index, Object ele):在 index 位置插入 ele 元素
    • boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
    • Object get(int index):获取指定 index 位置的元素
    • int indexOf(Object obj):返回 obj 在集合中首次出现的位置
    • int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
    • Object remove(int index):移除指定 index 位置的元素,并返回此元素
    • Object set(int index, Object ele):设置指定 index 位置的元素为 ele
    • List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合

3. List 实现类之一:ArrayList(主要)

  • ArrayList 是 List 接口的典型实现类、主要实现类

  • 本质上,ArrayList 是对象引用的一个“变长“数组

  • ArrayList 的 JDK 1.8 之前与之后的空参构造器的实现区别(其他方面无异)?

    • JDK 1.7:类似饿汉式,直接创建一个初始容量为 10 的数组

      • ArrayList list = new ArrayList();
        list.add(123); // elementData[0] = new Integer(123);
      • 当数组 elementData 数组容量不够时,默认情况下,扩容为原来的 1.5 倍,同时将原来数组中的数据复制到新的数组中。

      • 建议开发中使用带参构造器:一开始人为的确定容量

    • JDK 1.8:类似懒汉式,一开始创建一个长度为 0 的数组,当添加第一个元素时再创建一个是容量为 10 的数组

  • Array.asList() 方法返回的 List 集合,既不是ArrayList 试里,也不是 Vector 实例。Array.asList() 返回值是一个固定长度的 List 集合。

  • 源码分析传送门 (转载)

4. List 实现类之二:LinkedList

  • 适合频繁的插入或删除元素的操作,效率较高

  • 双向链表

5. List 实现类之三:Vector

  • Vector 很古老,JDK 1.0 就有了。大多数操作与 ArrayList 相同,区别在于Vector 是线程安全的,但效率总是比 ArrayList 慢,一般不用。

6. List 三种实现类的异同

  1. ArrayList 与 LinkedList 区别:(参考javaGuide)
    1. 是否线程安全 :二者线程不完全,相对线程安全的 Vector,执行效率更高
    2. 底层数据结构 :ArrayList 和Vector 实现了基于动态数组的数据结构,LinkedList是基于链表的数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环)。
    3. 插入和删除元素
      • 对于 ArrayList ,由于是数组结构,故在指定位置 i 插入或删除元素的画,时间复杂度为 O(n-i) ,即操作位置之后的所有元素后移或前移。
      • 对于 LinkedList ,由于采用链表结构,插入删除元素的时间复杂度近似为 O(1) , 若在指定位置插入元素,时间复杂度近似为 O(n) ,因为要先移动到指定位置,再进行操作。
    4. 是否支持快速随机访问: LinkedList 不支持高效的随即元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号开速获取元素对象。
    5. 内存空间占用: ArrayList 的空间浪费主要体现在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
  2. ArrayList 和 Vector 的区别
    Vector 和 ArrayList 几乎完全相同,最大区别为 Vector 是同步类(synchronized),属于强同步类。因此开销比 ArrayList 要大,访问要慢。正常情况下,一般使用 ArrayList 而不是 Vector,因为同步完全可以由程序员自己来控制(如使用Collections的方法)。
    Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
    Vector还有一个子类 Stack

五、Collection 子接口二:Set

1. Set 接口概述

  • Set 接口是 Collection 的子接口,Set 接口没有提供额外的方法。
  • Set 集合不允许包含相同的元素
  • Set 判断两个对象是否相同,使用的是 equals() 方法。

2. Set 实现类之一:HashSet

  • HashSet 是 Set 接口的典型实现,大多数使用 Set 集合时都使用该实现类。

  • HashSet 按 Hash 算法来存储集合种的元素,因此具有很好的存取、查找、删除性能。

  • HashSet 具有以下特点:

    • 不能保证元素的排列顺序
    • HashSet 不是线程同步的
    • 集合元素可以时null
  • HashSet 集合判断两个元素相等的标准:

    通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。

  • 对于存放在 Set 容器种的对象,对应的类一定要重写 equals() 和 hashCode(Object obj) 方法,以实现对象相等规则。即:”相等的对象必须具有相等的散列码“。

  • HashSet添加元素的过程:

    添加元素a,首先调用元素 a 所在类的 hashcode() 方法,计算元素a的哈希值,通过此哈希值和某种 散列函数 计算出在 HashSet 底层数组的存放位置(即为:索引位置),判断此位置上是否已经又元素:

    ​ 如果此位置上没有其他元素,则元素 a 添加成功(情况1)

    ​ 如果此位置上有其他元素 b (或以链表形式存在的多个元素), 则比较元素 a 与元素 b 的哈希值:

    ​ 若哈希值不同:则元素 a 添加成功(情况2)

    ​ 若哈希值相同:需要调用元素 a 所在类的 equals() 方法

    ​ equals() 返回 true :添加失败

    ​ equals() 返回 false:添加成功(情况3)

    • 对于添加成功的 情况2 和 情况3 而言:元素 a 与已经存在指定索引位置上数据以链表的方式存储。

      • jdk 7 :元素 a 放到数组中,指向原来的元素

      • jdk 8 :原来的元素在数组中,指向元素 a

        (七上八下)

    底层也是数组,初始容量为16,当如果使用率超过0.75(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128….等)

    • 上述提到的散列函数,会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好。
  • 重写 hashCode() 方法的基本原则

    • 在程序运行时,同时一个对象多次调用 hashCode() 方法应该返回相同的值。
    • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也相等
    • 对象中作用 equals() 方法比较的 Field,都应该用来计算 hashCode 值
  • 重写 equals() 方法的基本原则

    • 当一个类有自己独特的 “逻辑相等” 概念,当该重写 equals() 的时候,总要重写 hashCode() ,根据一个类的 equals 方法(重写后),两个截然不同的实例有可能在逻辑上时相等的,但是根据 Object.hashCode() 方法,它们仅仅是两个对象。
    • 因此,违反了“相等的对象必须具有相等的散列码”。
    • 结论:重写 equals 方法的时候一般都需要同时重写 hashCode 方法,通常参与计算 hashCode 的对象的属性也应该参与到 equals() 中进行计算。
    • 为什么用 Eclipse/IDEA 重写的 hashCode 方法,有31这个数字?
      • 选择系数的时候要尽量选择最大的系数,因为如果计算出来的 hash 地址越大,所谓的“冲突”就越少。查找起来效率也会提高(减少冲突)
      • 31 只占用 5 bits,相乘造成数据溢出的概率较小
      • 31 可以由 i * 31 == (i << 5) – 1,现在很多虚拟机里面都有相关优化。(提高算法效率)
      • 31 是一个素数,某个数与素数相乘,结果只能被该素数,和被乘数还有 1 来整除(减少冲突)

3. Set 实现类之二:LinkedHashSet

  • LinkedHashSet 是 HashSet 的子类

  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的

  • LinkedHashSet 插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有更好的性能

  • LinkedHashSet 不允许集合元素重复

4. Set 实现类之三:TreeSet

  • TreeSet 时 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
  • TreeSet 底层使用红黑树结构存储数据
  • TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
    • 自然排序:
      • 向 TreeSet 中添加元素时,只有第一个元素无须比较 compareTo() 方法,后面添加的元素都会调用 compareTo() 方法进行比较。
      • 因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该时同一个类的对象。
      • 对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值
      • 当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过 equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较 应返回 0 ,否则可读性较差。
    • 定制排序:
      • TreeSet 的自然排序要求元素所属的类实现 Comparable 接口,如果元素所属的类没有实现 Comparable 接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其他属性大小进行排序,则考虑使用定制排序。定制排序,通过 Comparator 接口来实现。需要重写 compae(T o1, T o2) 方法。
      • 利用 int compare(T o1, T o2) 方法, 比较 o1 和 o2 的大小:如果方法返回正整数,则表示 o1 大于 o2;如果返回 0,表示相等;返回负整数,表示 o1 小于 o2。
      • 要实现定制排序,需要将实现 Comparator 接口的实例作为参数传递给 TreeSet 的构造器
      • 此时,仍然只能向 TreeSet 中添加类型相同的对象。否则发生 ClassCastException 异常。
      • 使用定制排序判断两个元素相等的标准是:通过 Comparator 比较两个元素返回了 0。

六、Map 接口

1. Map 接口概述

  • Map 与 Collection 并列存在。用于保存具有映射关系的数据: key – value

  • Map 中的 key 和 value 都可以是任何引用类型的数据

  • Map 中的 key 用 Set 来存放, 不允许重复 ,即同一个 Map 对象所对应的类,必须重写 hashCode() 和 equals() 方法

  • 常用 String 类作为 Map 的 ”键“

  • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value

  • Map 接口的常用实现类:HashMap、TreeMap、LinkedHashMap 和 Properties。其中,HashMap 是 Map 接口使用频率最高的实现类。

  • Map结构的理解:

    • Map 中的 key 无序的、不可重复的,使用 Set 存储所有的 key (key 所在类要重写 equals() 和 hashCode() )
    • Map 中的 value:无序的、可重复的,使用 Collection 存储所有的 value (value 所在的类要重写 equals() )
    • 一个键值对:key – value 构成了一个 Entry 对象。
    • Map 中的 Entry :无序的、不可重复的,使用 Set 存储所有的 Entry。
  • Map 接口常用方法:

2. Map 实现类之一:HashMap

  1. Some Tips

    • HashMap 是 Map接口使用频率最高的实现类
    • 允许使用 null 键和 null 值,与 Hash Set 一样
    • 所有的 key 构成的集合是 Set :无序的、不可重复的。所以,key 所在的类要重写 equals() 和 hashCode()
    • 所有的 value 构成的集合是 Collection:无序的、可以重复的。所以,value所在类要重写 equals()
    • 一个 key-value 构成一个 entry
    • 所有的 entry 构成的集合是 Set:无需的、不可重复的
    • HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 返回 true
    • HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 返回 true
  2. JDK 7 及以前

    1. 添加元素的过程(jdk7)

      HashMap map = new HashMap();

      在实例化后,底层创建了长度为 16 的一维数组 Entry[] table

      (可能已经执行多次 put )

      map.put(key1, value1);

      首先,调用 key1 所在类的 hashCode() 计算 key1 哈希值,此哈希值经过某种算法计算后,得到在 Entry 数组中存放的位置

      如果此位置上的数据为空,此时的 key1-value1 添加成功(情况1)

      如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较 key1 和已经存在的一个或多个数据的哈希值:

      ​ 如果 key1 的哈希值与已经存在的数据的哈希值都不相同,key1-value1 添加成功(情况2)

      ​ 如果 key1 的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,就继续比较:调用 key1 所在类的 equals(key2):

      ​ 如果 equals() 返回 false:key1-value1添加成功。(情况3)

      ​ 如果 equals() 返回 true:使用 value1 替换 value2

      • (情况 2 与情况 3 :key1-value1 和原来的数据以链表的形式存储)
      • 扩容(resize): 转移到新的 容量为原来的 两倍 的数组中。
    2. 扩容时机

      当 HashMap 中的元素个数超过数组大小(数组总大小length,不是数组中个数 size ) * loadFactor 时 , 就会 进行数组扩容, loadFactor 的默认值( DEFAULT_LOAD_FACTOR)为 0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为 16,那么当 HashMap 中元素个数超过16 * 0.75=12(这个值就是代码中的 threshold 值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍, 然后重新计算每个元素在数组中的位置 ,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap 中元素的个数, 那么预设元素的个数能够有效的提高 HashMap 的性能。

  3. jdk 8 与jdk 7 的区别

    1. new HashMap() 时:底层没有创建一个长度为 16 的数组

    2. 首次调用 put() 方法时,底层创建长度为 16 的数组

    3. Entry() 改为 Node[]

    4. JDK 8 形成链表结构时,新添加的 key – value 对在链表的尾部 (7 上 8 下 )

    5. jdk 7 底层结构只有:数组 + 链表

      jdk 8 底层结构:数组 + 链表 + 红黑树

      当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前这个数组的长度超过 64 时,这个链会变成树,结点类型由 Node 变成 TreeNode 类型。当然,如果当映射关系被移除后,下次 resize 方法时判断树的结点个数低于6个,也会把树再转为链表。

  4. 部分主要常量

    • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
    • MAXIMUM_CAPACITY: HashMap的最大支持容量,2^30
    • DEFAULT_LOAD_FACTOR :HashMap的默认加载因子:0.75
    • TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树(8)
    • UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
    • MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量(64)。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
    • table:存储元素的数组,总是2的n次幂
    • entrySet: :存储具体元素的集
    • size:HashMap中存储的键值对的数量
    • modCount:HashMap扩容和结构改变的次数。
    • threshold 扩容的临界值 = 容量 * 填充因子 :16 * 0.75 = 12
      • 提前扩容,可以让链表少一些
    • loadFactor: :填充因子
  5. LoadFactor(负载因子值、 填充因子值、填充比)的大小,对 HashMap的影响

    • 决定 HashMap 的数据密度
    • LoadFactor 越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能下降
    • LoadFactor 越小,越容易触发扩容,数据密度也越小,碰撞几率越小,数组中的链表也越短,查询和插入时比较的次数也越小,性能会较高。但是会浪费更多的内存。而且经常扩容也会影响性能,故建议初始化预设大一点的空间。
    • 设置为 0.7 ~ 0.75 较为常规:此时平均检索长度接近于常数。

3. Map 实现类之二:LinkedHashMap

  • 是 HashMap 的子类

  • 在 HashMap 存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。

  • 与 LinkedHashSet 类似,其可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致。

  • HashMap 中的内部类:Node

    static class Node<K, V> implements Map.Entry<K, V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    }

    LinkedHashMap 中的内部类:Entry

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

4. Map 实现类之三:TreeMap

  • TreeMap 存储 Key-Value 对时,需要根据 key-value 对进行排序
    TreeMap 可以保证所有的 Key-Value 对处于有序状态。
  • TreeMap 底层使用 红黑树 结构存储数据。
  • TreeMap 的 Key 的排序:
    • 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClassCastException
    • 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
  • TreeMap 判断两个 key 相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回 0 。

5. Map 实现类之四:Hashtable

  • Hashtable 是个功劳的 Map 实现类(JDK 1.0),相比于 HashMap,Hashtable 时线程安全的。
  • 实现原理和 HashMap 相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以相互用。
  • 与 HashMap 不同,Hashtable 不允许使用 null 作为 key 和 value
  • 与HashMap 一样, Hashtable 也不能保证其中 Key-Value 对的顺序。
  • Hashtable 判断两个 key 相等、 两个 value 相等的标准,与 HashMap 一致。

6. Map 实现类之五:Properties

  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件

  • 由于属性文件里的 key、value 都是字符串类型,所以Properties 里的 key 和 value 都是字符串类型

  • 存储数据时,建议使用 setProperty (String key, String value)方法和 getProperty (String key) 方法

    #jdbc.properties
    name = Tom
    passWord == 123abc
    Properties pros = new Properties();
    pros.load(new FileInputStream("jdbc.properties"));
    String user = pros.getProperty("user");
    System.out.println(user)

七、Collections 工具类

1. 概述

  • Collections 是一个操作 Set、List 和 Map 等集合的工具类
  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
  • 排序操作 (均为 static 方法)
    • reverse(List): 反转 List 中元素的顺序
    • shuffle(List): 对 List 集合元素进行随机排序
    • sort(List): 根据元素的自然顺序对指定 List 集合元素按升序排序
    • sort(List, Comparator): 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
    • swap(List, int, int): 将指定的 List 集合中的 i 处元素和 j 处元素进行交换

2. Collections 常用方法

查找、替换

  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
  • Object min (Collection)
  • Object min (Collection, Comparator)
  • int frequency(Collection, Object) :返回指定集合中指定元素的出现次数
  • void copy(List dest, List src) :将 src 中的内容赋值到 dest 中
  • boolean replaceALl(List list, Object oldVal, Object newVal):使用新值替换 List 对象的所有旧值

3. 同步控制

  • Collections 类提供了多个关于同步控制的方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

4. Enumeration

  • Enumeration 接口是 Iterator 迭代器的”古老版本“

    Enumeration stringEnum = new StringTokenizer("a-b*c-d-e-g", "-");
    while(stringEnum.hasMoreElements()) {
    Object obj = stringEnum.nextElement();
    System.out.println(obj);
    }

八、关于HashSet的一道面试题

  1. hashCode 理解

    package com.exer03;
    import java.util.HashSet;
    import java.util.Objects;
    import java.util.Set;
    public class Test {
    @org.junit.Test
    public void test() {
    Person p1 = new Person(1001, "AA");
    Person p2 = new Person(1002, "BB");
    Set set = new HashSet();
    set.add(p1);
    set.add(p2);
    System.out.println(set);
    // ①
    p1.name = "CC";
    set.remove(p1);
    System.out.println(set);
    // ②
    set.add(new Person(1001, "CC"));
    System.out.println(set);
    // ③
    set.add(new Person(1001, "AA"));
    System.out.println(set);
    }
    }
    class Person {
    int num;
    String name;
    public Person(int num, String name) {
    this.num = num;
    this.name = name;
    }
    public Person() {
    }
    @Override
    public String toString() {
    return "Person{" +
    "num=" + num +
    ", name='" + name + '\'' +
    '}';
    }
    @Override
    public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    if (num != person.num) return false;
    return name != null ? name.equals(person.name) : person.name == null;
    }
    @Override
    public int hashCode() {
    int result = num;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    return result;
    }
    }
    // ① 输出结果:
    /*
    [Person{num=1002, name='BB'}, Person{num=1001, name='CC'}]
    解析:关键是注意 hashCode 的重写算法,p1.name 改为 “CC” 后,remove()方法调用的hashCode()使用的name为CC的hashcode ,该位置和一开始存入p1位置不同(是AA和1001 hash编码),故remove() 过程实际上结果为,删除 1001 和 CC hash编码的位置上的元素。
    */
    // ② 输出结果:
    /*
    [Person{num=1002, name='BB'}, Person{num=1001, name='CC'}, Person{num=1001, name='CC'}]
    解析;添加位置是1001和CC编码位置,p1的name虽然被改为CC,但位置在1001和AA编码位置。
    向set里add元素的流程:首先靠 hashCode 寻找某位置是否存在元素,若有则调用 equals() 判断,若无则直接添加。
    */
    // ③ 输出结果:
    /*
    [Person{num=1002, name='BB'}, Person{num=1001, name='CC'}, Person{num=1001, name='CC'}, Person{num=1001, name='AA'}]
    解析:虽然1001 和 AA 编码位置上有元素,但是经过 equals() 返回false,故可以添加成功。
    */
博客园精华区
我还没有学会写个人说明!
上一篇

id software创始人:厂商应该砍掉中介商直接销售PS5/XSX和新显卡

下一篇

鸿蒙的js开发部模式14:tabs组件通过python远程服务构建项目一

你也可能喜欢

评论已经被关闭。

插入图片