当前位置:   article > 正文

Java集合 : 底层原理剖析_java集合底层原理

java集合底层原理

目录

一、⭐️Java集合框架概述

二、⭐️Collection接口方法

三、⭐️Iterator迭代器接口

1. Iterator接口中的方法

2. foreach循环遍历集合元素

四、⭐️Collection子接口 : List

1. List的接口框架

2. ArrayList的源码分析

2.1 JDK 7中

2.2 JDK 8中

2.3 小结

3. LinkedList的源码分析

4. Vector的源码分析

5. List接口中的方法

五、⭐️Collection子接口 : Set

1. Set的接口框架

2. 如何理解Set接口特点

3. 添加元素的过程

4. hashCode()方法的重写

5. HashSet的使用

6. LinkedHashSet的使用

7. TreeSet的使用

7.1 TreeSet的自然排序

7.2 TreeSet的定制排序

六、⭐️Map接口

1. Map的接口框架

2. 关于Map结构的理解

3. HashMap的底层实现原理

3.1 JDK 7中

3.2 JDK 8中

4. LinkedHashMap的底层实现原理

5. Map接口中的方法

6. TreeMap的使用

6.1 TreeMap的自然排序

6.2 TreeMap的定制排序

7. Properties的使用

七、⭐️Collections工具类

1. 排序操作

2. 查找替换操作

3. 同步控制


一、Java集合框架概述

集合、数组都是对多个数据进行存储操作的结构,简称Java容器

说明 : 此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)

数组在存储多个数据方面的特点 :

  • 一旦初始化之后,其长度就确定了

  • 数组一旦定义好,其元素的类型也就确定了,只能操作指定类型的数据。比如 : String[] arr,int[] arr;

数组在存储多个数据方面的缺点 :

  • 一旦初始化之后,其长度就不可修改

  • 数组中提供的方法非常有限,对于 : 添加、删除、插入数据等操作,非常不方便,同时效率不高

  • 获取数组中实际元素的个数的需求,数组中没有现成的属性或方法可用

  • 数组存储数据的特点是有序的,可以存储重复的数据(有序,可重复),对于无序,不可重复的需求不能满足

关于数组的优缺点,集合都能很好的满足,其使用场景有 : 数据库返回的为一个List

 

Java集合可分为Collection和Map两种体系 :

  • Collection接口 : 单列数据,定义了存取一组对象的方法的集合

    • List : 元素有序、可重复的集合

    • Set : 元素无序、不可重复的集合

  • Map接口 : 双列数据,保存具有映射关系 "key - value 对" 的集合

Collection接口继承树 :

说明 : 实线为继承,虚线为实现

 

Map接口继承树 :

  1. |----Collection接口 : 单列集合,用来存储一个一个的对象
  2.   |----List接口 : 存储有序、可重复的数据 --> "动态"数组
  3.       |----ArrayList、LinkedList、Vector
  4.   |----Set接口 : 存储无序、不可重复的数据 --> 高中讲的 "集合"
  5.       |----HashSet、LinkedHashSet、TreeSet
  6. |----Map接口 : 双列集合,用来存储一对 (key - value) 一对的数据 --> 高中函数 : y = f(x)
  7.       |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

二、Collection接口方法

最基本的15种方法 :

  1. add(Object obj)、addAll(Collection coll)、size()、isEmpty()、clear();
  2. contains(Object obj)、containsAll(Collection coll)、remove(Object obj)、removeAll(Collection voll)、retainsAll(Collection coll)、equals(Object obj);
  3. hashCode()、toArray()、iterator();

1、添加

  • add(Object obj)

  • addAll(Collection coll)

2、获取有效元素的个数

  • int size()

3、清空集合

  • void clear()

4、是否是空集合

  • boolean isEmpty()

  1. /*
  2. Collection接口中声明的方法测试
  3. */
  4. @SuppressWarnings("rawtypes")
  5. @Test
  6. public void test01(){
  7.    Collection coll = new ArrayList();
  8.    //add(Object e) : 将元素e添加到集合coll中
  9.    coll.add("AA");
  10.    coll.add(123);
  11.    coll.add(new Object());
  12.    coll.add("BB");
  13.    //size() : 获取添加元素的个数
  14.    System.out.println(coll.size());//4
  15.    Collection coll1 = new ArrayList();
  16.    coll1.add("AA");
  17.    coll1.add(new Date());
  18.    //addAll(Collection c) : 将c集合中的元素全部添加到当前集合中
  19.    coll.addAll(coll1);
  20.    System.out.println(coll.size());//6
  21.    System.out.println(coll);
  22.    //[AA, 123, java.lang.Object@76fb509a, BB, AA, Fri Aug 12 17:38:17 CST 2022]
  23.    //clear() : 清空集合元素
  24.    coll.clear();
  25.    //isEmpty() : 判断当前集合是否为空
  26.    System.out.println(coll.isEmpty());//true
  27. }

5、是否包含某个元素

  • boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象

  • boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。

  1. /*
  2. Collection接口中声明的方法测试
  3. 向Collection接口的实现类中添加数据obj时,要求obj所在的类要重写equals()方法
  4. */
  5. @SuppressWarnings("rawtypes")
  6. @Test
  7. public void test02(){
  8.    Collection coll = new ArrayList();
  9.    coll.add(123);
  10.    coll.add(456);
  11.    coll.add(new String("Tom"));
  12.    coll.add(false);
  13.    Person p = new Person("Jerry", 20);
  14.    coll.add(p);
  15.    //contains(Object obj) : 判断当前集合是否包含obj
  16.    //我们在判断时会调用obj对象所在类的equals()方法
  17.    boolean contains = coll.contains(123);
  18.    System.out.println(contains);//true
  19.    System.out.println(coll.contains(new String("Tom")));//true
  20.                                        // --> 判断的内容(重写的equals方法)
  21.    System.out.println(coll.contains(new Person("Jerry", 20)));//false
  22.                                        // --> 由于equals方法未重写 所以就是判断的地址
  23.    //containsAll(Collection coll) : 判断形参coll中的所有元素是否都存在于当前集合中
  24.    List list = Arrays.asList(123, 456);//返回list类型 list是collection的子接口
  25.    System.out.println(coll.containsAll(list));//true
  26. }

6、删除

  • boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素

  • boolean removeAll(Collection coll):取当前集合的差集

  1. /*
  2. Collection接口中声明的方法测试
  3. */
  4. @SuppressWarnings("rawtypes")
  5. @Test
  6. public void test03(){
  7.    Collection coll = new ArrayList();
  8.    coll.add(123);
  9.    coll.add(456);
  10.    coll.add(new String("Tom"));
  11.    coll.add(false);
  12.    coll.add(new Person("Jerry", 20));
  13.    //remove(Object obj) : 从当前集合中移除
  14.    System.out.println(coll.remove(123));//true
  15.    System.out.println(coll);//[456, Tom, false, Person{name='Jerry', age=20}]
  16.    System.out.println(coll.remove(new Person("Jerry", 20)));
  17.    System.out.println(coll);
  18.    //removeAll(Collection coll) : 从当前集合中移除coll当中的所有元素 (差集)
  19.    List list = Arrays.asList(123, 456);
  20.    System.out.println(coll.removeAll(list));//true
  21. }

7、取两个集合的交集

  • boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c

8、集合是否相等

  • boolean equals(Object obj)

  1.  /*
  2.    Collection接口中声明的方法测试
  3.    */
  4.    @SuppressWarnings("rawtypes")
  5.    @Test
  6.    public void test04(){
  7.        Collection coll = new ArrayList();
  8.        coll.add(123);
  9.        coll.add(456);
  10.        coll.add(new String("Tom"));
  11.        coll.add(false);
  12.        coll.add(new Person("Jerry", 20));
  13.        //retainAll(Collection coll) : 获取当前集合和coll集合之间的交集 修改当前集合的值为交集的值
  14. //       List list = Arrays.asList(123, 456, 789);
  15. //       System.out.println(coll.retainAll(list));//true
  16. //       System.out.println(coll);//[123, 456]
  17.        //equals(Object obj) : 判断当前集合中的每个元素和另一个集合是否相等(顺序元素都相等)
  18.        Collection coll1 = new ArrayList();
  19.        coll1.add(123);
  20.        coll1.add(456);
  21.        coll1.add(new String("Tom"));
  22.        coll1.add(false);
  23.        coll1.add(new Person("Jerry", 20));
  24.        boolean equals = coll.equals(coll1);
  25.        System.out.println(equals);//true
  26.   }

9、转成对象数组

  • Object[] toArray()

10、获取集合对象的哈希值

  • hashCode()

11、遍历

  • iterator():返回迭代器对象,用于集合遍历

  1. /*
  2. Collection接口中声明的方法测试
  3. */
  4. @SuppressWarnings("rawtypes")
  5. @Test
  6. public void test05() {
  7.    Collection coll = new ArrayList();
  8.    coll.add(123);
  9.    coll.add(456);
  10.    coll.add(new String("Tom"));
  11.    coll.add(false);
  12.    coll.add(new Person("Jerry", 20));
  13.    //hashCode() : 返回当前对象的哈希值
  14.    System.out.println(coll.hashCode());//-2061953047
  15.    //toArray() : 集合 --> 数组
  16.    Object[] arr = coll.toArray();//返回的是Object类型的数组
  17.    for (int i = 0; i < arr.length; i++) {
  18.        System.out.println(arr[i]);
  19.   }
  20.    //拓展 : 数组 --> 集合 Arrays.asList(T...t) 可变形参不推荐new,直接写数据即可
  21.    List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
  22.    System.out.println(list);//[AA, BB, CC]
  23.    List<int[]> list1 = Arrays.asList(new int[]{123, 456});//认为是一个参数(一维数组)
  24.    System.out.println(list1);//[[I@300ffa5d]
  25.    System.out.println(list1.size());//1 --> 一个元素
  26.    List<Integer> list2 = Arrays.asList(new Integer[]{123, 456});
  27.    System.out.println(list2);//[123, 456]
  28.    System.out.println(list2.size());//2 --> 两个元素
  29.    
  30.    //iterator() : 返回Iterator接口的实例,用于遍历集合元素 放在IteratorTest.java中测试
  31. }

三、Iterator迭代器接口

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合(不用于Map集合)中的元素。

GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。

Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。

Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。

集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

1. Iterator接口中的方法

  
  1.  @SuppressWarnings("rawtypes")
  2.    @Test
  3.    public void test(){
  4.        Collection coll = new ArrayList();
  5.        coll.add(123);
  6.        coll.add(456);
  7.        coll.add(new String("Tom"));
  8.        coll.add(false);
  9.        coll.add(new Person("Jerry", 20));
  10.        Iterator iterator = coll.iterator();
  11.        //next() : 游标下移 返回游标对应的元素
  12.        //遍历方式一 :
  13.        System.out.println(iterator.next());//123
  14.        System.out.println(iterator.next());//456
  15.        System.out.println(iterator.next());//Tom
  16.        System.out.println(iterator.next());//false
  17.        System.out.println(iterator.next());//Person{name='Jerry', age=20}
  18.        //报异常 : NoSuchElementException
  19. //       System.out.println(iterator.next());
  20.        //遍历方式二 :
  21.        //注意游标不会重置 这里重新获取一个迭代器对象
  22.        Iterator iterator1 = coll.iterator();
  23.        for (int i = 0; i < coll.size(); i++) {
  24.            System.out.println(iterator1.next());
  25.       }
  26.        //hasNext() : 判断是否还有下一个元素
  27.        //遍历方式三 : 推荐
  28.        Iterator iterator2 = coll.iterator();
  29.        while (iterator2.hasNext()){// 判断是否还有下一个元素
  30.            //① 指针下移 ② 将下移以后集合位置上的元素返回
  31.            System.out.println(iterator2.next());
  32.       }
  33.   }

测试Iterator中的remove() :

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test2(){
  4. Collection coll = new ArrayList();
  5. coll.add(123);
  6. coll.add(456);
  7. coll.add(new String("Tom"));
  8. coll.add(false);
  9. coll.add(new Person("Jerry", 20));
  10. Iterator iterator = coll.iterator();
  11. //remove() : 可以在遍历的时候 删除集合中的元素 (删除游标对应的元素)
  12. while (iterator.hasNext()){
  13. Object obj = iterator.next();
  14. if ("Tom".equals(obj)){
  15. iterator.remove();
  16. //不能连续调用两次 会报异常 IllegalStateException
  17. // iterator.remove();
  18. }
  19. }
  20. Iterator iterator1 = coll.iterator();
  21. while (iterator1.hasNext()){
  22. System.out.println(iterator1.next());
  23. }
  24. }

注意 :

  • Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。

  • 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。

2. foreach循环遍历集合元素

Java 5.0 提供了 foreach 循环 (增强for循环) 迭代访问 Collection和数组。

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

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

  • foreach还可以用来遍历数组。

  1. for (集合元素的类型 局部变量 : 要遍历的集合对象){
  2. //内部仍然调用了迭代器实现
  3. }
  4. 局部变量 : 盛装对应的元素 不是索引

遍历集合 :

  1. public class ForTest {
  2. public static void main(String[] args) {
  3. Collection coll = new ArrayList();
  4. coll.add(123);
  5. coll.add(456);
  6. coll.add(new String("Tom"));
  7. coll.add(false);
  8. coll.add(new Person("Jerry", 20));
  9. //for(集合元素的类型 局部变量 : 集合对象)
  10. /*
  11. 过程 : 内部用迭代器实现
  12. 先取coll中第一个元素赋值给obj
  13. 然后取第二个元素赋值给obj
  14. */
  15. for(Object obj : coll){
  16. System.out.println(obj);
  17. }
  18. }
  19. }

遍历数组 :

  1. @Test
  2. public void test(){
  3. int[] arr = new int[]{1, 2, 3, 4, 5, 6};
  4. //for(数组元素的类型 局部变量 : 数组对象)
  5. for (int i : arr){
  6. System.out.println(i);//这里的 i 就是对应的元素 不是索引
  7. }
  8. }

练习题 :

  1. @Test
  2. public void test01() {
  3. String[] arr = new String[]{"MM", "MM", "MM"};
  4. //方式一 : 普通for赋值
  5. // for (int i = 0; i < arr.length; i++) {
  6. // arr[i] = "GG";
  7. // }
  8. //方式二 : 增强for赋值
  9. for (String str : arr){
  10. str = "GG";//此时的赋值不是修改的数组本身 而是修改的局部变量
  11. }
  12. for (int i = 0; i < arr.length; i++) {
  13. System.out.println(arr[i]);
  14. }
  15. }

四、Collection子接口 : List

鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组,称作动态数组。

List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。

List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。

1. List的接口框架

  1. |----Collection接口 : 单列集合,用来存储一个一个的对象
  2. |----List接口 : 存储有序、可重复的数据 --> "动态"数组,替换原有的数组
  3. |----ArrayList
  4. |----LinkedList
  5. |----Vector

比较ArrayList、LinkedList、Vector三者的异同 ?

相同点 : 三个类都是实现了List接口,存储数据的特点相同 : 存储有序的、可重复的数据

不同点 :

  • ArrayList : 作为List接口的主要实现类,线程不安全的,效率高,底层使用Object[] elementData存储

  • Linkedlist : 对于频繁的增加和删除操作,使用此类效率比ArrayList高,底层使用双向链表存储

  • Vector : 作为List接口的古老实现类,线程安全的,效率低,底层使用Object[] elementData存储

2. ArrayList的源码分析

2.1 JDK 7中

ArrayList list = new ArrayList();

底层创建了长度是10的 Object[] 数组 elementData

  1. list.add(123);//elementData[0] = new Integer(123);
  2. ...
  3. list.add(456);//如果此次的添加导致底层elementData数组容量不够,则需要扩容

默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中 ( elementData = Arrays.copyOf(elementData, newCapacity) )。

结论 : 建议开发中使用带参的构造器 :

ArrayList list = new ArrayList(int capacity);

2.2 JDK 8中

ArrayList list = new ArrayList();

底层Object[] elementData初始化为 {},并没有创建长度为10的数组

list.add(123);

当第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]

后续的添加与扩容操作与JDK7相同。

2.3 小结

JDK 7中的ArrayList的对象的创建类似于单例模式的饿汉式

JDK 8中的ArrayList的对象的创建类似于单例模式的懒汉式,延迟了数组的创建,节省内存。

3. LinkedList的源码分析

LinkedList list = new LinkedList();

内部声明了Node类型的first和last属性,默认值为null

list.add(123);

将123封装到Node中,创建了Node对象,其中Node的定义为如下,体现了LinkedList双向链表的实现原理。

  1. private static class Node<E> {
  2. E item;
  3. Node<E> next;
  4. Node<E> prev;
  5. Node(Node<E> prev, E element, Node<E> next) {
  6. this.item = element;
  7. this.next = next;
  8. this.prev = prev;
  9. }
  10. }

4. Vector的源码分析

由于Vector容器是在JDK 1.0时就存在,而Collection接口框架在JDK 1.2时才有,是后才将Vector并入接口框架中的,所以对Vector的使用不多,Vector的特点是线程安全的。其底层实现与ArrayList的原理一样(Object[] 数组),在创建对象的时候默认也是创建一个容量为10的Object数组,但在扩容的时候与ArrayList不同的是默认扩容2倍而非1.5倍。

5. List接口中的方法

List除了从Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的方法 :

  • void add(int index, Object ele) : 在index位置插入ele元素

  • boolean addAll(int index, Collection eles) : 从index位置开始将eles中的所有元素添加进来

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test01(){
    4. List list = new ArrayList<Object>();
    5. list.add(123);
    6. list.add(456);
    7. list.add(new String("Tom"));
    8. list.add(false);
    9. list.add(new Person("Jerry", 20));
    10. //void add(int index, Object ele) : 在index位置插入ele元素
    11. list.add(1, new String("insert"));
    12. System.out.println(list);//[123, insert, 456, Tom, false, Person{name='Jerry', age=20}]
    13. //boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
    14. List list1 = Arrays.asList(1, 2, 3);
    15. list.addAll(list1);
    16. System.out.println(list.size());
    17. }
  • Object get(int index) : 获取指定index位置的元素

  • int indexOf(Object obj) : 返回obj在集合中首次出现的位置

  • int lastIndexOf(Object obj) : 返回obj在当前集合中末次出现的位置

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test02(){
    4. List list = new ArrayList<Object>();
    5. list.add(123);
    6. list.add(456);
    7. list.add(new String("Tom"));
    8. list.add(false);
    9. list.add(456);
    10. list.add(new Person("Jerry", 20));
    11. //Object get(int index):获取指定index位置的元素
    12. Object obj = list.get(1);
    13. System.out.println(obj);//456
    14. //int indexOf(Object obj):返回obj在集合中首次出现的位置 如果不存在返回-1
    15. int index = list.indexOf(456);
    16. System.out.println(index);//1
    17. //int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置 如果不存在返回-1
    18. int lastIndex = list.lastIndexOf(456);
    19. System.out.println(lastIndex);//4
    20. }
  • Object remove(int index) : 移除指定index位置的元素,并返回此元素

  • Object set(int index, Object ele):设置指定index位置的元素为ele,并返回被修改的元素

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test03(){
    4. List list = new ArrayList<Object>();
    5. list.add(123);
    6. list.add(456);
    7. list.add(new String("Tom"));
    8. list.add(false);
    9. list.add(456);
    10. list.add(new Person("Jerry", 20));
    11. //Object remove(int index):移除指定index位置的元素,并返回此元素
    12. Object removeObj = list.remove(4);
    13. System.out.println(removeObj);
    14. System.out.println(list);//[123, 456, Tom, false, Person{name='Jerry', age=20}]
    15. //Object set(int index, Object ele):设置指定index位置的元素为ele 并返回被修改的元素
    16. Object obj = list.set(0, 789);
    17. System.out.println(obj);//123
    18. System.out.println(list);//[789, 456, Tom, false, Person{name='Jerry', age=20}]
    19. }
  • List subList(int fromIndex, int toIndex) : 返回从fromIndex到toIndex位置的子集合

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test04(){
    4. List list = new ArrayList<Object>();
    5. list.add(123);
    6. list.add(456);
    7. list.add(new String("Tom"));
    8. list.add(false);
    9. list.add(456);
    10. list.add(new Person("Jerry", 20));
    11. System.out.println(list);//[123, 456, Tom, false, 456, Person{name='Jerry', age=20}]
    12. //List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
    13. List<Object> subList = list.subList(2, 4);
    14. System.out.println(subList);//[Tom, false]
    15. System.out.println(list);//[123, 456, Tom, false, 456, Person{name='Jerry', age=20}]
    16. }
  • 遍历 : ①迭代器 ②增强for循环 ③普通for循环

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test05(){
    4. List list = new ArrayList<Object>();
    5. list.add(123);
    6. list.add(456);
    7. list.add(new String("Tom"));
    8. list.add(false);
    9. list.add(456);
    10. list.add(new Person("Jerry", 20));
    11. //方式一 : 迭代器
    12. Iterator iterator = list.iterator();
    13. while (iterator.hasNext()){
    14. System.out.print(iterator.next() + " ");
    15. }
    16. System.out.println();
    17. //方式二 : 增强for循环
    18. for (Object obj : list){
    19. System.out.print(obj + " ");
    20. }
    21. System.out.println();
    22. //方式三 : 普通for循环
    23. for (int i = 0; i < list.size(); i++) {
    24. System.out.print(list.get(i) + " ");
    25. }
    26. System.out.println();
    27. }

总结,常用方法 :

  1. 增 : add(Object obj)
  2. 删 : remove(int index) / remove(Object obj)
  3. 改 : set(int index, Object ele)
  4. 查 : get(int index)
  5. 插 : add(int index, Object obj)
  6. 长度 : size()
  7. 遍历 : ①迭代器 ②增强for循环 ③普通for循环

区分remove中remove(int index) ,remove(Object obj)方法,例子 :

  1. public class ListExer {
  2. public static void main(String[] args) {
  3. List list = new ArrayList();
  4. list.add(1);
  5. list.add(2);
  6. list.add(3);
  7. updateList(list);
  8. System.out.println(list);//[1, 2]
  9. }
  10. public static void updateList(List list){
  11. list.remove(2);
  12. //list.remove(new Integer(2))
  13. }
  14. }

remove方法有 : remove(int index) ,remove(Object obj)两个,在方法外调用remove方法时首先默认调用的是不需要自动装箱的方法,如果想要调用remove(Object obj),则需要手动装箱。

五、Collection子接口 : Set

Set接口是Collection的子接口,set接口没有提供额外的方法

Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。

Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法

1. Set的接口框架

  1. |----Collection接口 : 单列集合,用来存储一个一个的对象
  2. |----Set接口 : 存储无序的、不可重复的数据 --> 高中讲的 "集合"
  3. |----HashSet
  4. |----LinkedHashSet
  5. |----TreeSet

比较HashSet、LinkedHashSet、TreeSet三者的异同 ?

相同点 : 三个类都是实现了Set接口,存储数据的特点相同 : 存储无序的、不可重复的数据

不同点 :

  • HashSet : 作为Set接口的主要实现类,线程不安全的,可以存储null值

  • LinkedHashSet : 作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历

  • TreeSet : 可以按照添加对象的指定属性,进行排序

2. 如何理解Set接口特点

以HashSet为例说明 :

(1) 无序性 : 不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。无序只是指在存储的时候不按索引顺序添加。

(2) 不可重复性 : 保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test01(){
  4. Set set = new HashSet();
  5. set.add(456);
  6. set.add(123);
  7. set.add(123);
  8. set.add("AA");
  9. set.add("CC");
  10. set.add(new User("Tom", 12));
  11. set.add(new User("Tom", 12));
  12. set.add(129);
  13. System.out.println(set);
  14. //如果未重写equals方法 判断的地址值 所以不同
  15. //[AA, CC, User{name='Tom', age=12}, 129, User{name='Tom', age=12}, 456, 123]
  16. System.out.println(set);//重写equals方法之后 还是有两个
  17. //[AA, CC, User{name='Tom', age=12}, 129, User{name='Tom', age=12}, 456, 123]
  18. System.out.println(set);//重写了equals方法和hashCode方法之后 只有一个了
  19. //[AA, CC, 129, 456, 123, User{name='Tom', age=12}]
  20. }

3. 添加元素的过程

以HashSet为例 :

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法 (& (size - 1) 等操作) 计算出在HashSet底层数组中的存放位置 (即 : 索引位置) ,判断数组此位置上是否已经有元素 :

如果此位置上没有其他元素,则元素a添加成功 (情况1)。如果此位置上有其他元素b (或以链表形式存在的多个元素),则比较元素a与元素b的hash值 :

如果hash值不相同,则元素a添加成功 (情况2)。如果hash值相同,则进而需要调用元素a所在类的equals()方法 :

如果equals()返回false,则元素a添加成功 (情况3)。如果equals()返回true,则元素a添加失败。

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

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

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

HashSet底层 : 数组 + 链表的结构(前提JDK 7)

实际上HashSet的实现原理就是HashMap,HashSet的数据实际上就是HashMap中的key,而value存放的是一个静态Object类对象,没有意义。

4. hashCode()方法的重写

当向Set集合中添加数据时,在重写equals()方法时,建议同时重写hashCode(),并且保证两个方法的一致性 (相同的属性参与运算) ,这是因为在当对象放到HashSet集合或HashMap集合时,保证去重后的正确性。

了解hashCode方法和equals方法的两个重要的规范 :

规范一 : 两个对象相等,其hashCode一定相等

规范二 : 两个对象不等(equals方法返回false),并不要求这两个对象的hashCode得到不同的数

则得到如下推论 :

推论一 : hashCode相同的两个对象,不一定相等

推论二 : hashCode不同的两个对象,一定不相等

hashCode方法在Object类中定义,Object类中定义的hashCode方法调用的底层的C语言来计算出来一个随机数,这个随机数基本保证了不同的对象有不同的hashCode。如果我们不重写hashCode的话,在往HashSet集合中存放对象时,如果存放内容相等的两个对象,其hashCode不同,那两个对象都会存放成功,达不到去重效果。

重写hashCode()方法的基本原则 :

  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。

  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。

  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

  1. @Override
  2. public int hashCode() {
  3. int result = name != null ? name.hashCode() : 0;
  4. result = 31 * result + age;
  5. return result;
  6. }

为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

  • 并且31只占用5bits,相乘造成数据溢出的概率较小。

  • 31可以 由i * 31 == ( i << 5 ) - 1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)

  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

5. HashSet的使用

关于HashSet的使用,有关remove等方法的说明 : 先调用hashCode再用equals

  1. public class hashSetTest {
  2. public static void main(String[] args) {
  3. HashSet set = new HashSet();
  4. Person p1 = new Person(1001, "AA");
  5. Person p2 = new Person(1002, "BB");
  6. set.add(p1);
  7. set.add(p2);
  8. System.out.println(set);
  9. //[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
  10. p1.name = "CC";
  11. set.remove(p1);
  12. System.out.println(set);
  13. //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
  14. set.add(new Person(1001, "CC"));
  15. System.out.println(set);
  16. //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
  17. set.add(new Person(1001, "AA"));
  18. System.out.println(set);
  19. //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'},
  20. // Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
  21. }
  22. }

以上测试体现了HashSet的特点,当我们向HashSet中添加数据时,按照hashCode计算索引位置的规则进行添加的。当我们将添加以后的数据内容更改以后,其计算的hashCode将会改变。当我们remove改数据时,通过计算hashCode来寻找改元素计算得到的索引位置大概率不会是原来的位置,所以remove操作将会失败。当我们再向HashSet中添加一个更新后的数据时,由于原来更改后的数据没有占着索引位置,所以将会添加成功。当我们向HashSet中添加一个更新前的数据时,这时由于原来的数据占着其索引位置,那么就会比较hashCode,两个数据内容不同,所以hashCode不同,则添加成功。

6. LinkedHashSet的使用

LinkedHashSet是HashSet的子类,特点是在遍历的时候,可以按照添加的顺序遍历出来。原理是,在添加元素的同时还记录下了上一个添加的元素的地址,上一个添加的元素同时也记录了本次添加元素的地址。相当于形成了一个双向链表。

优点 : 对于频繁的遍历操作,LinkedHashSet效率高于HashSet。

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test02(){
  4. Set set = new LinkedHashSet();
  5. set.add(456);
  6. set.add(123);
  7. set.add(123);
  8. set.add("AA");
  9. set.add("CC");
  10. set.add(new User("Tom", 12));
  11. set.add(new User("Tom", 12));
  12. set.add(129);
  13. System.out.println(set);
  14. //[456, 123, AA, CC, User{name='Tom', age=12}, 129]
  15. }

7. TreeSet的使用

TreeSet : 可以按照添加对象的指定属性,进行排序

TreeSet底层使用的红黑树实现的,利用二叉树存储数据。调用compareTo()或compare()来判断元素是否相同。

在后续的调用contains方法、remove方法等,都是调用的compareTo或者compare方法来判断的。

向TreeSet中添加的数据,要求是同一个类的对象,不能添加不同类的对象,否则报ClassCastException异常。

向TreeSet中添加Integer对象 :

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test01(){
  4. TreeSet set = new TreeSet();
  5. set.add(34);
  6. set.add(-34);
  7. set.add(43);
  8. set.add(11);
  9. set.add(8);
  10. System.out.println(set);//[-34, 8, 11, 34, 43]
  11. Iterator it = set.iterator();
  12. while(it.hasNext()){
  13. Object obj = it.next();
  14. System.out.print(obj + " ");
  15. }//-34 8 11 34 43 从小到大的顺序
  16. }

7.1 TreeSet的自然排序

在创建TreeSet对象时,调用空参构造器,那么就会按照对象的自然排序方式进行操作。

向空参构造的TreeSet中添加自定义类User对象,其自定义的对象必须实现Comparable接口,否则报异常

ClassCastException: com.atguigu.study.User cannot be cast to java.lang.Comparable

自然排序中,比较两个对象是否相同的标准为 : compareTo()返回 0,不再是equals方法。两个对象是否相同的标准,是一个属性相同就认为两个对象相同,还是所有属性相同才认为两个对象相同,就要看自己在compareTo()方法中实现的逻辑了。

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test02(){
  4. TreeSet set = new TreeSet();
  5. set.add(new User("Tom", 12));
  6. set.add(new User("Jerry", 32));
  7. set.add(new User("Jim", 2));
  8. set.add(new User("Mike", 65));
  9. set.add(new User("Jack", 33));
  10. Iterator it = set.iterator();
  11. while(it.hasNext()){
  12. Object obj = it.next();
  13. System.out.print(obj + " ");
  14. }
  15. //User{name='Jack', age=33} User{name='Jerry', age=32} User{name='Jim', age=2}
  16. // User{name='Mike', age=65} User{name='Tom', age=12}
  17. }
  18. public class User implements Comparable{
  19. private String name;
  20. private int age;
  21. public User(){}
  22. public User(String name, int age){
  23. this.age = age;
  24. this.name = name;
  25. }
  26. //按照名字从小到大排
  27. @Override
  28. public int compareTo(Object o) {
  29. if (o instanceof User) {
  30. User user = (User) o;
  31. return this.name.compareTo(user.name);
  32. } else throw new RuntimeException("传入数据类型不一致");
  33. }
  34. }

7.2 TreeSet的定制排序

在创建TreeSet对象时,调用传入比较器的构造器,那么就会按照对象的定制排序方式进行操作。

定制排序中,比较两个对象是否相同的标准为 : compare()返回 0,不再是equals方法。

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test03(){
  4. //按照年龄从小到大排 生成一个比较器
  5. Comparator com = new Comparator() {
  6. @Override
  7. public int compare(Object o1, Object o2) {
  8. if (o1 instanceof User && o2 instanceof User) {
  9. User user1 = (User) o1;
  10. User user2 = (User) o2;
  11. return Integer.compare(user1.getAge(), user2.getAge());
  12. } else throw new RuntimeException("传入数据类型不一致");
  13. }
  14. };
  15. //构造器中传入一个比较器 按照定制排序方式操作
  16. TreeSet set = new TreeSet(com);
  17. set.add(new User("Tom", 12));
  18. set.add(new User("Jerry", 32));
  19. set.add(new User("Jim", 2));
  20. set.add(new User("Mike", 65));
  21. set.add(new User("Jack", 33));
  22. Iterator it = set.iterator();
  23. while(it.hasNext()){
  24. Object obj = it.next();
  25. System.out.print(obj + " ");
  26. }
  27. //User{name='Jim', age=2} User{name='Tom', age=12} User{name='Jerry', age=32}
  28. // User{name='Jack', age=33} User{name='Mike', age=65}
  29. }

六、Map接口

1. Map的接口框架

  1. |----Map : 双列数据,存储key-value对的数据 ---->类似于高中的函数 : y = f(x)
  2. |----HashMap : 作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value
  3. |----LinkedHashMap : 保证在遍历map元素时,可以按照添加的顺序实现遍历
  4. 原理 : 在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
  5. 对于频繁的遍历操作,此类执行效率高于HashMap。
  6. |----Hashtable : 作为古老的实现类,线程安全,效率低,不可以存储null的key和value
  7. |----Properties : 常用来处理配置文件。key-value都是String类型
  8. |----TreeMap : 保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
  9. 底层使用红黑树实现
  10. HashMap的底层 : 数组 + 链表 (JDK 7及之前)
  11. 数组 + 链表 + 红黑树(JDK 8)

2. 关于Map结构的理解

key-value拆分理解 :

Map中的key : 无序的、不可重复的,使用Set存储所有的key (以HashMap为例,key所在的类要重写equals()和hashCode())。

Map中的value : 无序的、可重复的,使用Collection存储所有的value(value所在的类要重写equals()方法)。

一个键值对 : key-value构成了一个Entry对象。

Map中的Entry : 无序的、不可重复的,使用Set存储所有的Entry。

3. HashMap的底层实现原理

3.1 JDK 7中

HashMap map = new HashMap();

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

  1. map.put()...
  2. map.put(key1, value1);

已经执行多次put以后的put(一个普通的put) 的执行过程 :

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

如果此位置上的数据为空,此时的key1-value1(Entry)添加成功 (情况1) 。如果此位置上的数据不为空(意味着此位置上存在一个或多个数据以链表形式存在),比较当前key1和已经存在的一个或多个数据的哈希值。

如果key1的哈希值与已经存在的数据都不相同,此时key1-value1添加成功 (情况2) 。如果key1的哈希值与已经存在的某个数据 (key2-value2) 的哈希值相同,继续比较,调用key1所在类的equals()方法比较。

如果equals()返回false,此时key1-value1添加成功 (情况3) 。如果equals()返回true,则将使用value1替换value2。

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test02(){
  4. HashMap map = new HashMap();
  5. //put具有修改功能
  6. map.put("Tom", 12);
  7. System.out.println(map);//{Tom=12}
  8. map.put("Tom", 45);
  9. System.out.println(map);//{Tom=45}
  10. }

补充 : 关于情况2和情况3,此时key1-value1和原来的数据以链表的方式 (JDK 7新元素在上) 存储。

在添加过程中,涉及到扩容问题,当超出临界值时 (且要存放的位置非空) 默认的扩容方式 : 扩容为原来数组的二倍,并将原有的数据复制过来。在复制的时候会重新计算各个元素的位置,重新存放。

3.2 JDK 8中

JDK 8相较于JDK 7在底层实现方面的不同 :

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

  • JDK 8底层的数组是Node[],而非Entry[]

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

  • JDK 7底层结构只有 : 数组 + 链表,JDK 8底层结构 : 数组 + 链表 + 红黑树。当数组的某一个索引位置上的以链表形式存在的数据个数大于8,且当前数组的长度大于64时,此时,此索引位置上的所有数据改为使用红黑树存储。

HashMap源码中的重要常量 :

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

  • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子 (可以控制链表的多少),0.75 (比较稳定的数值)

  • threshold:扩容的临界值,容量 x 填充因子 = 0.75 x 16 = 12

  • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 ,8

  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,64

负载因子值的大小对HashMap有什么影响 ?

  • 负载因子的大小决定了HashMap的数据密度。

  • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。

  • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。

  • 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。

4. LinkedHashMap的底层实现原理

能够按照添加的顺序进行遍历 :

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test01(){
  4. Map map1 = new HashMap();
  5. map1.put(123, "AA");
  6. map1.put(456, "BB");
  7. map1.put(789, "CC");
  8. System.out.println(map1);//{789=CC, 456=BB, 123=AA}
  9. Map map2 = new LinkedHashMap();
  10. map2.put(123, "AA");
  11. map2.put(456, "BB");
  12. map2.put(789, "CC");
  13. System.out.println(map2);//{123=AA, 456=BB, 789=CC}
  14. }

在源码中,内部类Entry :

  1. static class Entry<K,V> extends HashMap.Node<K,V> {
  2. Entry<K,V> before, after;
  3. Entry(int hash, K key, V value, Node<K,V> next) {
  4. super(hash, key, value, next);
  5. }
  6. }

增加了两个指针来记录上一个添加的元素和下一个添加的元素地址,遍历的时候通过该指针实现按照添加顺序遍历。

5. Map接口中的方法

添加、删除、修改操作:

Object put(Object key,Object value) : 将指定key-value添加到(或修改)当前map对象中

void putAll(Map m) : 将m中的所有key-value对存放到当前map中

Object remove(Object key) : 移除指定key的key-value对,并返回value

void clear() : 清空当前map中的所有数据

  1. /*
  2. Map接口中的常用方法测试
  3. */
  4. @SuppressWarnings("rawtypes")
  5. @Test
  6. public void test03(){
  7. Map map = new HashMap();
  8. //1. Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  9. //添加
  10. map.put("AA", 123);
  11. map.put(45, 123);
  12. map.put("BB", 56);
  13. //修改
  14. map.put("AA", 87);
  15. System.out.println(map);//{AA=87, BB=56, 45=123}
  16. Map map1 = new HashMap();
  17. map1.put("CC", 123);
  18. map1.put("DD", 123);
  19. //2. void putAll(Map m):将m中的所有key-value对存放到当前map中
  20. map.putAll(map1);
  21. System.out.println(map);
  22. //{AA=87, BB=56, CC=123, DD=123, 45=123}
  23. //3. Object remove(Object key):移除指定key的key-value对,并返回value
  24. Object value = map.remove("CC");
  25. Object ee = map.remove("EE");
  26. System.out.println(value);//123
  27. System.out.println(ee);//null remove的key不存在将返回null
  28. System.out.println(map);//{AA=87, BB=56, DD=123, 45=123}
  29. //4. void clear():清空当前map中的所有数据
  30. map.clear();//与map = null操作不同
  31. System.out.println(map.size());//0
  32. System.out.println(map);//{}
  33. }

元素查询的操作:

Object get(Object key) : 获取指定key对应的value

boolean containsKey(Object key) : 是否包含指定的key

boolean containsValue(Object value) : 是否包含指定的value

int size() : 返回map中key-value对的个数

boolean isEmpty() : 判断当前map是否为空

boolean equals(Object obj) : 判断当前map和参数对象obj是否相等

  1. /*
  2. Map接口的常用方法测试
  3. */
  4. @SuppressWarnings("rawtypes")
  5. @Test
  6. public void test04(){
  7. Map map = new HashMap();
  8. map.put("AA", 123);
  9. map.put(45, 123);
  10. map.put("BB", 56);
  11. //1. Object get(Object key):获取指定key对应的value
  12. System.out.println(map.get("AA"));//123
  13. System.out.println(map.get("CC"));//null
  14. //2. boolean containsKey(Object key):是否包含指定的key
  15. boolean isExistKey = map.containsKey("AA");
  16. System.out.println(isExistKey);//true
  17. //3. boolean containsValue(Object value):是否包含指定的value
  18. boolean isExistVal = map.containsValue(123);
  19. System.out.println(isExistVal);//true
  20. //4. int size():返回map中key-value对的个数
  21. System.out.println(map.size());//3
  22. //5. boolean isEmpty():判断当前map是否为空
  23. boolean isEmpty = map.isEmpty();
  24. System.out.println(isEmpty);//false
  25. //6. boolean equals(Object obj):判断当前map和参数对象obj是否相等
  26. //要想为true则必须传入一个map且数据相同
  27. }

元视图操作的方法:

Set keySet() : 返回所有key构成的Set集合

Collection values() : 返回所有value构成的Collection集合

Set entrySet() : 返回所有key-value对构成的Set集合

  1. /*
  2. Map接口中的常用方法测试
  3. */
  4. @SuppressWarnings("rawtypes")
  5. @Test
  6. public void test05(){
  7. Map map = new HashMap();
  8. map.put("AA", 123);
  9. map.put(45, 123);
  10. map.put("BB", 56);
  11. //1. 遍历所有的Key集合 :
  12. Set keySet = map.keySet();
  13. for (Object o : keySet) {
  14. System.out.print(o + " ");
  15. }//AA BB 45
  16. System.out.println();
  17. //2. 遍历所有的values集 :
  18. Collection values = map.values();
  19. Iterator it = values.iterator();
  20. while(it.hasNext()){
  21. Object obj = it.next();
  22. System.out.print(obj + " ");
  23. }//123 56 123
  24. System.out.println();
  25. //3. 遍历所有的Entry集 :
  26. //方式一 :
  27. Set entrySet = map.entrySet();
  28. Iterator iterator = entrySet.iterator();
  29. while(iterator.hasNext()){
  30. Object obj = iterator.next();
  31. //entrySet集合中的元素都是entry
  32. Map.Entry entry = (Map.Entry) obj;
  33. System.out.println(entry.getKey() + " ---> " + entry.getValue());
  34. }
  35. /*
  36. AA ---> 123
  37. BB ---> 56
  38. 45 ---> 123
  39. */
  40. System.out.println();
  41. //方式二 :
  42. Iterator iterator1 = keySet.iterator();
  43. while(iterator1.hasNext()){
  44. Object key = iterator1.next();
  45. Object value = map.get(key);
  46. System.out.println(key + " ---> " + value);
  47. }
  48. /*
  49. AA ---> 123
  50. BB ---> 56
  51. 45 ---> 123
  52. */
  53. }

总结 : 常用方法

  1. 增 : put(Object key, Object value)
  2. 删 : remove(Object key)
  3. 改 : put(Object key, Object value)
  4. 查 : get(Object key)
  5. 长度 : size()
  6. 遍历 : keySet()/values()/entrySet()

6. TreeMap的使用

向TreeMap中添加key-value,要求key必须是由同一个类创建的对象。

只能按照key来排,不能按照value排序。

6.1 TreeMap的自然排序

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test01(){
  4. TreeMap map = new TreeMap();
  5. User u1 = new User("Tom", 23);
  6. User u2 = new User("Jerry", 32);
  7. User u3 = new User("Jack", 20);
  8. User u4 = new User("Rose", 18);
  9. map.put(u1, 98);
  10. map.put(u2, 89);
  11. map.put(u3, 76);
  12. map.put(u4, 100);
  13. Set entrySet = map.entrySet();
  14. Iterator iterator = entrySet.iterator();
  15. while(iterator.hasNext()){
  16. Object obj = iterator.next();
  17. //entrySet集合中的元素都是entry
  18. Map.Entry entry = (Map.Entry) obj;
  19. System.out.println(entry.getKey() + " ---> " + entry.getValue());
  20. }
  21. /*
  22. User{name='Jack', age=20} ---> 76
  23. User{name='Jerry', age=32} ---> 89
  24. User{name='Rose', age=18} ---> 100
  25. User{name='Tom', age=23} ---> 98
  26. */
  27. }

6.2 TreeMap的定制排序

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test02(){
  4. TreeMap map = new TreeMap(new Comparator() {
  5. @Override
  6. public int compare(Object o1, Object o2) {
  7. if (o1 instanceof User && o2 instanceof User) {
  8. User u1 = (User) o1;
  9. User u2 = (User) o2;
  10. //按照年龄排
  11. return Integer.compare(u1.getAge(), u2.getAge());
  12. } else throw new RuntimeException("传入的类型不一致");
  13. }
  14. });
  15. User u1 = new User("Tom", 23);
  16. User u2 = new User("Jerry", 32);
  17. User u3 = new User("Jack", 20);
  18. User u4 = new User("Rose", 18);
  19. map.put(u1, 98);
  20. map.put(u2, 89);
  21. map.put(u3, 76);
  22. map.put(u4, 100);
  23. Set entrySet = map.entrySet();
  24. Iterator iterator = entrySet.iterator();
  25. while(iterator.hasNext()){
  26. Object obj = iterator.next();
  27. //entrySet集合中的元素都是entry
  28. Map.Entry entry = (Map.Entry) obj;
  29. System.out.println(entry.getKey() + " ---> " + entry.getValue());
  30. }
  31. /*
  32. User{name='Rose', age=18} ---> 100
  33. User{name='Jack', age=20} ---> 76
  34. User{name='Tom', age=23} ---> 98
  35. User{name='Jerry', age=32} ---> 89
  36. */
  37. }

7. Properties的使用

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

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

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

  1. public class PropertiesTest {
  2. public static void main(String[] args) {
  3. FileInputStream fis = null;
  4. try {
  5. Properties pros = new Properties();
  6. fis = new FileInputStream("jdbc.properties");
  7. pros.load(fis);//加载流对应的文件
  8. String name = pros.getProperty("name");//获取value
  9. String password = pros.getProperty("password");
  10. System.out.println("name = " + name + " password = " + password);
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. } finally {
  14. if (fis != null)
  15. try {
  16. fis.close();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }

七、Collections工具类

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

Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。

1. 排序操作

关于集合的排序操作,均为static方法 :

  • reverse(List) : 反转List 中元素的顺序

  • shuffle(List) : 对List集合元素进行随机排序

  • sort(List) : 根据元素的自然顺序对指定List集合元素按升序排序

  • sort(List,Comparator) : 根据指定的Comparator产生的顺序对List 集合元素进行排序

  • swap(List,int,int) : 将指定list 集合中的 i 处元素和 j 处元素进行交换

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test01(){
    4. List list = new ArrayList();
    5. list.add(123);
    6. list.add(43);
    7. list.add(765);
    8. list.add(-97);
    9. list.add(0);
    10. System.out.println(list);//[123, 43, 765, -97, 0]
    11. //1. reverse(List) : 反转List中元素的顺序
    12. Collections.reverse(list);
    13. System.out.println(list);//[0, -97, 765, 43, 123]
    14. //2. shuffle(List) : 对List集合元素进行随机排序 (随机化)
    15. Collections.shuffle(list);
    16. System.out.println(list);//[-97, 0, 43, 123, 765]
    17. //3. sort(List) : 根据元素的自然顺序对指定List集合元素按升序排序
    18. Collections.sort(list);
    19. System.out.println(list);//[-97, 0, 43, 123, 765]
    20. //4. swap(List,int,int) : 将指定list集合中的i处元素和j处元素进行交换
    21. Collections.swap(list, 0, 1);
    22. System.out.println(list);//[0, -97, 43, 123, 765]
    23. }

2. 查找替换操作

  • Object max(Collection) : 根据元素的自然顺序,返回给定集合中的最大元素

  • Object max(Collection,Comparator) : 根据Comparator 指定的顺序,返回给定集合中的最大元素

  • Object min(Collection)

  • Object min(Collection,Comparator)

  • int frequency(Collection,Object) : 返回指定集合中指定元素的出现次数

    1. @SuppressWarnings("rawtypes")
    2. @Test
    3. public void test02(){
    4. List list = new ArrayList();
    5. list.add(123);
    6. list.add(43);
    7. list.add(765);
    8. list.add(765);
    9. list.add(765);
    10. list.add(-97);
    11. list.add(0);
    12. System.out.println(list);
    13. //1. Object max(Collection) : 根据元素的自然顺序,返回给定集合中的最大元素
    14. Comparable max = Collections.max(list);
    15. System.out.println(max);//765
    16. //2. Object min(Collection)
    17. Comparable min = Collections.min(list);
    18. System.out.println(min);//-97
    19. //3. int frequency(Collection,Object) : 返回指定集合中指定元素的出现次数
    20. int frequency = Collections.frequency(list, 765);
    21. System.out.println(frequency);//3
    22. }
  • void copy(List dest,List src) : 将src中的内容复制到dest中

  • boolean replaceAll(List list,Object oldVal,Object newVal) : 使用新值替换List对象的所有旧值

    1.    @SuppressWarnings("rawtypes")
    2.    @Test
    3.    public void test03(){
    4.        List list = new ArrayList();
    5.        list.add(123);
    6.        list.add(43);
    7.        list.add(765);
    8.        list.add(-97);
    9.        list.add(0);
    10.        System.out.println(list);
    11.        //1. void copy(List dest,List src) : 将src中的内容复制到dest中
    12.        //错误 报异常 底层要判断 src.size() > dest.size()
    13.        //java.lang.IndexOutOfBoundsException: Source does not fit in dest
    14. //       List dest = new ArrayList();
    15. //       Collections.copy(dest, list);
    16.        //利用Object数组把dest的size撑开
    17.        List dest = Arrays.asList(new Object[list.size()]);
    18.        Collections.copy(dest, list);
    19.        System.out.println(dest);//[123, 43, 765, -97, 0]
    20.        //2. boolean replaceAll(List list,Object oldVal,Object newVal) : 使用新值替换List对象的所有旧值
    21.        Collections.replaceAll(list, 123, 0);
    22.        System.out.println(list);//[0, 43, 765, -97, 0]
    23.   }

3. 同步控制

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

  1. @SuppressWarnings("rawtypes")
  2. @Test
  3. public void test04(){
  4.    List list = new ArrayList();
  5.    list.add(123);
  6.    list.add(43);
  7.    list.add(765);
  8.    list.add(-97);
  9.    list.add(0);
  10.    //synchronizedXxx() : 该方法可使将指定集合包装成线程同步的集合
  11.    //返回的synchronizedList即为线程安全的List
  12.    List synchronizedList = Collections.synchronizedList(list);
  13.    System.out.println(synchronizedList);//[123, 43, 765, -97, 0]
  14. }
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号