集合总结


一.Java集合框架概述

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt, .jpg, .avi,数据库中)。Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。

1.数组在内存存储方面的特点:

  • 数组初始化以后,长度就确定了
  • 数组声明的类型,就决定了进行元素初始化时的类型

2.数组在存储数据方面的弊端:

  • 数组初始化以后,长度就不可变了,不便于扩展
  • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取数组中实际元素的个数
  • 数组存储的数据是有序的、可以重复的。对于无序、不可重复的需求,不能满足。

3.Java集合分为Collection和Map两种关系:

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

二.Collection接口

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法。既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

1.常用的API

在这里插入图片描述

2.抽象方法

删除、包含相关方法底层都是调用元素类型对象重写的equals方法(比较内容)来判断集合中是否有要删除或包含的元素。向集合中添加obj数据时,要求obj所在类要重写equals()。

  • 增:
    • boolean add(Object o)添加元素到集合
    • boolean addAll(Collection c):将指定集合中的所有元素添加到此集合
  • 删:
    • boolean remove(Object o)删除找到的第一个元素(equals)
    • boolean removeAll(Collection c):从当前集合中删除公共元素(equals)(差集)
    • boolean retainAll(Collection c):从当前集合中删除非公共元素(equals)(交集)
    • void clear():删除所有元素
  • 查:
    • iterator()返回迭代器对象,用于遍历
    • for(集合元素的类型 局部变量 : 集合对象)增强for循环,用于遍历
    • int size()查询有效元素个数
    • hashCode()查询当前对象的哈希值
  • 判断:
    • boolean contains(Object o)是否包含某个元素(equals)
    • boolean containsAll(Collection c):是否包含某个集合的所有元素(equals)
    • boolean isEmpty()判断集合size==0
    • boolean equals(Object o)集合是否相等(比较集合是否相等:元素及顺序)
  • 转换:
    • Object[] toArray()集合 –> 数组
    • T[] toArray(T[] a) : 集合 –> 数组
    • (Arrays)public static List<T> asList(T... a)数组 –> 集合

3.注意

获取长度的区分:

  • java 中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
  • java 中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到 length()这个方法.
  • java 中的size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!

向Collection接口的实现类的对象中添加obj数据时,要求obj所在类要重写equals():

  • 在判断时会调用obj对象所在类的equals()方法:equals方法默认比较地址,需要重写来比较内容。String、File、Date、包装类默认重写了equals方法
  • equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同(包括元素的顺序)
  • 调用Arrays类的静态方法asList():基本数据类型数组会被当做一个对象,需要使用包装类对象数组当形参
Collection coll = new ArrayList();
coll.add(123);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);

// 在判断时会调用obj对象所在类的equals()方法:equals方法默认比较地址,需要重写来比较内容
// 1.contains(Object obj):判断当前集合中是否包含obj
boolean contains = coll.contains(123);
System.out.println(contains);    // true
// String、File、Date、包装类重写了equals方法
System.out.println(coll.contains(new String("Tom")));    // true
// Person没有重写equals方法时:false;重写了equals方法:true
System.out.println(coll.contains(new Person("Jerry",20)));

// coll2集合中:元素内容与coll集合相同,但顺序不同
Collection coll2 = new ArrayList();
coll2.add(new Person("Jerry",20));
coll2.add(new String("Tom"));
coll2.add(123);
coll2.add(false);
// 2.equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同(包括元素的顺序)。
System.out.println(coll.equals(coll1));  // false

// 3.集合 --> 数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
    System.out.println(arr[i]);
}
// 若集合中都是相同类型的元素,若都为Integer类型
// 第一种方式(最常用)
Integer[] integer = arrayList.toArray(new Integer[0]);
// 第二种方式(容易理解)
Integer[] integer1 = new Integer[arrayList.size()];
arrayList.toArray(integer1);
// 抛出异常,java不支持向下转型,讲Object数组转为Integer数组
//Integer[] integer2 = new Integer[arrayList.size()];
//integer2 = arrayList.toArray();
System.out.println();

// 4.数组 --> 集合:调用Arrays类的静态方法asList()
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);    // [AA, BB, CC]
// 当是int[]时,会当成一个int数组对象
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());    // 1
// 包装类数组时,会当成多个元素
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());    // 2

Person类:重写equals()方法

// Person对象重写equals方法
public class Person {
    String name;
    int age;

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }
}

三.集合遍历

1.Iterator接口

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

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

  • 内部的方法:hasNext()判断是否还有下一个元素next()指针下移并将指向的元素返回
  • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
  • 内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()

示例:

Collection coll = new ArrayList();
coll.add(123);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));

Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
    //next():1.指针下移 2.将下移以后集合位置上的元素返回
    System.out.print(iterator.next()+" ");    //123 Person{name='Jerry', age=20} Tom
}

//错误方式:
//集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
while (coll.iterator().hasNext()){    // 死循环
    System.out.print(coll.iterator().next());    // 123 123 123 ...
}

//遍历过程中删除集合中"Tom"元素
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
    //iterator.remove();    //指针没有下移就remove()会报IllegalStateException
    Object obj = iterator.next();
    if("Tom".equals(obj)){
        iterator.remove();
    }
}
//删除"Tom"之后再遍历集合
iterator = coll.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());    //123 Person{name='Jerry', age=20}
}

2.增强for循环

遍历集合:for(集合元素的类型 局部变量 : 集合对象)

Collection coll = new ArrayList();
coll.add(123);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);

//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
for(Object obj : coll){
    System.out.println(obj);
}

遍历数组:for(数组元素的类型 局部变量 : 数组对象)

int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
    System.out.println(i);
}

注意:数组赋值时不能使用增强for循环

String[] arr = new String[]{"MM","MM","MM"};

//    //方式一:普通for赋值
//    for(int i = 0;i < arr.length;i++){
//        arr[i] = "GG";
//    }

//方式二:增强for循环
for(String s : arr){
    s = "GG";    // s是局部变量,存储在栈中,并不是原数组的引用
}

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

四.List接口

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

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

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

插:

  • void add(int index, Object ele) : 在 index 位置插入ele 元素
  • boolean addAll(int index, Collection eles) : 从index 位置开始将eles 中的所有元素添加进来

删:

  • Object remove(int index) : 移除指定index 位置的元素,并返回此元素

改:

  • Object set(int index, Object ele) : 设置指定index 位置的元素为ele

查:

  • Object get(int index) : 获取指定index 位置的元素
  • int indexOf(Object obj): 返回obj 在集合中首次出现的位置,如果不存在,返回-1.
  • int lastIndexOf(Object obj) : 返回obj 在当前集合中末次出现的位置,如果不存在,返回-1.
  • List subList(int fromIndex, int toIndex) : 返回从fromIndex 到toIndex位置的左闭右开区间的子集合

1.ArrayList

ArrayList 是 List 接口的主要实现类,本质上,ArrayList是对象引用的一个”变长”数组,容量能动态增长.

ArrayList 的JDK1.8 之前与之后的实现区别?

  • JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
  • JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个初始容量为10的数组

区分List中remove(int index)和remove(Object obj) ?

List list = new ArrayList();
list.add(1);    // 自动装箱
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);    // [1, 3]
// 判断list是否包含3,contains没有索引的形参
System.out.println(list.contains(3));    // true

private void updateList(List list) {
  // list.remove(2);    // 2代表索引,先根据索引
  list.remove(new Integer(2));    // 2代表对象
}

更多内容详看 : 源码学习 – > ArrayList

2.LinkedList

双向链表内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。定义内部类Node,作为LinkedList中保存数据的基本结构。

Node除了保存数据,还定义了两个变量:prev变量记录前一个元素的位置; next变量记录下一个元素的位置.

在这里插入图片描述

    // 双向链表
    private static class Node<E> {    // 内部类:只有当前类需要使用,外面类不需要使用时
        E item;        // 泛型,obj对象数据
        Node<E> next;    // 下一个元素节点地址:指向下一个元素的指针
        Node<E> prev;    // 上一个元素节点地址:指向上一个元素的指针

        // 元素节点分为三部分:上节点地址,obj对象数据,下节点地址
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

对于 频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高.

新增方法:

  • void addFirst(Object obj)
  • void addLast(Object obj)
  • Object getFirst()
  • Object getLast()
  • Object removeFirst()
  • Object removeLast()

更多内容详看 : 源码学习 – > LinkedList

3.Vector

Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的.

在各种list中,最好把ArrayList作为默认选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。

更多内容详看 : 源码学习 –> ArrayList –> Vector

4.三者联系与区别

联系:

  • 都实现了List接口, 存储有序的、可重复的数据
  • add自定义类时,该类需要重写equals方法(contains,remove等方法以equals为标准判断的)

区别:

  • ArrayList: 底层使用动态数组结构; 线程不安全,效率高; 默认创建空列表,添加第一个元素时,分配10个大小的空间,每次扩容为原来的1.5倍;对于随机访问get和set操作效率高于LinkedList
  • LinkedList: 底层使用双向链表结构; 线程不安全; 对于频繁的插入, 删除操作效率比ArrayList高
  • Vector: 底层使用动态数组结构; 线程安全,效率低; 默认创建10个大小的数组,每次扩容为原来的2倍

五.Set接口

Set接口是Collection的子接口,set接口没有提供额外的方法,使用的都是Collection中声明过的方法。存储无序的、不可重复的数据,类似于高中数学中的”集合”。

以HashSet为例:

  • 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
  • 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个

向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals(),重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。 Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法

HashSet和TreeSet是Set接口的实现类,LinkedHashSet是HashSet的子类。

  • HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
  • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历;对于频繁的遍历操作,LinkedHashSet效率高于HashSet
  • TreeSet:可以按照添加对象的指定属性,进行排序

1.HashSet

1.1 概述

HashSet 是 Set接口的主要实现,HashSet 按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。底层是 数组+链表结构。数组初始容量为16,当如果使用率超过0.75,(16 * 0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128….等)。

特点:不能保证元素的排列顺序、不是线程安全的、集合元素可以是null。

判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。

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

1.2 添加元素过程

在这里插入图片描述

向HashSet中添加元素的过程(重要):

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

  • 如果此位置上没有其他元素,则元素a添加成功;
  • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值
    • 如果hash值不相同,则元素a添加成功;
    • 如果hash值相同,进而需要调用元素a所在类的equals()方法
      • equals()返回true,元素a添加失败;
      • equals()返回false,则元素a添加成功。

元素a 与已经存在指定索引位置上数据以链表的方式存储。可以用”七上八下”来形容。

  • JDK7:元素a放到数组中,指向原来的元素
  • JDK8:原来的元素在数组中,指向元素a

如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。

示例:

//其中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);
// [Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]
System.out.println(set);

p1.name = "CC";
set.remove(p1);    // 由于p1的name变化导致hash值变化了,去查找对应的存储位置是null的
// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
System.out.println(set);    

set.add(new Person(1001,"CC"));    // p1原先的hash值是由"AA"算出来的
// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, 
// Person{id=1001, name='CC'}]
System.out.println(set);

set.add(new Person(1001,"AA")); // hash值与p1相同,但equals时是false的
// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, 
// Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
System.out.println(set);

图示:

在这里插入图片描述

1.3 重写方法原则

一般用idea自动生成的重写方法hashCode和equals方法就可以了,复写的hashCode方法有31这个数字。

重写hashCode方法:

  • 同一个对象多次调用 hashCode() 方法应该返回相同的值
  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()方法的返回值也应相等
  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值

重写equals方法:

  • 当改写equals方法时,总要改写hashCode方法;
  • 相等的对象必须具有相等的散列码
  • 参与计算hashCode 的对象的属性也应该参与到equals() 中进行计算

1.4 示例

Set set = new HashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);

// 若不重写hashCode方法,则用父类Object的hashCode方法,hash值是随机生成的
// 先判断hash值,hash值不同,两个user对象都会添加到集合中
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    // AA    CC    129    456    123    User{name='Tom', age=12}
    System.out.print(iterator.next()+"\t");
}

Person类:

public class User implements Comparable{
    private String name;
    private int age;

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

    @Override
    public boolean equals(Object o) {
        System.out.println("User equals()....");
        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() { //return name.hashCode() + age;
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
            int compare = -this.name.compareTo(user.name);
            if(compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不匹配");
        }

    }
}

使用HashSet去除List重复数字值:

public List duplicateList(List list) {
    HashSet set = new HashSet();
    set.addAll(list);
    return new ArrayList(set);
}

2.LinkedHashSet

LinkedHashSet 是 HashSet 的子类。根据元素的 hashCode 值来决定元素的存储位置,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,效率高于HashSet

底层结构:

在这里插入图片描述

示例:

Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);

// 打印输出看似有序,是由于每个数据还维护了两个引用
// 存储位置是由hash值决定的
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    //456    123    AA    CC    User{name='Tom', age=12}    129    
    System.out.print(iterator.next()+"\t");
}

3.TreeSet

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。TreeSet底层使用 红黑树结构存储数据。有序,查询速度比List快。向TreeSet中添加的数据,要求是相同类的对象

红黑树介绍:平衡查找树之红黑树

结构:

在这里插入图片描述

TreeSet 两种排序方法: 自然排序和 定制排序。默认情况下,TreeSet 采用自然排序。

  • 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
  • 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

3.1 自然排序

TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。

如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable接口。实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。

示例:

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("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));

Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//        User{name='Tom', age=12}
//        User{name='Mike', age=65}
//        User{name='Jim', age=2}
//        User{name='Jerry', age=32}
//        User{name='Jack', age=33}
//        User{name='Jack', age=56}

// User类
public class User implements Comparable {
    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(User u) {
        int compare = -this.name.compareTo(u.name);
        if(compare != 0){
            return compare;
        }else {
            return Integer.compare(this.age,u.age);
        }
    }
}

3.2 定制排序

定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。将实现Comparator接口的实例作为形参传递给TreeSet的构造器。向TreeSet中只能添加类型相同的对象。否则发生ClassCastException异常。

示例:

Comparator com = new Comparator() {
    //按照年龄从小到大排列
    @Override
    public int compare(User u1, User u2) {
        return Integer.compare(u1.getAge(),u2.getAge());
    }
};

TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
set.add(new User("Jack",33));
set.add(new User("Jack",56));

Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

六.Map接口

在这里插入图片描述

1.Map与Collection并列存在。用于保存具有映射关系的数据:key-value。key和value可以是任何引用类型的数据。 Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和
Properties

  • HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key和value
  • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap
  • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。底层使用红黑树
  • Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key或value
  • Properties:常用来处理配置文件key和value都是String类型

2.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中的所有数据
  • 修改:
    • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  • 查询:
    • Object get(Object key)获取指定key对应的value
    • int size()返回map中key-value对的个数
  • 判断:
    • boolean containsKey(Object key)是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • boolean isEmpty()判断当前map是否为空
    • boolean equals(Object obj)判断当前map和参数对象obj是否相等
  • 遍历:
    • Set keySet()返回所有key构成的Set集合
    • Collection values()返回所有value构成的Collection集合
    • Set entrySet()返回所有key-value对构成的Set集合

1.HashMap

HashMap是Map接口的主要实现类。允许使用null键和null值。

1.1 结构理解

  • key无序的、不可重复的,使用Set存储所有的key –> key所在的类要重写equals()和hashCode()
  • value无序的、可重复的,使用Collection存储所有的value –> value所在的类要重写equals()
  • 键值对:key-value构成了一个Entry对象
  • entry无序的、不可重复的,使用Set存储所有的entry

即判断两个key相等的标准:hashCode相等且equals相等;判断value相等的标准:equals相等。

1.2 重要常量

  • DEFAULT_INITIAL_CAPACITY : 默认容量:16
  • DEFAULT_LOAD_FACTOR:默认加载因子:0.75
  • threshold:扩容的临界值(容量*填充因子):16 * 0.75 => 12
  • TREEIFY_THRESHOLD:Bucket(桶)中链表长度大于该默认值,转化为红黑树:8
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

1.3 底层实现

JDK7:

  • new HashMap():创建了一个长度为16的Entry[] table数组
  • 数组+链表(形成链表时:新的元素指向旧的元素)

JDK8:

  • new HashMap()没有创建一个长度为16的数组,首次调用put()方法,底层创建成都为16的 Node[] 数组
  • 数组+链表+红黑树(形成链表时:旧的元素指向新的元素)
  • 当数组的某一个索引位置上的元素以链表形式存在的数据个数(大于8) 且当前数组的长度(大于64)时,此索引位置上的所有数据改为使用红黑树存储

1.4 添加元素的过程(JDK7)

向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i

  • 如果位置i上没有元素,则entry1直接添加成功。
  • 如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry。
    • 如果彼此hash值不同,则直接添加成功。
    • 如果hash值相同,继续比较二者是否equals
      • 如果返回值为true,则使用entry1的value去替换equals为true的entry的value。
      • 如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。

1.5 数组扩容

JDK7:

在不断的添加过程中,会涉及到扩容问题,当超出临界值12(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来

HashMap数组扩容后,原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize方法。比较消耗性能。

JDK8:

扩容:当HashMap中元素个数超过16 * 0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,并放进去。

树形化:当HashMap中的其中一个链的对象个数如果达到了8个,此时如果数组capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。

1.6 负载因子值

  • 负载因子的大小决定了HashMap的数据密度
  • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降
  • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内存空间。而且经常扩容也会影响性能
  • 按照语言参考及研究经验,会将负载因子设置为 0.7~0.75,此时平均检索长度接近于常数

1.7 示例

Map map = new HashMap();
map.put("AA",123);
map.put(45,123);
map.put("BB",56);
map.put(null,76);

System.out.println(map.get(45));    // 123

Object value = map.remove("AA");
System.out.println(value);    // 123 

boolean isExist = map.containsKey("BB");
System.out.println(isExist);    // true
isExist = map.containsValue(123);
System.out.println(isExist);    // true

map.clear();
System.out.println(map.isEmpty());    // true

//遍历时:通常使用增强for循环
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){
    System.out.println(obj);
}
//遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
    Object obj = iterator1.next();
    //entrySet集合中的元素都是entry
    Map.Entry entry = (Map.Entry) obj;
    System.out.println(entry.getKey() + "---->" + entry.getValue());
}
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
    Object key = iterator2.next();
    Object value = map.get(key);
    System.out.println(key + "=====" + value);
}

更多内容详看 : 源码学习 – > HashMap

2.LinkedHashMap

LinkedHashMap 是 HashMap 的子类。

在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。迭代遍历时:顺序与添加顺序一致

// 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 继承 Node
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);
    }
}

3.TreeMap

TreeMap底层使用红黑树结构存储数据。可以保证所有的 Key-Value 对处于有序状态。向TreeMap中添加key-value,要求key必须是由同一个类创建的对象

按照key进行排序:

  • 自然排序:所有key必须实现Comparable接口,所有key应是同一个类的对象,否则抛出ClassCastException
  • 定制排序:TreeMap构造器中传入一个Comparator对象,该对象负责对所有key进行排序,此时不需要Key所在类实现Comparable接口

判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0

自然排序示例:

TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

// 遍历打印
Set<User> keys = map.keySet();
for (User key : keys) {
    System.out.print(key + ":" + map.get(key)+"   ");

}

// User类:实现 Comparable接口
public class User implements Comparable{
    private String name;
    private int age;

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

    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(User u) {
        if(this.name.equals(u.name)){
            return Integer.compare(this.age,U.age);
        }else{
            return -this.name.compareTo(u.name);
        }
    }
}    

定制排序示例:

TreeMap map = new TreeMap(new Comparator() {
    @Override
    public int compare(User u1, User u2) {
        return Integer.compare(u1.getAge(), u2.getAge());
    }
});

User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);

map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);

// 遍历打印
Set<User> keys = map.keySet();
for (User key : keys) {
    System.out.print(key + ":" + map.get(key)+"   ");
}

4.Hashtable与Properties

1.Hashtable比较古老了,JDK1.0就提供了。Hashtable实现原理和HashMap相同,功能相同。判断两个key或value值相等的标准也一致。

HashMap与Hashtable区别:

  • HashMap线程不安全,Hashtable线程安全
  • HashMap允许使用null作为key或value,Hashtable不允许

2.Properties 类是 Hashtable 的子类,该对象用于处理配置文件。由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key和 value 都是字符串类型

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

Properties pros = new Properties();

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

String name = pros.getProperty("name");
System.out.println("name = " + name);

七.Collections工具类

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

1.排序操作

方法 描述
reverse(List) 反转 List 中元素的顺序
shuffle(List) 对 List 集合元素进行随机排序
sort(List) 根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List, Comparator) 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List, int, int) 将指定 list 集合中的 i 处元素和 j 处元素进行交换

示例:

List list = new ArrayList();
list.add(56);
list.add(12);
list.add(34);
list.add(89);
list.add(-53);
list.add(0);
list.add(34);
// 反转
Collections.reverse(list);
System.out.println(list);
// 随机排序
Collections.shuffle(list);
System.out.println(list);
// 两个索引位置的元素进行交换
Collections.swap(list,1,2);
System.out.println(list);

// Collections自然排序和定制排序方法底层都还是调用的 Arrays.sort(a, (Comparator) c)方法
// 自然排序
Collections.sort(list);
System.out.println(list);

// List接口中sort默认方法部分源码(JDK8中接口新特性)
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();    // 将集合转为数组
    Arrays.sort(a, (Comparator) c);    // 调用Arrays的sort方法

    ...
}

2.查找、替换操作

方法 描述
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 对象的所有旧值

示例:

// 元素出现次数
int frequency = Collections.frequency(list, 34);
System.out.println(frequency);

// 复制
//报异常:IndexOutOfBoundsException("Source does not fit in dest")
// List dest = new ArrayList();
// Collections.copy(dest,list);
//正确的:目标集合size大小 大于等于 原集合size大小
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());//list.size();
Collections.copy(dest,list);

// Collections中void copy(List dest,List src)方法源码
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    // 若原集合size大小 大于 目标集合size大小(并不是集合初始化容量大小),则抛异常
    if (srcSize > dest.size())    
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    ...
}

3.同步控制操作

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

List list = new ArrayList();
list.add(56);
list.add(12);
//返回的list2即为线程安全的List
List list2 = Collections.synchronizedList(list);

文章作者: ligong zhang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ligong zhang !
评论
 上一篇
集合源码学习 集合源码学习
一.ArrayListArrayList 底层是数组队列,相当于动态数组.与 Java 中的数组相比,它的容量能动态增长.线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1) . 它
2019-10-28
下一篇 
Java基础之常用类 Java基础之常用类
一.字符串相关的类1.String及常用方法1.1 String的特性 String:字符串,使用一对””引起来表示。 String声明为final的,不可被继承 String实现了Serializable接口:表示字符串是支持序列化的。
2019-10-12
  目录