Java-集合

集合

部分文字和图片版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/feiyanaffection/article/details/81394745

1.集合框架概述

在这里插入图片描述

1.1.集合与数组

在这里插入图片描述

  • 集合和数组都是对多个数据进行存储操作的结构,简称Java容器。
    • 此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储。

2.Collection

  • Collection:接口,单列数据,定义了存取一组对象的方法的集合。
  • Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
  • 所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。

在这里插入图片描述

  • All方法参数的类型都为Collection ,大多数方法都是返回boolean类型值,Collection 接口用于表示任何对象或元素组。想要尽可能以常规方式处理一组元素时,就使用这一接口。(如,可以直接add(100),可以是普通数据类型)。

  • 容器类对象在调用remove,contains等方法时需要比较对象是否相等地,这会涉及到对象类型的equals方法和hashcode方法。即,相等的对象应该有相等的hashcode。当然,如果是自定义的类型,需要重写这两个方法。

2.1.遍历集合元素

2.1.1.Iterator接口
  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
  • Collection接口继承了java.lang.Iterator接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象。
  • Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
  • 集合对象每次iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
boolean hasNext()  //如果仍有元素可以迭代,则返回 true。
    
E next()           //返回迭代的下一个元素。 
    
void remove()      //从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操作)。 

2.1.2.foreach循环
  • Java 5.0 提供了foreach循环迭代访问Collection和数组。
  • 遍历操作不需要获取Collection或数组的长度,无需使用索引访问元素。
  • 遍历集合的底层是调用Iterator完成操作的。
  • foreach还可以用来遍历数组。

2.2.List和Set的区别

在这里插入图片描述

2.3.List接口

  • List 接口:元素有序,可重复的集合,元素按进入先后有序保存。 ----> “动态”数组
  • List接口继承了 Collection接口以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。
  • 有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
  • List接口提供了特殊的迭代器,称为 ListIterator,除了允许 Iterator接口提供的正常操作外,该迭代器还允许元素插入和替换,以及双向访问。还提供了一个方法来获取从列表中指定位置开始的列表迭代器。

在这里插入图片描述

List接口的实现类

2.3.1.ArrayList
  • 作为List接口的主要实现类, 线程不安全,效率高;底层使用Object[] elementData存储。
  • 本质上,ArrayList是对象引用的一个“变长”数组。
  • ArrayList的JDK1.8之前与之后的实现区别
    • JDK1.7:直接创建一个初始容量为10的数组。
    • JDK1.8:一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组。
  • 当数组容量不够时,默认情况下扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
  • Arrays.asList(…)方法返回的是List集合,既不是ArrayList实例,也不是Vector实例。Arrays.asList(…)返回值是一个固定长度的List集合
import java.util.ArrayList;
import java.util.List;

/*
    测试题
    区分List中remove(int index)和remove(Object obj)
 */
public class Test02 {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);

        updateList(list);
        System.out.println(list);
    }

    private static void updateList(List list) {
//        list.remove(2);   //删除的是索引为2的元素
        list.remove(new Integer(2));  //删除的是内容为2的元素
    }
}
2.3.2.LinkedList
  • 对于频繁的插入,删除操作,使用此类效率比ArrayList高, 线程不安全;底层使用双向链表存储。
  • 内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时定义内部类Node,作为LinkedList中保存数据的基本结构。
LinkedList list = new LinkedList();  //内部声明了Node类型的first和last属性,默认值为null
list.add(12);    //将12封装到Node中,创建了Node对象

//Node的定义
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
2.3.3.Vector
  • 作为List接口的古老实现类, 线程安全,效率低;底层使用Object[] elementData存储。

  • Stack:是Vector类的实现类 。

2.4.Set接口

  • Set 接口:元素无序,不可重复的集合。 ----> 类似于数学中的集合
  • Set接口中没有提供额外的方法。
  • Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败。
  • Set判断两个对象是否相同不是使用==运算符,而是使用equals()方法。
  • 无序性和不可重复性的理解(已HashSet为例)
    • 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的hash值决定的。
    • 不可重复性:保证添加的元素按照equals()判断时,不能返回true。即:相同的元素只能添加一个。

Set接口实现类

2.4.1.HashSet
  • HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值。
  • HashSet按Hash算法来存储集合中的元素,因此具有很好的存取,查找和删除性能。
  • HashSet具有以下特点
    • 不能保证元素的排列顺序。
    • HashSet不是线程安全的。
    • 集合元素可以是null值。
  • HashSet判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
  • 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode()方法,以实现对象相等规则。即:相等的对象必须有相等的散列码。

在这里插入图片描述

import java.util.*;

//练习:在List内去除重复数字值,要求尽量简单
public class Test03 {

    public static List test(List list){
        HashSet hashSet = new HashSet();

        hashSet.addAll(list);
        return new ArrayList(hashSet);
    }

    public static void main(String[] args) {
        List list = new ArrayList();

        list.add(new Integer(6));
        list.add(new Integer(6));
        list.add(new Integer(4));
        list.add(new Integer(5));
        list.add(new Integer(5));
        list.add(new Integer(7));

        List list2 = test(list);

        for (Object integer : list2) {
            System.out.println(integer);
        }
    }
}
import java.util.HashSet;

public class Test04 {

    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001, "AA");
        Person p2 = new Person(1002, "BB");

        set.add(p1);
        set.add(p2);
        System.out.println(set);
        System.out.println("=================");

        /*
        将name的值修改为CC,当执行删除操作时,先计算此时的Hash值,由于name的值进行了修改,
        Hash值也相应的改变,此时再去查找,就找不到之前存储的那个位置,删除失败。
         */
        p1.name = "CC";
        set.remove(p1);
        System.out.println(set);
        System.out.println("===================");

        /*
        当添加如下对象时,先计算当前对象的Hash值,由于p1存储时是按照name为AA计算Hash值的,
        存储完毕后,存储位置固定,进行修改操作位置不会发生改变。此时如下对象计算Hash值时,
        由于name属性值不同,计算出的Hash值不会与p1相同,存储成功。
         */
        set.add(new Person(1001, "CC"));
        System.out.println(set);
        System.out.println("==================");

        /*
        当添加如下对象时,先计算Hash值,发现与p1相同,再调用当前对象的equals方法与p1进行
        比较,name属性值不一致,存储成功。
         */
        set.add(new Person(1001, "AA"));
        System.out.println(set);
    }
}
2.4.2.LinkedHashSet
  • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序进行访问。
  • LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入的顺序保存的。
  • LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
  • LinkedHashSet不允许集合元素重复。
2.4.3.TreeSet
  • TreeSetSortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
  • TreeSet:底层实现为二叉树;可以按照添加对象的指定属性进行排序。
  • TreeSet底层使用红黑树结构来存储。
  • TreeSet两种排序方式:自然排序定制排序默认情况下,TreeSet采用自然排序。
2.4.4.添加元素的过程

以HashSet为例

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

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

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

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

总结:七上八下

在这里插入图片描述

3.Map

  • Map:接口,键值对的集合,存储一对一对的集合 (key-value)(双列集合) ----> 类似于数学中的函数 y=f(x)
  • Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。
  • Map 没有继承 Collection 接口,Map 提供 key 到 value 的映射,你可以通过“键”查找“值”。一个 Map 中不能包含相同的 key ,每个 key 只能映射一个 value 。Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。
  • key所在的类要重写equals()方法和hashCode()方法(已HashMap为例)。
  • value所在的类要重写equals()方法。

在这里插入图片描述

3.1.HashMap

  • HashMap:作为Map接口的主要实现类 ;线程不安全,效率高;可以存储null的key和value。HashSet一样不保证映射的顺序。

  • 所有key构成的集合是Set:无序的,不可重复的。所以,key所在的类要重写equals()和hashCode()。

  • 所有value构成的集合:无序的,可以重复的。所以,value所在类要重写equals()。

  • 一个key-value构成一个entry。

  • 所有的entry构成的集合是Set:无序的,不可重复的。

  • HashMap判断两个key相等的标准是:两个key通过equals()方法返回true,hashCode值也相等。

  • HashMap判断两个value相等的标准是:两个value通过equals()方法返回true。

  • HashMap底层

    • 数组+链表 (JDK7及之前)
    • 数组+链表+红黑树 (JDK8)

在这里插入图片描述

3.2.HashMap底层实现原理

以JDK 7为例

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

可能执行过多次put

map.put(key1,value1):

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

  • 如果此位置上的数据为空,此时可key1-value1添加成功 —> 情况1
  • 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表的形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
    • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功 —> 情况2
    • 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)。
      • 如果equals()返回为false:此时key1-value1添加成功。 —> 情况3
      • 如果equals()返回为true:使用value1替换value2。

补充关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

默认扩容为原来的两倍,并将原有的数据复制过来。

JDK8相较于JDK7在底层实现方面的不同:

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

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

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

  • JDK 7底层结构只有:数组+链表。JDK 8底层结构:数组+链表+红黑树。

    • 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,

      此时此索引位置上的所有数据改为使用红黑树。

3.3.其他实现类

在这里插入图片描述

LinkedHashMap
  • LinkedHashMap:双向链表和哈希表实现;保证在遍历map元素时,可以按照添加的顺序实现遍历。

  • 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个数据。

  • 对于频繁的遍历操作,此类执行效率高于HashMap。

TreeMap
  • 保证按照添加的key-value对进行排序,实现排序操作。此时考虑key的自然排序或定制排序。

在这里插入图片描述

Hashtable
  • 作为Map接口的古老实现类;线程安全,效率低;不可以存储null的key和value。
  • HashMap和HashTable的比较

在这里插入图片描述

Properties
  • Hashtable的子类;常用来处理配置文件。key和value都是String类型。
  • 存取数据时,建议使用setProperty(String key ,String value)和getProperty(String key)方法。
Properties pros = new Properties();
pros.load(new FileInputStream("文件.properties"));
String user = pros.getProperty("user");
System.out.println(user);
IdentifyHashMap
  • IdentityHashMapHashMap的具体区别,IdentityHashMap使用 == 判断两个key是否相等,而HashMap使用的是equals方法比较key值。有什么区别呢?
  • 对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等; 如果作用于引用类型的变量,则比较的是所指向的对象的地址。
  • 对于equals方法,注意:equals方法不能作用于基本数据类型的变量
  • 如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
  • 诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

4.Collections工具类

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

Collections常用方法

修饰语和类型方法描述
static booleanaddAll(Collection<? super T> c, T… elements)将所有指定的元素添加到指定的集合
static intbinarySearch(List<? extends Comparable<? super T>> list, T key)使用二叉搜索算法搜索指定对象的指定列表
static voidcopy(List<? super T> dest, List<? extends T> src)将所有元素从一个列表复制到另一个列表中
static <T extends Comparable<? super T>>sort(List list)根据其元素的自然顺序对指定的列表进行排序
static voidfill(List<? super T> list, T obj)用指定的元素代替指定列表的所有元素
static intfrequency(Collection<?> c, Object o)返回指定集合中与指定对象相等的元素数
static intindexOfSubList(List<?> source, List<?> target)返回指定源列表中指定目标列表的第一次出现的起始位置,如果没有此类事件,则返回-1
static intlastIndexOfSubList(List<?> source, List<?> target)返回指定源列表中指定目标列表的最后一次出现的起始位置,如果没有此类事件则返回-1
static ArrayListlist(Enumeration e)返回一个数组列表,其中包含由枚举返回的顺序由指定的枚举返回的元素
static booleanreplaceAll(List list, T oldVal, T newVal)将列表中一个指定值的所有出现替换为另一个
static voidreverse(List<?> list)反转指定列表中元素的顺序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值