Java基础学习——第十一章 Java集合

Java基础学习——第十一章 Java集合

一、Java集合框架概述

1. Java容器:数组与集合

  1. 面向对象编程对事物的体现都是以对象的形式,为了便于对多个对象进行操作,就要对对象进行存储
  2. Java容器:集合和数组都是对多个数据进行存储(内存层面)的结构;① 数组在存储对象方面存在着一些弊端;② 集合可以动态地多个对象的引用放入容器中
  3. 数组在存储多个数据方面的弊端:
    • 数组一旦初始化以后,长度就不可变了,不便于扩展
    • 数组一旦声明类型后,其元素的类型也就确定了,不能再存储其他类型的数据(多态情况除外)
    • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作, 且效率不高。同时无法直接获取存储元素的个数
    • 数组存储的数据是有序的、可以重复的 —> 存储数据的特点单一
  4. Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组

2. 集合的使用场景

在这里插入图片描述

3. 集合的两种体系:Collection & Map

  • Java 集合可分为 Collection 和 Map 两种体系
    1. Collection接口单列集合,用来存储一个一个的==对象==
      • List子接口元素有序可重复的集合 —> 动态“数组”
        • 实现类:ArrayList、LinkedList、Vector
      • Set子接口元素无序不可重复的集合 —> 数学中的“集合”
        • 实现类:HashSet、LinkedHashSet、TreeSet
    2. Map接口双列集合,用来存储一对一对的,具有==映射关系“key-value”==的数据 —> 函数:y = f(x)
      • 实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
  • 注意:元素有序/无序是指在当前集合中,存储的对象在底层数组中是否按照数组的索引顺序添加
3.1 Collection接口继承树

在这里插入图片描述

  • 实线表示继承关系;虚线表示实现关系
3.2 Map接口继承树

在这里插入图片描述

二、Collection接口

1. Collection接口概述

  1. Collection接口是 List、Set和Queue接口的父接口,该接口中定义的方法可用于操作List、Set和Queue集合
  2. JDK不提供Collection接口的任何直接实现类,而是提供更具体的子接口,如:List 和 Set
  3. Collection接口:单列集合,用来存储一个一个的对象;对于基本数据类型元素,实际存储的是其包装类对象
  4. 在JDK5.0之前, Java集合会丢失容器中所有对象的数据类型,把所有对象都当成Object类型处理;从JDK5.0增加了泛型以后,Java 集合可以记住容器中对象的数据类型

2. Collection接口中的常用方法

  • 通常向Collection接口的实现类对象(集合)中添加对象时,要求该对象所在类重写equals()方法,比较对象的实际内容
  1. 添加(增):将指定对象(或指定集合中的所有元素)添加到当前集合中(末尾位置)
    • add(Object obj):将对象obj添加到当前集合中
    • addAll(Collection coll):将另一个集合coll中的元素全部添加到当前集合中
  2. 获取当前集合中有效元素的个数
    • int size()
  3. 清空当前集合中的所有元素
    • void clear()
  4. 判断当前集合是否为空集合,即size()方法的返回值是否等于0
    • boolean isEmpty()
  5. 判断当前集合是否包含某个对象obj(或另一个集合c中的所有元素)
    • boolean contains(Object obj):调用形参对象obj所在类的equals()方法来判断
    • boolean containsAll(Collection c):调用形参集合c中的元素所在类的equals()方法来判断,挨个比较
  6. 删除(删):从当前集合中删除指定元素(或和指定集合的交集元素)
    • boolean remove(Object obj):调用形参对象obj所在类的equals()方法来判断当前集合中的元素是否为要删除的元素,仅删除第一个找到的元素
    • boolean removeAll(Collection coll):取两个集合的差集,调用形参集合c中的元素所在类的equals()方法来判断,挨个比较
  7. 取两个集合的交集
    • boolean retainAll(Collection c):将返回的交集元素存储在当前调用该方法的集合中,不影响形参集合c
  8. 判断两个集合是否相等:比较两个集合中的元素内容是否一致(如果是List的实现类还需比较元素排列的顺序是否完全一致)
    • boolean equals(Object obj):形参对象obj应当为Collection接口的实现类的对象(集合)
  9. 将当前集合转化成对象数组:集合 --> 数组
    • Object[] toArray()
    • 拓展:数组 --> 集合:调用Arrays类的静态方法asList()
  10. 返回当前集合对象的哈希值
    • int hashCode()
  11. 遍历:返回一个实现了Iterator接口的迭代器对象,用于遍历 Collection 集合元素
    • iterator()
import org.junit.Test;
import java.util.*;

public class CollectionTest {
    @Test
    public void test1() {
        //ArrayList类是Collection接口的子接口List的实现类
        Collection coll = new ArrayList();

        //add(Object obj):将对象obj添加到当前集合中
        coll.add("AA");
        coll.add("BB");
        coll.add(123); //自动装箱
        coll.add(new Date());

        //size():获取有效元素的个数
        System.out.println(coll.size()); //4

        //addAll(Collection coll):将另一个集合coll中的元素全部添加到当前集合中
        Collection coll1 = new ArrayList();
        coll1.add(456);
        coll1.add("CC");
        coll1.addAll(coll);
        System.out.println(coll1.size()); //6
        //实际执行的是ArrayList的父类AbstractList中重写的toString方法
        System.out.println(coll1); //[456, CC, AA, BB, 123, Wed Apr 06 18:43:02 CST 2022]

        //clear():清空当前集合中的所有元素
        coll1.clear();

        //isEmpty():判断当前集合是否为空集合
        System.out.println(coll1.isEmpty()); //true

        //contains(Object obj):判断当前集合是否包含某个对象(调用形参对象obj所在类的equals()方法来判断)
        System.out.println(coll.contains(123)); //true
        System.out.println(coll.contains(new String("AA"))); //true

        //containsAll(Collection c):判断当前集合是否包含另一个集合中所有元素(调用形参集合c中的元素所在类的equals()方法来判断,挨个比较)
        Collection coll2 = Arrays.asList(123, "AA");
        System.out.println(coll.containsAll(coll2)); //true

        //remove(Object obj):从当前集合中删除指定元素
        //调用形参对象obj所在类的equals()方法来判断当前集合中的元素是否为要删除的元素,仅删除第一个找到的元素
        coll.remove("AA");
        System.out.println(coll); //[BB, 123, Wed Apr 06 20:46:05 CST 2022]
        coll.remove(new String("BB"));
        System.out.println(coll); //[123, Wed Apr 06 20:46:05 CST 2022]

        //removeAll(Collection coll):从当前集合中删除和指定集合的交集元素
        //调用形参集合c中的元素所在类的equals()方法来判断,挨个比较
        Collection coll3 = Arrays.asList(123, 4567);
        coll.removeAll(coll3);
        System.out.println(coll); //[Wed Apr 06 21:22:41 CST 2022]

        //retainAll(Collection c):将返回的交集元素存储在当前调用该方法的集合中,不影响形参集合c
        Collection coll4 = new ArrayList();
        coll4.add(123);
        coll4.add(456);
        coll4.add("AA");
        coll4.add(new String("BB"));
        Collection coll5 = Arrays.asList(123, 456, "CC");
        coll4.retainAll(coll5);
        System.out.println(coll4); //[123, 456]
        System.out.println(coll5); //[123, 456, CC]

        //equals(Object obj):判断两个集合是否相等:比较两个集合中的元素内容是否一致(如果是List的实现类还需比较元素排列的顺序是否完全一致)
        //形参对象obj应当为Collection接口的实现类的对象(集合)
        Collection coll6 = Arrays.asList(123, 456);
        System.out.println(coll4.equals(coll6)); //true
        Collection coll7 = Arrays.asList(456, 123);
        System.out.println(coll4.equals(coll7)); //false:如果是List的实现类还需比较元素排列的顺序是否完全一致

        //toArray():将当前集合转化成对象数组  集合 --> 数组
        Object[] array = coll7.toArray();
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " "); //456 123
        }
        System.out.println();
        //拓展:数组 --> 集合:调用Arrays类的静态方法asList()
        List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
        System.out.println(list); //[AA, BB, CC]

        //hashCode():返回当前集合对象的哈希值
        System.out.println(coll7.hashCode()); //15220
    }
}

三、Iterator迭代器接口

1. Iterator接口概述

  1. Iterator 对象称为迭代器(设计模式的一种),专门用于遍历 Collection 接口的实现类对象(集合)中各个元素
  2. 迭代器模式的定义为:提供一种方法访问一个容器对象中各个元素,而又无需暴露该对象的内部细节。 迭代器模式,就是为容器而生
  3. Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,因此所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的迭代器对象
  4. Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力如果需要创建Iterator对象,则必须有一个被迭代的集合

2. Iterator接口常用方法

  1. boolean hasNext():判断当前指针所处位置是否还有下一个元素
  2. next():①指针下移一位;②返回下移后的位置对应的集合元素
  3. void remove():删除通过当前迭代器的next()方法返回的最后一个集合元素

3. 使用Iterator迭代器对象遍历Collection集合元素

  1. 对于某个实现了Collection接口的集合类对象,调用其iterator()方法,返回一个实现了Iterator接口的迭代器对象,默认指针在集合的第一个元素之前
    • 注意:创建迭代器不能使用匿名对象,集合对象每次调用iterator()方法都会返回一个全新的迭代器对象指针都会复位到集合的第一个元素之前
  2. 在调用迭代器对象的next()方法之前必须先调用hasNext()方法进行检测,判断当前指针所处位置是否还有下一个元素。若不调用,且下一条记录无效,直接调用next()方法会抛NoSuchElementException异常
  3. 调用迭代器对象的next()方法:①指针下移一位;②返回下移后的索引对应的集合元素
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorTest {
    @Test
    public void test1() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add("AA");
        coll.add(new String("Tom"));
        coll.add(false);
		
		//调用实现了Collection接口的集合类的iterator()方法
        //返回一个实现了Iterator接口的迭代器对象,默认指针在集合的第一个元素之前
        Iterator iterator = coll.iterator();
        
        //使用Iterator迭代器遍历该集合中的元素:
        //hasNext():判断当前指针所处位置是否还有下一个元素
        while (iterator.hasNext()) {
            //next(): ①指针下移一位;②返回下移后的位置对应的集合元素
            System.out.println(iterator.next());
        }
    }
}

在这里插入图片描述

4. Iterator接口中的remove()方法

  1. void remove():删除通过当前迭代器的next()方法返回的最后一个集合元素
  2. Iterator可以删除集合的元素,但是是在遍历过程中通过调用迭代器对象的remove方法,不是集合对象的remove方法
  3. 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorTest {
    @Test
    public void test2() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add("AA");
        coll.add(new String("Tom"));
        coll.add(false);

        //删除集合中的"Tom"元素:
        Iterator iterator = coll.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            if ("Tom".equals(obj)) {
                //remove():调用迭代器的remove()方法,删除当前指针所处位置的集合元素
                iterator.remove();
            }
        }

        //使用Iterator迭代器遍历该集合中的元素:
        //由于上面的迭代器对象指针已经到了集合最后一个元素的位置,需要再新建一个迭代器对象
        Iterator iterator1 = coll.iterator();
        while (iterator1.hasNext()) {
            System.out.println(iterator1.next());
        }
    }
}

5. 使用foreach循环(增强for)遍历Collection集合元素

  1. JDK5.0新增了 foreach 循环(增强 for 循环),用于迭代遍历 Collection 集合和数组
  2. foreach 遍历无需获取 Collection 集合或数组的长度,无需使用索引访问指定元素
  3. foreach 遍历集合的底层调用 Iterator 迭代器接口完成
  4. foreach 还可以用来遍历数组
  5. foreach 循环遍历集合or数组的格式:for (集合或数组元素的类型 局部变量 : 集合或数组对象的名称) {}

在这里插入图片描述

import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;

public class ForeachTest {
    @Test
    public void test1() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add("AA");
        coll.add(new String("Tom"));
        coll.add(false);

        //使用foreach循环(增强for循环)遍历集合中的元素:
        //for (集合元素的类型 局部变量 : 集合对象的名称) {}
        for (Object obj : coll) {
            System.out.println(obj);
        }
    }

    @Test
    public void test2() {
        int[] arr = new int[]{1, 2, 3, 4, 5, 6};
        //使用foreach循环(增强for循环)遍历数组中的元素:
        //for (数组元素的类型 局部变量 : 数组对象的名称) {}
        for (int i : arr) {
            System.out.println(i);
        }
    }
}
5.1 增强for循环面试题
@Test
public void test3() {
    String[] arr = new String[]{"MM", "MM", "MM"};

    for (String s : arr) {
        s = "GG"; //注意这里只是给局部变量s赋值,而不是给数组中的元素赋值
        System.out.println(s); //GG
    }

    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]); //MM
    }
}

四、Collection子接口一:List接口

1. List接口概述

  1. 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组 --> 动态“数组”
  2. List集合类中 元素有序、可重复,集合中的每个元素都有其对应的顺序索引
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  4. 常用的List接口的实现类:① ArrayList;② LinkedList;③ Vector

2. List接口实现类之一:ArrayList

  1. ArrayList 是 List 接口的主要实现类
  2. 线程不安全的,执行效率高
  3. 底层使用Object类型的数组存储(顺序存储)
  4. 本质上,ArrayList实现了基于“动态数组“的数据结构
  5. 对于随机访问get和set,ArrayList的效率比LinkedList高,因为LinkedList要移动指针
  6. ArrayList每次扩容为原来容量的1.5倍
2.1 ArrayList源码分析:JDK7.0
  1. JDK7.0的ArrayList实现像单例模式的饿汉式:在实例化ArrayList对象时,底层直接创建一个初始长度为10的Object类型的对象数组elementDate
  2. 若某项操作导致底层数组elementDate容量不足,则进行扩容(默认为1.5倍):本质上新建一个长度为原来长度1.5倍的数组,并将原有数组中的元素按次序复制到新数组中 Arrays.copyOf()
  3. 在实际开发中,当需要实例化ArrayList对象时,推荐使用带参构造器指定ArrayList集合的初始容量
//在实例化ArrayList对象时,底层直接创建一个初始长度为10的Object[]数组elementDate
//本质上:Object[] elementDate = new Object[10];
ArrayList list = new ArrayList();

//本质上:elementDate[0] = new Integer(123);
list.add(123);

...

//若此次添加导致底层数组elementDate容量不足,则扩容:默认情况下,扩容为原来容量的1.5倍
list.add(11);

//推荐使用带参构造器指定ArrayList集合的初始容量
ArrayList list1 = new ArrayList(int initialCapacity);
2.2 ArrayList源码分析:JDK8.0的变化
  1. JDK8.0的ArrayList实现像单例模式的懒汉式:在实例化ArrayList对象时,底层Object类型的对象数组elementDate初始化为{},即数组长度为0;当添加第一个元素时,再创建一个初始长度为10的Object[]
  2. JDK8.0的改进延迟了ArrayList对象在实例化时底层数组的创建,节省了内存
//在实例化ArrayList对象时,底层Object类型的对象数组elementDate初始化为{},即数组长度为0
//本质上:Object[] elementDate = {};
ArrayList list = new ArrayList();

//当添加第一个元素时,再创建一个初始长度为10的Object[],并将数据添加到新建的数组中
//本质上:elementDate = new Object[10];
//elementDate[0] = new Integer(123);
list.add(123);

...

//后续的添加和扩容操作于JDK7.0一样

3. List接口实现类之二:LinkedList

  1. 底层使用==双向链表==存储
  2. 对于频繁的==插入和删除操作,LinkedList的效率比ArrayList高==
3.1 LinkedList源码分析
  1. LinkedList中声明了内部类Node,作为LinkedList存储数据的基本结构,即LinkedList对象中的==每个元素实际上都是一个Node类的对象=。每个Node对象声明了三个属性:
    • **item:**用于存储当前集合元素的真实数据,指向实际存储的对象
    • **prev:**指向前一个元素(Node类的对象)
    • **next:**指向下一个元素(Node类的对象)

在这里插入图片描述

  1. 在实例化LinkedList对象时:底层并没有声明数组,而是声明了 Node 类型的 first 和 last 属性(引用类型变量,用于指向双向链表首末位置的元素(Node类的对象)),默认值为null
  2. 当调用**add()**方法往LinkedList对象中添加元素时:
    • ① 创建了一个新的Node对象,该Node对象的item属性用于存储该元素的真实数据,指向实际要存储的对象;prev属性指向前一个元素(Node类的对象);next属性默认初始化为null
    • ② 修改前一个元素(Node类的对象)的next属性,使其指向当前新建的Node对象
    • ③ 修改LinkedList对象的last属性,指向新建的Node对象;若该新建的Node对象为该集合的首个元素,则使first属性也指向该新建的Node对象
//实例化LinkedList对象:底层声明了Node类型的first和last属性(引用变量,用于指向链表首末元素),默认值为null
LinkedList list = new LinkedList();

/*
① 创建了一个新的Node对象,该Node对象的item属性用于存储该元素的真实数据,指向实际要存储的对象"123";prev属性指向前一个元素(Node类的对象);next属性默认初始化为null
② 修改前一个元素(Node类的对象)的next属性,使其指向当前新建的Node对象
③ 修改LinkedList对象的last属性,指向新建的Node对象;若该新建的Node对象为该集合的首个元素,则使first属性也指向该新建的Node对象
 */
list.add(123);
3.2 LinkedList新增方法
  1. void addFirst(Object obj)
  2. void addLast(Object obj)
  3. Object getFirst()
  4. Object getLast()
  5. Object removeFirst()
  6. Object removeLast()

4. List接口实现类之三:Vector(基本不用)

  1. List接口的古老实现类,大多数操作与ArrayList相同
  2. 线程安全的(Vector类中的方法大多为同步方法),执行效率低
  3. 底层使用Object类型的数组存储(顺序存储)
  4. Vector每次扩容为原来容量的2倍

面试题:比较ArrayList & LinkedList & Vector

  1. 三者的共同点:
    • ① 都是List接口的实现类
    • ② 存储数据的特点相同:单列集合,用来存储一个一个的对象,元素有序、可重复
  2. 三者的不同点:
    • ArrayList(JDK1.0):
      • ① List接口的主要实现类
      • 线程不安全的,执行效率高
      • ③ 底层使用Object类型的数组存储(顺序存储)
      • ④ 对于随机访问get和set,ArrayList的效率比LinkedList高,因为LinkedList要移动指针
      • ⑤ ArrayList每次扩容为原来容量的1.5倍
    • LinkedList(JDK1.2):
      • ① 底层使用双向链表存储
      • ② 对于频繁的插入和删除操作,LinkedList的效率比ArrayList高
    • Vector(JDK1.0):
      • ① List接口的古老实现类,大多数操作与ArrayList相同,基本不用了
      • 线程安全的(Vector类中的方法大多为同步方法),执行效率低
      • ③ 底层使用Object类型的数组存储(顺序存储)
      • ④ Vector每次扩容为原来容量的2倍

5. List接口常用方法

  • List接口中除了包含从Collection接口继承的方法外,还添加了一些根据索引来操作集合元素的方法
  1. void add(int index, Object ele):在index位置插入ele元素
  2. boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素插入进来
  3. Object get(int index):获取指定index位置的元素
  4. int indexOf(Object obj):返回对象obj在集合中首次出现的位置
  5. int lastIndexOf(Object obj):返回对象obj在当前集合中最后一次出现的位置
  6. Object remove(int index):删除指定index位置的元素,并返回此元素
  7. Object set(int index, Object ele):设置指定index位置的元素为ele
  8. List subList(int fromIndex, int toIndex):返回从索引fromIndex到toIndex的子集合(左闭右开)
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class ListTest {
    @Test
    public void test1() {
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");
        list.add(new Date());
        list.add(456);

        //实际执行的是ArrayList的父类AbstractList中重写的toString方法
        System.out.println(list); //[123, 456, AA, Thu Apr 07 14:48:12 CST 2022, 456]

        //1. void add(int index, Object ele):在index位置插入ele元素
        list.add(1, "BB");
        System.out.println(list); //[123, BB, 456, AA, Thu Apr 07 14:54:52 CST 2022, 456]

        //2. boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素插入进来
        List list1 = Arrays.asList(1, 2, 3);
        list.addAll(1, list1);
        System.out.println(list); //[123, 1, 2, 3, BB, 456, AA, Thu Apr 07 15:01:25 CST 2022, 456]

        //3. Object get(int index):获取指定index位置的元素
        System.out.println(list.get(1)); //1

        //4. int indexOf(Object obj):返回对象obj在集合中首次出现的位置
        System.out.println(list.indexOf(456)); //5

        //5. int lastIndexOf(Object obj):返回对象obj在当前集合中最后一次出现的位置
        System.out.println(list.lastIndexOf(456)); //8

        //6. Object remove(int index):删除指定index位置的元素,并返回此元素
        Object remove = list.remove(1);
        System.out.println(list); //[123, 2, 3, BB, 456, AA, Thu Apr 07 15:08:39 CST 2022, 456]
        System.out.println(remove); //1
        list.remove(new Integer(456));
        System.out.println(list); //[123, 2, 3, BB, AA, Thu Apr 07 15:10:56 CST 2022, 456]

        //7. Object set(int index, Object ele):设置指定index位置的元素为ele
        list.set(1, "CC");
        System.out.println(list); //[123, CC, 3, BB, AA, Thu Apr 07 15:12:22 CST 2022, 456]

        //8. List subList(int fromIndex, int toIndex):返回从索引fromIndex到toIndex的子集合(左闭右开)
        List list2 = list.subList(0, 3);
        System.out.println(list2); //[123, CC, 3]
    }
}
5.1 遍历List集合元素的方法(以ArrayList为例)
  1. 方式一:使用Iterator迭代器对象遍历集合元素
  2. 方式二:使用foreach循环(增强for)遍历集合元素
  3. 方式三:使用普通for循环,通过调用get(int index)方法
public class ListTest {
    @Test
    public void test2() {
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");

        //方式一:使用Iterator迭代器对象遍历集合元素
        //调用当前List集合类对象的iterator()方法,返回一个实现了Iterator接口的迭代器对象,默认指针在集合的第一个元素之前
        Iterator iterator = list.iterator();
        //使用Iterator迭代器遍历该集合中的元素:
        //hasNext():判断当前指针所处位置是否还有下一个元素
        while (iterator.hasNext()) {
            //next(): ①指针下移一位;②返回下移后的位置对应的集合元素
            System.out.println(iterator.next());
        }

        //方式二:使用foreach循环(增强for)遍历集合元素
        //for (集合元素的类型 局部变量 : 集合对象的名称) {}
        for (Object obj : list) {
            System.out.println(obj);
        }

        //方式三:使用普通for循环,通过调用get(int index)方法
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}
5.2 总结:List接口常用方法(必须掌握)
  1. 增:① add(Object obj);② addAll(Collection coll)
  2. 删:① remove(Object obj);② remove(int index)
  3. 改:① set(int index, Object ele)
  4. 查:① get(int index)
  5. 插:① add(int index, Object ele);② addAll(int index, Collection eles)
  6. 集合中有效元素个数:① size()
  7. 遍历:① 使用Iterator迭代器对象遍历集合元素;② 使用foreach循环(增强for)遍历集合元素;
    ③ 使用普通for循环,通过调用get(int index)方法

五、Collection子接口二:Set接口

1. Set接口概述

  1. Set接口是Collection的子接口, 但set接口没有提供额外的方法,使用的都是Collection接口中声明的方法
  2. Set集合类中 元素无序、不可重复
  3. 对于存放在Set集合中的对象,对应的类一定要重写equals()和hashCode()方法,以保证当两个对象的equals()方法比较返回 true 时,这两个对象的hashCode()方法的返回值也应相等
  4. 常用的Set接口的实现类:① HashSet;② LinkedHashSet;③ TreeSet

2. Set接口实现类之一:HashSet

  1. HashSet 是 Set 接口的主要实现类,大多数时候使用 Set 集合时都使用这个实现类
  2. HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
  3. HashSet底层:数组+链表的结构;底层数组初始容量为16,当使用率超过0.75时,就会扩容为原来的2倍
  4. 线程不安全
  5. 集合元素可以是==null==
2.1 理解元素无序 & 不可重复(以HashSet为例)
  1. 无序性:不等同于随机性。对于HashSet集合而言,其存储的对象在底层数组中并非按照数组的索引顺序添加,而是通过调用对象所在类的hashCode()方法的返回值(hash值),再通过散列函数计算存储的位置
  2. 不可重复性:HashSet判断两个对象相等的标准:
    • ① 当前要添加对象的hashCode()方法返回值与集合中元素的hashCode()方法返回值相同
    • ② 本质上:调用当前要添加对象所在类的equals()方法与集合中元素进行判断时,返回true
2.2 向HashSet中添加元素的过程
  1. 当向HashSet集合中添加(add)一个对象时,首先调用该对象所在类的hashCode()方法,返回==该对象的hash值==
  2. 根据该hash值,通过某种==散列函数==计算当前对象在HashSet底层数组中应当存储的位置**(索引)**
    • 散列函数会根据当前对象的哈希值和底层数组的长度计算得到该对象在数组中的索引
    • 散列函数应当尽可能保证能均匀存储数据,越是散列分布,该散列函数设计的越好
  3. 判断在HashSet底层数组中,该索引位置上是否已存在元素:
    • 若该位置上没有其他元素,则当前对象添加成功 —> 添加成功:情况1
    • 若该位置上已有其他元素(一个对象或以链表形式存储的多个对象),则比较当前对象与该索引位置上所有元素的hash值(hashCode方法的返回值)
      • 若当前对象与该索引位置上所有元素的hash值都不相同,则当前对象添加成功 —> 添加成功:情况2
      • 若当前对象与该索引位置上某元素的hash值相同,则再**调用当前对象所在类的equals()方法**进行比较
        • 若equals()方法返回false,则当前对象添加成功 —> 添加成功:情况3
        • 若equals()方法返回true,则表明当前对象与数组中元素相同,添加失败

  • 总结:hashCode()方法的返回值(hash值)决定了当前对象在HashSet底层数组中应当存储的位置;当该索引位置已有其他元素时,只要hashCode()返回值不同,就一定代表是不同的元素;若hashCode值相同,则最后只能通过当前对象的equals()方法来判断
  • 对于添加成功的情况2和情况3:当前对象与该索引位置上原有的元素以==链表==的方式进行存储:
    • JDK7.0:当前对象(最新添加的对象)作为链表的head,存放到数组中,指向原有的元素
    • JDK8.0:原有的元素存放在数组中,指向当前对象(最新添加的对象)——作为链表的last

在这里插入图片描述

2.3 重写hashCode()和equals()方法的原则
  1. 重写 hashCode() 方法的基本原则:
    • 同一个对象多次调用 hashCode() 方法应该返回相同的值
    • ① 当两个对象调用equals()方法进行比较返回true时,这两个对象的hashCode()方法的返回值也应相等(相等的对象必须具有相等的散列码
    • ② 当两个对象的hashCode()方法的返回值不相等时,这两个对象调用equals()方法进行比较返回false
    • 在重写equals()方法时比较的属性,都应当用来计算hashCode值
  2. 重写 equals() 方法的基本原则:
    • 重写equals()方法的时候一般都需要同时重写hashCode()方法。通常参与计算hashCode的属性也应该参与到equals()方法中进行比较
  3. 为什么用IDEA重写hashCode方法,有31这个数字:
    • 主要作用是放大不同对象hashCode的差异,减少冲突,所以乘了这个系数
    • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,冲突就越少,查找效率也越高
    • 31只占用5bits,相乘造成数据溢出的概率较小
    • i * 31 == (i << 5) - 1
    • 31是一个质数

3. Set接口实现类之二:LinkedHashSet

  1. LinkedHashSet 是 HashSet 的子类
  2. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,可以按照添加的顺序遍历集合元素
  3. 在需要频繁遍历Set元素时,LinkedHashSet的效率比HashSet高
public class SetTest {
    @Test
    public void test1() {
        //HashSet
        Set set = new HashSet();
        set.add(456);
        set.add(123);
        set.add("AA");
        set.add(new Date());
        set.add(129);
        set.add(129);

        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            //HashSet根据元素的hashCode值和散列函数来决定元素的存储位置
            System.out.print(iterator.next() + " "); //AA 129 Thu Apr 07 22:39:42 CST 2022 456 123
        }
        System.out.println();

        //LinkedHashSet
        Set set1 = new LinkedHashSet();
        set1.add(456);
        set1.add(123);
        set1.add("AA");
        set1.add(new Date());
        set1.add(129);
        set1.add(129);

        Iterator iterator1 = set1.iterator();
        while (iterator1.hasNext()) {
            //LinkedHashSet根据元素的hashCode值和散列函数来决定元素的存储位置
            //但它同时使用双向链表维护元素的次序,可以按照添加的顺序遍历集合元素
            System.out.print(iterator1.next() + " "); //456 123 AA Thu Apr 07 22:39:42 CST 2022 129
        }
    }
}

在这里插入图片描述

4. Set接口实现类之三:TreeSet

  1. TreeSet 是 SortedSet 接口的实现类
  2. TreeSet会根据Java比较器中制定的排序规则对其中的元素进行排序,确保集合元素==处于排序状态==
  3. TreeSet中的==元素必须是同一类的对象==,保证可以使用Java比较器进行排序
  4. TreeSet底层使用==红黑树==存储数据
  5. TreeSet 两种排序方法: 自然排序定制排序。默认情况下,TreeSet 采用自然排序

在这里插入图片描述

4.1 TreeSet的自然排序
  1. 如果使用TreeSet的空参构造器创建集合对象,会自动调用集合元素所在类的compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按==升序(默认情况)==排列
  2. 如果试图把一个对象添加到 TreeSet 时,则==该对象所在类必须实现 Comparable 接口,并实现 Comparable 接口的抽象方法compareTo(Object obj)==
  3. Comparable接口的实现类对象通过compareTo(Object obj)方法的返回值来比较大小:
    • 如果当前对象this大于形参对象obj,则返回正整数
    • 如果当前对象this小于形参对象obj,则返回负整数
    • 如果当前对象this等于形参对象obj,则返回零
  4. 自然排序的TreeSet,判断两个对象是否相同的标准:集合元素所在类的 compareTo(Object obj) 方法是否返回 0,而不再是equals()方法
  5. 对于自定义实现类C的对象e1和e2来说,当且仅当 e1.compareTo(e2) == 0e1.equals(e2) 具有相同的boolean值时,类C的自然排序才叫做与 equals() 一致。建议最好使自然排序与 equals() 一致
import org.junit.Test;
import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetTest {
    @Test
    public void test1() {
        TreeSet set = new TreeSet();
        //TreeSet中的元素必须是同一类的对象,保证可以使用Java比较器进行排序
        set.add(34);
        set.add(-34);
        set.add(43);
        set.add(11);
        set.add(8);

        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            //可以按照集合中元素对象的指定属性进行排序,即确保集合元素处于排序状态
            System.out.print(iterator.next() + " "); //-34 8 11 34 43
        }
    }

    @Test
    public void test2() {
        TreeSet set = new TreeSet();
        set.add(new User("Tom", 12));
        set.add(new User("Jerry", 32));
        set.add(new User("Jim", 2));
        set.add(new User("Jack", 33));
        set.add(new User("Mike", 12));

        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            //User类实现了Comparable接口,并实现了其抽象方法compareTo(Object obj)
            //TreeSet会自动调用集合元素所在类的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列
            System.out.println(iterator.next());
        }
    }
}
public class User implements Comparable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }

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

    //先按照name从小到大排列;若name相同,则按照age从小到大排列
    @Override
    public int compareTo(Object o) {
        if (o instanceof User) {
            User user = (User) o;
            int compare = this.name.compareTo(user.name);
            if (compare != 0) {
                return compare;
            } else {
                return Integer.compare(this.age, user.age);
            }
        }
        throw new RuntimeException("输入的数据类型不一致");
    }
}
4.2 TreeSet的定制排序
  1. 当TreeSet集合元素所在类没有实现Comparable接口,或实现了Comparable接口但其重写的compareTo()方法中的排序规则不适合当前操作时,可以使用Comparator接口的实现类对象进行定制排序
  2. 使用方法:将Comparator接口实现类的对象作为参数传递给TreeSet的构造器,从而实现定制排序
  3. 定制排序的TreeSet,判断两个对象是否相同的标准:当前Comparator比较器对象的 compare(Object o1, Object o2) 方法是否返回 0,而不再是equals()方法

面试题:比较HashSet & LinkedHashSet & TreeSet

  1. 三者的共同点:
    • ① 都是Set接口的实现类
    • ② 存储数据的特点相同:单列集合,用来存储一个一个的对象,元素无序、不可重复
  2. 三者的不同点:
    • HashSet:
      • ① Set接口的主要实现类
      • 底层:数组 + 链表;底层数组初始容量为16,当使用率超过0.75,就扩容为原来的2倍
      • ③ 其存储的对象不是按照数组的索引顺序添加的,而是通过调用对象所在类的hashCode()方法,根据返回的hashCode值,再通过散列函数计算存储的位置
      • ④ 判断两个对象是否相同的标准:两个对象调用==equals()方法的返回值是否为true==
      • 线程不安全
      • ⑥ 集合元素可以是==null==
    • LinkedHashSet:
      • ① 是 HashSet 的子类
      • ② 在HashSet的基础上,使用==双向链表==维护元素的次序,可以按照添加的顺序遍历集合元素
      • ③ 在需要频繁遍历Set元素时,LinkedHashSet的效率比HashSet高
    • TreeSet:
      • ① SortedSet 接口的实现类
      • ② 底层使用==红黑树==存储数据
      • ③ TreeSet会根据Java比较器中制定的排序规则对其中的元素进行自然排序或定制排序
      • ④ TreeSet中的==元素必须是同一类的对象==,保证可以使用Java比较器进行排序
      • ⑤ 自然排序:调用TreeSet的空参构造器创建集合对象,要求集合元素所在类实现Comparable接口,实现compareTo()方法;定制排序:将Comparator接口实现类的对象作为参数传递给TreeSet的构造器,在Comparator接口实现类中实现compare()方法
      • ⑥ 判断两个对象是否相同的标准:
        • 自然排序的TreeSet:compareTo() 方法是否返回 0
        • 定制排序的TreeSet:compare()方法是否返回0

5. 例题

  • 例1:判断输出。其中Person类中重写了hashCode()和equal()方法
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);

p1.name = "CC";
//将p1指向的对象作为参数传递给remove方法:对于HashSet,先计算该对象的hashCode值,确定在底层数组中的索引;
//由于p1指向的对象的name属性改变了,而hashCode值的计算与属性相关,故其hashCode()方法的返回值也改变了
//根据改变的hashCode值找到的底层数组索引位置已经不是之前添加p1时的位置,故remove失败
set.remove(p1);
System.out.println(set); //此时HashSet中仍然存在两个元素

//虽然HashSet中已存在属性值为1001,"CC"的元素,但该元素的实际存储位置是按照1001,"AA"计算的,故添加成功
set.add(new Person(1001,"CC"));
System.out.println(set); //此时HashSet中有三个元素

//虽然1001,"AA"的属性值对应的hashCode值对应的存储位置已有元素,但现有的元素的属性值已经修改为1001,"CC"
//即待添加对象和该位置现有元素的hashCOde值不相等,equals也返回false,故添加成功
set.add(new Person(1001,"AA"));
System.out.println(set); //此时HashSet中有四个元素
  • 例2:在List内去除重复数字值,要求尽量简单:将List中的元素放到HashSet中,过滤重复元素
public static List duplicateList(List list) {
	HashSet set = new HashSet(); //set可以用来过滤重复元素
	set.addAll(list);
	return new ArrayList(set);
}

public static void main(String[] args) {
	List list = new ArrayList();
	list.add(new Integer(1));
	list.add(new Integer(2));
	list.add(new Integer(2));
	list.add(new Integer(4));
	list.add(new Integer(4));
    
	List list2 = duplicateList(list);
	
	for (Object integer : list2) {
		System.out.println(integer);
	}
}

六、Map接口

1. Map接口概述

  1. Map接口双列集合,用来存储一对一对的,具有==映射关系“key-value”==的数据
  2. Map 中的key和value可以是任何引用类型变量,但key通常为String类型的
  3. Map接口的实现类:HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
1.1 如何理解Map中的key-value
  • Map中key-value 的关系就像是函数 y = f(x) 中自变量 x 和因变量 y 之间的关系:key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
  1. Map中的key:无序、不可重复的,使用Set存储所有的key
    • 对于HashMap和LinkedHashMap,key所在的类要重写equals()和hashCode()方法
    • 对于TreeMap,key所在的类要实现Comparable接口或需要使用Comparator的实现类对象
  2. Map中的value:无序、可重复的,使用Collection存储所有的Value
    • value所在的类要重写equals()方法
  3. 在Map中,key和value实际上都是Entry类的属性,即一个键值对key-value构成一个Entry对象
  4. Map中的Entry对象:无序、不可重复的,使用Set存储所有的Entry对象

在这里插入图片描述

2. Map接口实现类之一:HashMap

  1. HashMap是Map接口的主要实现类;线程不安全的
  2. key和value可以是==null==
  3. Map中的key:无序、不可重复的,使用HashSet存储所有的key;key所在的类要重写equals()和hashCode()
  4. Map中的value:无序、可重复的,使用Collection存储所有的Value;value所在的类要重写equals()
  5. 一对key-value构成一个Entry对象;Map中的Entry对象:无序、不可重复的,使用Set存储所有的Entry对象
  6. HashMap判断两个key相等的标准:① 两个key的hashCode()返回值相同;② 且通过equals()方法返回true
  7. HashMap判断两个 value 相等的标准:两个value通过 equals()方法返回 true
2.1 HashMap底层实现:JDK7.0
2.1.1 HashMap实例化的过程
  1. 在实例化HashMap对象时,底层创建一个初始长度为16的一维数组==Entry[] table,在这个数组中可以存放元素的位置我们称之为桶 (bucket)==,每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素
  2. 每个bucket中存储一个Entry对象,每一个Entry对象有一个Entry类型属性next,用于指向链表中下一个元素,而且新添加的元素作为链表的head,放在数组中,指向原有的元素
HashMap map = new HashMap(); //本质上:Entry[] table = new Entry[16];

在这里插入图片描述

2.1.2 向HashMap中添加key-value的过程
  1. 当向HashMap中添加(put)一对key-value时,首先调用该key所在类的hashCode()方法,返回==该key的hash值==
  2. 根据该hash值,通过某种==散列函数==计算当前key-value在底层Entry数组中应当存储的位置**(索引)**
    • 散列函数会根据当前key的hash值和底层Entry数组的长度计算得到当前key-value在数组中的索引
    • 散列函数应当尽可能保证能均匀存储数据,越是散列分布,该散列函数设计的越好
  3. 判断在底层Entry数组中,该索引位置上是否已存在Entry对象(key-value对):
    • 若该位置上没有其他Entry对象,则当前key-value以Entry对象的形式添加成功 —> 添加成功:情况1
    • 若该位置上已有其他Entry对象(一个或以链表形式存储的多个Entry对象),则比较当前key与该索引位置上所有Entry对象的key的hash值(hashCode方法的返回值)
      • 若当前key与该索引位置上所有Entry对象的key的hash值都不相同,则当前key-value以Entry对象的形式添加成功 —> 添加成功:情况2
      • 若当前key与该索引位置上某Entry对象的key的hash值相同,则再调用当前key所在类的equals()方法
        • 若equals()返回false,则当前key-value以Entry对象的形式添加成功 —> 添加成功:情况3
        • 若equals()返回true,表明是两个相同的key,则使用当前value替换原有Entry对象的value属性
/*
1. 调用key1所在类的hashCode()方法,返回key1的hash值;
2. 根据该hash值hash值,通过某种散列函数计算key1-value1在底层Entry数组中应当存储的位置(索引)
3. 判断在底层Entry数组中,该索引位置上是否已存在Entry对象(key-value对)
   - 若存在其他Entry对象的情况下,则先比较两个key的hashCode值是否相等;若相等,调用key1的equals()比较
   - 若equals()返回false,则添加成功;若返回true,则表明两个key是相同的,使用value1替换原有的value属性
 */
map.put(key1, value1);

  • 总结:当前要添加的key的hashCode()值决定了当前Entry对象(包含当前key-value)在底层Entry数组中存储的位置;而==equals()方法才最终决定了当前key与该索引位置原有Entry对象的key是否相同==
  • 对于添加成功的情况2和情况3:当前Entry对象(包含当前key-value)与该索引上原有Entry对象以==链表==方式存储
    • JDK7.0:当前Entry对象(最新添加的对象)作为链表的head,存放到数组中,指向原有的元素
    • JDK8.0:原有的元素存放在数组中,指向当前Entry对象(最新添加的对象)——作为链表的last
2.1.3 HashMap底层Entry数组的扩容0
  1. 当元素个数超过 当前容量 * 负载因子(threshold值,也叫做临界值),且下一个要添加的索引位置已存在元素,就会进行数组扩容。loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75;底层数组容量(DEFAULT_INITIAL_CAPACITY)默认为16,当HashMap中元素个数超过 16*0.75=12,就会进行首次扩容
  2. 默认扩容方式:新建一个长度为原数组2倍的新数组(扩容为原来的2倍),然后根据Entry对象的key和当前Entry数组的长度重新计算每个元素存放的位置
2.2 HashMap底层实现:JDK8.0的变化
  1. JDK8.0的HashMap,底层的数组是Node[],而不再是Entry[]
  2. JDK8.0在实例化HashMap对象时,并没有先创建底层数组,而只是初始化了initialCapacity和loadFactor;当首次调用put()方法添加第一对key-value时,再创建一个初始长度为initialCapacity=16的Node[]数组
  3. JDK7.0:HashMap底层是==数组+链表结构(即链地址法);JDK8.0:HashMap底层是数组+链表+红黑树==
    • 当底层数组某一索引位置上以链表形式存储的对象个数 > 8 但当前数组长度 < 64 时,HashMap会先扩容(扩容后会根据Entry对象的key和当前数组的长度重新计算每个元素存放的位置,可能会减轻链表的负担)
    • 当底层数组某一索引位置上以链表形式存储的对象个数 > 8 且当前数组长度 >= 64 时,该索引位置上所有数据改用红黑树存储——提高查找效率
//在实例化HashMap对象时,并没有先创建底层数组,而只是初始化了initialCapacity和loadFactor
HashMap map = new HashMap();

//当添加第一个元素时,再创建一个初始长度为16的Node[],并将数据添加到新建的数组中
//本质上:Node[] table = new Node[16];
map.put(key1, value1);

在这里插入图片描述

2.3 HashMap源码中的重要常量 & 变量
  1. **DEFAULT_INITIAL_CAPACITY:**HashMap的默认容量(底层数组的默认长度):16
  2. DEFAULT_LOAD_FACTOR: HashMap的默认负载因子(填充因子):0.75
  3. TREEIFY_THRESHOLD: Bucket(底层数组的每个索引位置)中链表长度大于该默认值,转化为红黑树:8
  4. **MIN_TREEIFY_CAPACITY:**Bucket中的Node对象被树化时最小的HashMap容量(底层数组长度):64
  5. threshold: 扩容的临界值 = 当前HashMap容量 * 负载因子(填充因子)
2.4 面试题:负载因子对HashMap有什么影响
  1. 负载因子的大小决定了HashMap的数据密度
  2. 负载因子越大,密度越大,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降
  3. 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生==哈希碰撞==的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高,但是会浪费一定的容量空间。而且经常扩容也比较影响性能,建议初始化预设大一点的容量
    • 哈希碰撞:不同的key,计算出了相同的哈希值(通过散列函数计算在底层数组存储的索引位置相同)
  4. HashMap中默认的负载因子(填充因子)为0.75

3. Map接口实现类之二:LinkedHashMap

  1. LinkedHashMap 是 HashMap 的子类
  2. 在HashMap的基础上,使用==双向链表==来记录添加key-value对的顺序,可以按照添加的顺序遍历
  3. 内部类Entry中声明的属性==beforeafter==专门用来指向当前Entry对象的前一个和后一个Entry对象
  4. 在需要频繁遍历Map中的key-value对时,LinkedHashMap的效率比HashMap高

在这里插入图片描述

public class MapTest {
    @Test
    public void test2() {
        Map map1 = new HashMap();
        map1.put(123, "AA");
        map1.put(345, "BB");
        map1.put(12, "CC");
        System.out.println(map1); //{345=BB, 123=AA, 12=CC}

        Map map2 = new LinkedHashMap();
        map2.put(123, "AA");
        map2.put(345, "BB");
        map2.put(12, "CC");
        System.out.println(map2); //{123=AA, 345=BB, 12=CC}
    }
}

4. Map接口实现类之三:TreeMap

  1. TreeMap 是 SortedMap 接口的实现类
  2. TreeMap会根据Java比较器中制定的排序规则==对所有key进行排序,确保Map中的key-value对处于排序状态==
  3. TreeMap中的==key必须是同一类的对象==,保证可以使用Java比较器进行排序
  4. TreeMap底层使用==红黑树==存储数据
  5. TreeMap 两种 key 排序方法: 自然排序定制排序。默认情况下,TreeMap 采用自然排序
  6. TreeMap==只能按照key排序,不能按照value排序==,因为key-value是单向一对一的关系,指定的 key 能找到唯一的、确定的 value
4.1 TreeMap的自然排序
  1. 如果使用TreeMap的空参构造器实例化对象时,会自动调用key所在类的compareTo(Object obj) 方法来比较所有key的大小关系,然后将key-value对按==key升序(默认情况)==排列
  2. TreeMap中的key必须是同一类的对象,且key所在类实现Comparable 接口,重写compareTo(Object obj)方法
  3. Comparable接口的实现类对象通过compareTo(Object obj)方法的返回值来比较大小:
    • 如果当前对象this大于形参对象obj,则返回正整数
    • 如果当前对象this小于形参对象obj,则返回负整数
    • 如果当前对象this等于形参对象obj,则返回零
  4. 自然排序的TreeMap,判断两个key是否相同的标准:key所在类实现的 compareTo(Object obj) 方法是否返回 0,而不再是equals()方法
//其中key所在的User类实现了Comparable接口,并实现了compareTo(Object obj)方法
public class TreeMapTest {
    //自然排序
    @Test
    public void test1() {
        TreeMap map = new TreeMap();
        map.put(new User("Tom", 23), 98);
        map.put(new User("Jerry", 32), 89);
        map.put(new User("Jack", 20), 76);
        map.put(new User("Rose", 18), 100);

        //遍历
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
4.2 TreeMap的定制排序
  1. 当TreeMap中的key所在类没有实现Comparable接口,或实现了Comparable接口但其重写的compareTo()方法中的排序规则不适合当前操作时,可以使用Comparator接口的实现类对象进行定制排序
  2. 使用方法:声明一个Comparator接口的实现类,并实现compare(Object o1, Object o2)方法,将该实现类的对象作为参数传递给TreeMap的构造器,从而实现定制排序
  3. 定制排序的TreeMap,判断两个key是否相同的标准:当前Comparator比较器对象的 compare(Object o1, Object o2) 方法是否返回 0,而不再是equals()方法
public class TreeMapTest {
	//定制排序
    @Test
    public void test2() {
        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //对key进行排序,因此检查是否是key的类型
                if (o1 instanceof User && o2 instanceof User) {
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    return Integer.compare(u1.getAge(), u2.getAge());
                }
                throw new RuntimeException("输入数据类型不一致");
            }
        });
        map.put(new User("Tom", 23), 98);
        map.put(new User("Jerry", 32), 89);
        map.put(new User("Jack", 20), 76);
        map.put(new User("Rose", 18), 100);

        //遍历
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

5. Map接口实现类之四:Hashtable

  1. Map接口的古老实现类,实现原理与HashMap相同,功能也相同,基本不使用了
  2. 线程安全的(Hashtable中的方法大多为同步方法),执行效率低
  3. key和value不可以是null

6. Map接口实现类之五:Properties

  1. Properties 是 Hashtable 的子类,其对象用于处理配置文件
  2. 由于属性文件里的 key、 value 都是String类型,所以 Properties 里的 key和 value 都是String类型
  3. 存取数据时,建议使用==**setProperty(String key,String value)方法和getProperty(String key)**方法
public class PrioertiesTest {
    public static void main(String[] args) throws IOException {
        Properties pros = new Properties();

        FileInputStream fis = new FileInputStream("jdbc.properties");
        pros.load(fis); //加载流对应的文件

        //getProperty(String key)
        String name = pros.getProperty("name");
        String password = pros.getProperty("password");
        System.out.println("name = " + name + ", password = " + password);
    }
}

面试题:比较HashMap & LinkedHashMap & TreeMap & Hashtable & Properties

  1. 三者的共同点:
    • ① 都是Map接口的实现类
    • ② 存储数据的特点相同:双列集合,用来存储一对一对的,具有**映射关系“key-value”**的数据
  2. 三者的不同点:
    • HashMap:
      • ① Map接口的主要实现类;线程不安全的
      • ② key和value可以是null
      • ③ key:无序、不可重复的;value:无序、可重复的;key-value实际上是HashMap的内部类Entry的属性,实际用Set来存储无序、不可重复的Entry对象
      • ④ JDK7.0底层是数组+链表;JDK8.0底层是数组+链表+红黑树
      • ⑤ 底层数组存储的Entry对象不是按照数组的索引顺序添加的,而是通过调用key所在类的hashCode()方法,根据返回的hash值,再通过散列函数计算存储的位置
      • equals()方法才最终决定了当前key与该索引位置原有Entry对象的key是否相同
      • 当底层数组某一索引位置上以链表形式存储的对象个数 > 8 且当前数组长度 >= 64 时,该索引位置上所有数据改用红黑树存储
    • LinkedHashMap:
      • ① 是 HashMap 的子类
      • ② 在HashMap的基础上,使用双向链表来记录添加key-value对的顺序,可以按照添加的顺序遍历
      • ③ 在需要频繁遍历Map中的key-value对时,LinkedHashMap的效率比HashMap高
    • TreeMap:
      • ① SortedMap 接口的实现类
      • ② 底层使用红黑树存储数据
      • ③ TreeMap根据Java比较器制定的规则对对所有key进行排序==,确保所有key-value对处于排序状态
      • ④ TreeMap中的key必须是同一类的对象,保证可以使用Java比较器进行排序
      • ⑤ 自然排序:调用TreeMap的空参构造器创建Map对象,要求key所在类实现Comparable接口,实现compareTo()方法;定制排序:将Comparator接口实现类的对象作为参数传递给TreeMap的构造器,在Comparator接口实现类中实现compare()方法
      • ⑥ 判断两个对象是否相同的标准:
        • 自然排序的TreeMap:compareTo() 方法是否返回 0
        • 定制排序的TreeMap:compare()方法是否返回0
    • Hashtable:
      • ① Map接口的古老实现类,实现原理与HashMap相同,功能也相同,基本不使用了
      • ② 线程安全的(Hashtable中的方法大多为同步方法),执行效率低
      • ③ key和value不可以是null
    • Properties:
      • ① 是 Hashtable 的子类
      • ② key和value都是String类型
      • ③ 常用来处理配置文件

7. Map接口常用方法

7.1 添加、 删除、修改
  1. Object put(Object key,Object value):将指定key-value添加到(或修改)当前Map对象中
  2. **void putAll(Map m):**将另一个Map中的所有key-value对存放到当前Map中
  3. **Object remove(Object key):**移除指定key的key-value对,并返回value
  4. **void clear():**清空当前map中的所有数据
@Test
public void test3() {
    Map map = new HashMap();
    //1. Object put(Object key,Object value):将指定key-value添加到(或修改)当前Map对象中
    //添加
    map.put("AA", 123);
    map.put(45, 123);
    map.put("BB", 56);
    //当key相同时,修改(替换)value
    map.put("AA", 87);
    //实际调用了AbstractMap类重写的toString方法
    System.out.println(map); //{AA=87, BB=56, 45=123}

    //2. void putAll(Map m):将另一个Map中的所有key-value对存放到当前Map中
    Map map1 = new HashMap();
    map1.put("CC", 123);
    map1.put("BB", 123);
    map.putAll(map1);
    System.out.println(map); //{AA=87, BB=123, CC=123, 45=123}

    //3. Object remove(Object key):移除指定key的key-value对,并返回value
    Object value = map.remove("CC");
    System.out.println(value); //123
    System.out.println(map); //{AA=87, BB=123, 45=123}

    //4. void clear():清空当前map中的所有数据
    map.clear();
    System.out.println(map); //{}
}
7.2 元素查询
  1. **Object get(Object key):**获取指定key对应的value
  2. **boolean containsKey(Object key):**是否包含指定的key
  3. **boolean containsValue(Object value):**是否包含指定的value
  4. **int size():**返回map中key-value对的个数
  5. **boolean isEmpty():**判断当前map是否为空,即size()返回值是否为0
  6. **boolean equals(Object obj):**判断当前map和参数对象obj是否相等,形参对象obj应当为对应的Map对象
@Test
public void test4() {
    Map map = new HashMap();
    map.put("AA", 123);
    map.put(45, 123);
    map.put("BB", 56);

    //5. Object get(Object key):获取指定key对应的value
    System.out.println(map.get(45)); //123
    //因为根据key计算hash值得到的数组索引位置还没有存储Node对象,故返回null
    System.out.println(map.get(455)); //null

    //6. boolean containsKey(Object key):是否包含指定的key
    boolean bb = map.containsKey("BB");
    System.out.println(bb); //true

    //7. boolean containsValue(Object value):是否包含指定的value
    boolean value = map.containsValue(123);
    System.out.println(value); //true

    //8. int size():返回map中key-value对的个数
    System.out.println(map.size()); //3

    //9. boolean isEmpty():判断当前map是否为空,即size()返回值是否为0
    System.out.println(map.isEmpty()); //false

    //10. boolean equals(Object obj):判断当前map和参数对象obj是否相等
    Map map1 = new HashMap();
    map1.put("AA", 123);
    map1.put(45, 123);
    map1.put("BB", 56);
    System.out.println(map.equals(map1)); //true
}
7.3 元视图操作:遍历Map中的key/value/key-value
  • 当遍历Collection集合中的元素时,可以直接调用iterator()方法,返回一个Iterator迭代器对象遍历集合元素;或使用增强for进行遍历
  • 但是Map接口并没有定义iterator()方法,无法直接使用Iterator迭代器对象进行遍历;也不能直接使用增强for
  • Map中的key使用Set存储,vaule使用Collection存储,Entry对象(key-value对)使用Set存储;可以使用以下方法返回所有key、value、Entry对象构成的集合;再调用iterator()方法或使用增强for进行遍历:
  1. **Set keySet():**返回所有key构成的Set集合
  2. **Collection values():**返回所有value构成的Collection集合
  3. **Set entrySet():**返回所有Entry对象(key-value对)构成的Set集合
@Test
public void test5() {
    Map map = new HashMap();
    map.put("AA", 123);
    map.put(45, 123);
    map.put("BB", 56);

    //11. Set keySet():返回所有key构成的Set集合
    Set set = map.keySet();
    //再通过该Set集合调用iterator()方法,返回Iterator迭代器对象进行遍历
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
        System.out.print(iterator.next() + " "); //AA BB 45
    }
    System.out.println();
    //12. Collection values():返回所有value构成的Collection集合
    Collection values = map.values();
    //使用增强for遍历Collection集合元素
    for (Object obj : values) {
        System.out.print(obj + " "); //123 56 123
    }
    System.out.println();

    //13. Set entrySet():返回所有Entry对象(key-value对)构成的Set集合
    Set set1 = map.entrySet();
    Iterator iterator1 = set1.iterator();
    while (iterator1.hasNext()) {
        //实际遍历的是Entry类的对象
        Object obj = iterator1.next();
        Map.Entry entry = (Map.Entry) obj; //强转
        System.out.print(entry.getKey()  + "->" + entry.getValue() + " "); //AA->123 BB->56 45->123
    }
7.4 总结:Map接口常用方法(必须掌握)
  1. 增:① put(Object key,Object value);② putAll(Map m)
  2. 删:① remove(Object key);② clear()
  3. 改:① put(Object key,Object value)
  4. 查:① get(Object key)
  5. Map中有效元素个数:① size()
  6. 遍历:先使用 ① keySet();② values();③ entrySet() 返回所有key、value或Entry对象(key-value对)构成的集合,再通过这些Collection集合 ① 使用Iterator迭代器对象遍历集合元素;② 使用增强for遍历集合元素;

七、Collections工具类的使用

1. Collections工具类概述

  1. Collections是一个操作==Collection的子接口(List、Set)的实现类Map接口的实现类==的工具类
    • Arrays是一个操作数组的工具类
  2. Collections 中提供了一系列==静态方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制==等方法

2. Collections工具类常用方法

2.1 排序操作(只适用于List集合)
  • 只适用于List集合,均为static方法
  1. **reverse(List list):**反转 List 中元素排列顺序(在底层数组中存储的顺序)
  2. **shuffle(List list):**对 List 中元素进行随机排序
  3. **sort(List list):**自然排序:根据元素的自然顺序对指定 List 集合元素按升序排序
  4. **sort(List list, Comparator c):**定制排序:根据指定的Comparator比较器对 List 集合元素排序
  5. **swap(List list, int i, int j):将指定 List 集合索引位置为 i 和 j 的两个元素进行交换
public class CollectionsTest {
    @Test
    public void test1() {
        List list = new ArrayList();
        list.add(123);
        list.add(43);
        list.add(765);
        list.add(-97);
        list.add(0);

        System.out.println(list); //[123, 43, 765, -97, 0]

        //1. reverse(List):反转 List 中元素排列顺序(在底层数组中存储的顺序)
        Collections.reverse(list);
        System.out.println(list); //[0, -97, 765, 43, 123]

        //2. shuffle(List list): 对 List 中元素进行随机排序
        Collections.shuffle(list);
        System.out.println(list); //[765, 43, 123, 0, -97]
        Collections.shuffle(list);
        System.out.println(list); //[43, -97, 123, 765, 0]

        //3. sort(List list):自然排序:根据元素的自然顺序对指定 List 集合元素按升序排序
        Collections.sort(list);
        //调用的是Integer包装类实现的compareTo方法
        System.out.println(list); //[-97, 0, 43, 123, 765]

        //4. sort(List list, Comparator c):定制排序:根据指定的Comparator比较器对List集合元素排序
        //比如从大到小排列
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Integer && o2 instanceof Integer) {
                    Integer i1 = (Integer) o1;
                    Integer i2 = (Integer) o2;
                    return -Integer.compare(i1, i2);
                }
                throw new RuntimeException("输入的数据类型不符");
            }
        });
        System.out.println(list); //[765, 123, 43, 0, -97]

        //5. swap(List list, int i, int j):将指定 List 集合索引位置为 i 和 j 的两个元素进行交换
        Collections.swap(list, 0, 4); //[765, 123, 43, 0, -97]
    }
}
2.2 查找 & 替换操作
  • 均为static方法
  1. **Object max(Collection coll):**根据元素的自然顺序,返回给定集合中的最大元素
  2. **Object max(Collection coll, Comparator comp):**根据Comparator比较器对象定制的顺序,返回给定集合中的最大元素
  3. Object min(Collection coll)
  4. Object min(Collection coll, Comparator comp)
  5. **int frequency(Collection coll, Object obj):**返回指定集合中指定元素的出现次数
  6. **void copy(List dest, List src):**将src中的元素复制到dest中
    • src和dest均为List集合
    • dest.size() >= src.size(),即要求dest中有效元素的个数不能小于src中有效元素的个数,否则报异常
    • 解决:List dest = Arrays.asList(new Object[src.size()]); 此时返回的List集合dest中有效元素均为null
  7. **boolean replaceAll(List list, Object oldVal, Object newVal):**使用新值替换指定 List 集合的所有指定旧值
public class CollectionsTest {
    @Test
    public void test2() {
        List list = new ArrayList();
        list.add(123);
        list.add(43);
        list.add(765);
        list.add(-97);
        list.add(0);

        //5. int frequency(Collection coll, Object obj):返回指定集合中指定元素的出现次数
        System.out.println(Collections.frequency(list, 765)); //1

        //6. void copy(List dest, List src):将src中的内容复制到dest中
        //- src和dest均为List集合
        //- dest.size() >= src.size(),即要求**dest中有效元素的个数不能小于src中有效元素的个数,否则报异常
        //- 解决:List dest = Arrays.asList(new Object[src.size()]); 返回的List集合dest中有效元素均为null
        List dest = Arrays.asList(new Object[list.size()]);
        System.out.println(dest.size()); //5
        System.out.println(dest); //[null, null, null, null, null]

        Collections.copy(dest, list);
        System.out.println(dest); //[123, 43, 765, -97, 0]
    }
}
2.3 同步控制
  • Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使==将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题==
  • 主要用于线程不安全的==ArrayListHashMap==:synchronizedList()和synchronizedMap()

在这里插入图片描述

public class CollectionsTest {
	@Test
    public void test3() {
        List list = new ArrayList();
        list.add(123);
        list.add(43);
        list.add(765);
        list.add(-97);
        list.add(0);
        //返回的list1就是线程安全的List集合
        List list1 = Collections.synchronizedList(list);
    }
}

3. 面试题:Collection & Collections 的区别

  1. Collection接口:
    • 存储单列数据的集合接口,用来存储一个一个的对象
    • 但JDK不提供Collection接口的直接实现类,而是提供更具体的子接口,如:List和Set接口
  2. Collections工具类:是一个操作Collection的子接口(List、Set)的实现类和Map接口的实现类的工具类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值