1.5(java高级特性)集合Collection

**前言:**兄弟们你的三连是对我最大的鼓励。下次一定嘿嘿

我会努力认真的完成后续的文章的,希望可以帮到需要的人。

我也是特别懒惰,动不动就刷短视频就是小半天,哎呀,要自觉自觉。

接下来的文章内容来源于

[硅谷学习路线](2021年度全网最全Java学习路线 - 哔哩哔哩 (bilibili.com))

[2021年黑马程序员Java学习路线图 - 哔哩哔哩 (bilibili.com)](

java集合(1)

集合这篇文章我分成两份,一份写Collction一份Map

面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象 的操作,就要对对象进行存储。

另一方面,使用Array存储对象方面具有一些弊 端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。

数组的特点

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

数组的一些弊端

  1. 数组初始化以后,长度就不可变了,不便于扩展
  2. 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数
  3. 数组存储的数据是有序的、可以重复的。---->存储数据的特点单一

Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的 关联数组。

集合使用常见场景

比如说,歌曲列表,招聘网的公司列表
在这里插入图片描述

在列表中的每一项都表示一个对象,当然对象中还存在多个属性或者其他对象。

这个对象呢都是用集合存储,在用来展示

在这里插入图片描述

java集合概述

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

Collection接口

单列数据,定义了存取一组对象的方法的集合

在这里插入图片描述

List

元素有序、可重复的集合

set

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

Map接口

双列数据,保存具有映射关系“key-value对”的集合

在这里插入图片描述

Collection

上面介绍说java集合分为单列数据,和键值对数据,这块主要介绍单列数据

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

JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List) 实现。collection接口没有是是实现类但是他的子接口存在实现类

在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都 当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容 器中对象的数据类型。(泛型之后会进行介绍)

Collection接口方法

注意这里说的是接口的方法,子类会存在接口的方法,还会存在子类自己的方法。这与之后的执行子接口实现类方法不要搞乱

方法 描述
add(Object obj) 添加一个指定对象
addAll(Collection coll) 添加一个Collection集合,主要还是多态,放入是子接口实现类
int size() 返回集合中的个数
void clear() 清空集合
boolean isEmpty() 判断集合是否为空
boolean contains(Object obj) 判断集合是否存在指定对象
boolean containsAll(Collection c) 也是调用元素的equals方法来比 较的。拿两个集合的元素挨个比较。
boolean remove(Object obj) 删除指定对象,删除指定的第一个对象,因为list接口集合表示可重复的
boolean removeAll(Collection coll) 取当前集合的差集
boolean retainAll(Collection c) 把交集的结果存在当前集合中,不 影响c
Object[] toArray() 转换为Object数组
hashCode() 获取哈希值,来自于Object的父类方法
iterator() 返回迭代器对象,用于集合遍历,使用迭代器进行遍历集合,适用于set接口的集合不能通过下标获取值

示例使用

因为接口不能创建对象,只能使用多态的方式引用执行实例

引用子接口的实现类

public class CollectionMethodTest {
    
    
    public static void main(String[] args) {
    
    

//        首先我先使用ArrayList实现类来展示一下方法
//        父接口的引用指向子接口的实现
        Collection coll = new ArrayList();

//        发现可以传入不同的类型啊 ,这里是可以引入泛型来约定类型的。
        coll.add("我是String类型");
        coll.add(123);
        coll.add(true);

//        打印集合中元素个数
        System.out.println("coll集合中存在多少:"+coll.size());
//        移除指定数据
        coll.remove("我是String类型");
//        遍历集合
//        这里的实现了因为是List接口所以可以使用下标的形式获取
        System.out.println("使用foeeach遍历");
        for (Object o : coll) {
    
    
            System.out.println(o);
        }
        System.out.println("使用for遍历");
//        再次创建一个集合用来传入到另一个集合中
        ArrayList arrayList = new ArrayList();
        arrayList.add("one");
        arrayList.add("two");
        arrayList.add("three");
        ((ArrayList) coll).addAll(arrayList);
        for (int i = 0; i < coll.size(); i++) {
    
    
            //注意get方法并不来自于collection接口,而是来源自List接口
            System.out.println(((ArrayList) coll).get(i));
        }

        System.out.println("是否存在值为123的:"+coll.contains(123));

        System.out.println("清空集合");
        coll.clear();
        System.out.println("是否清空:"+coll.isEmpty());



    }

}

在这里插入图片描述

迭代器Iterator

这个可以用于遍历集合

因为List接口集合是存在指定下标的调用,但是set并不存在。没有下标怎么遍历呢,foreach是一种

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

迭代器的使用

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

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

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

Iterator接口方法

方法 描述
boolean hasNext() 判断是否存在下一个元素
Element next() 指针向下一位移动获取下一位的数据
remove() 移除当前指针下的元素

remove()方法如果删除的位置不存在元素,则报IllegalStateException。

示例

public class IteratorTest {
    
    
    public static void main(String[] args) {
    
    

//        创建一个List接口实现进行遍历
        Collection arrayList = new ArrayList();
        arrayList.add(123);
        arrayList.add("456");
        arrayList.add(true);

        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println("list:"+iterator.next());
        }

//        创建一个set接口实现进行遍历
        Collection hashSet = new HashSet();
        hashSet.add(789);
        hashSet.add("111");
        hashSet.add(false);
        Iterator iterator1 = hashSet.iterator();

        while (iterator1.hasNext()){
    
    
            System.out.println("set:"+iterator1.next());
        }


//        这里使用Iterator
//        首先通过集合获取Iterator,此时Iterator中存在数据。数据像一个列表一样排列
//        指针开始没有指向数据,此时要进行判断,下一个位置存不存在数据。通过hasNext()
//        如果存在,既可以获取next(),假设如果next()没有获取就会出现异常
//        直接调用it.next()会抛出NoSuchElementException异常。

    }
}

在这里插入图片描述

迭代器执行原理

//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
    
    
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}

//        这里使用Iterator
//        首先通过集合获取Iterator,此时Iterator中存在数据。数据像一个列表一样排列
//        指针开始没有指向数据,此时要进行判断,下一个位置存不存在数据。通过hasNext()
//        如果存在,既可以获取next(),假设如果next()没有获取就会出现异常
//        直接调用it.next()会抛出NoSuchElementException异常。

在这里插入图片描述

List接口

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

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

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

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

这里一段太长,不看这一段也可以的。直接从ArrayList开始

这里的这些我是在别的博文看到的,引用过来的。如果有侵权,就告诉我一下,我把他改掉

List三个接口对比

List实现Collection接口,它的数据结构是有序可以重复的结合,该结合的体系有索引;它有三个实现类:ArrayList、LinkList、Vector三个实现类。

三个实现类的基本区别

这里的这些我是在别的博文看到的,引用过来的。如果有侵权,就告诉我一下,我把他改掉

  1. ArrayList:底层数据结构使数组结构,查询速度快,增删改慢
  2. LinkList:底层使用链表结构,增删速度快,查询稍慢;
  3. Vector:底层是数组结构,Vector是线程同步的,所以它也是线程安全的。而ArratList是线程异步的,不安全。如果不考虑安全因素,一般用Arralist效率比较高;(和ArrayList很像)

可变长度数组不断new数组:
(1) ArrayList当初始化容量超过10时,会new一个50%de ,把原来的东西放入这150%中;
(2) Vector:当容量超过10时,会new一个100%的浪费内存;

ArrayList

概述

① ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

② 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

③ 注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

实现

对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析ArrayList的源代码:底层使用数组实现

Java代码 private transient Object[] elementData;

构造方法:
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

LinkList

概述

LinkedList的本质是双向链表。

(01) LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
(02) LinkedList包含两个重要的成员:header 和 size。
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
size是双向链表中节点的个数。

实现

LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。

既然LinkedList是通过双向链表的,但是它也实现了List接口{
     
     也就是说,它实现了get(int location)remove(intlocation)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?


实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。

这里的这些我是在别的博文看到的,引用过来的。如果有侵权,就告诉我一下,我把他改掉

ArrayList实现类

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

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

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

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

ArrayList源码分析

JDK7

new ArrayList();底层创建了长度是10的Object[]数组。

如果添加元素容量不够的时候,则进行扩容,默认情况下是原来的1.5倍,同时将原来数组的内容移动到新的数组中

JDK8

new ArrayList();底层数组初始化为{},并没有创建数组长度。

当第一次添加的时候创建长度为10.

下面的扩容和JDK7一致

这里再硅谷存在很多的源码带入,我只能进行文件的总结还是建议看看硅谷的视频

LinkedList实现类

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

LinkedList的源码分析

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

LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。

同时,定义内部类Node,作为LinkedList中保存数据的基 本结构。Node除了保存数据,还定义了两个变量:

  1. prev变量记录前一个元素的位置
  2. next变量记录下一个元素的位置

LinkedList中的每一个数据对象都是Node类型

在这里插入图片描述

LinkedList的新增方法

因为LinkedList作为实现类,而且底层数据类型的不一致。对比于接口肯定可以有自己的方法

方法 描述
void addFirst(Object obj) 插入到第一个元素上
void addLast(Object obj) 插入到最后一个位置上
Object getFirst() 获取第一个
Object getLast() 获取第二个
Object removeFirst() 删除第一个
Object removeLast() 删除最后一个

ArrayList和LinkedList

ArrayList底层是数组,LinkedList底层是双向链表

为什么ArrayList比LinkedList插入删除效率不高

因为LinkedList是双向链表,再插入或删除的时候只需要指定左右两边的node指向问题即可。

数组再插入和删除的时候,需要对其之后的元素位置移动,插入后面的全部向后移动,删除后面的全部向前移动

为什么ArrayList比LinkedList读取效率高

因为ArrayList存在下标,直接通过下标进行指定到元素即可

LinkedList没有下标,通过一个一个的node对象进行查找

在这里插入图片描述

List小结

因为上面的内容太多了,我写的你们看有可能会看的乱一点。这里我整理几个主要重点

  1. List接口的特点
  2. List接口下的三个实现类区别(也是面试题)
  3. 针对于ArrayList和LinkeList的源码解析部分。

Set接口

Set接口是Collection的子接口,set接口没有提供额外的方法所以下面就不会在介绍方法了

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

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

也就是说Set存储的内容是无序的唯一的

三个主要的实现类

HashSet

作为Set接口的主要实现类,线程是不安全的,可以存储NULL值

但是只能存储一次,因为Set无序唯一性

LinkedHashSet

作为HashSet的子类,在遍历内部元素的时候可以按照添加的顺序遍历

TreeSet

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

Set的无序性和唯一性

无序性

public class SetTest {
    
    
    public static void main(String[] args) {
    
    

//        创建一个Hashset做演示
        HashSet hashSet = new HashSet();
        hashSet.add(123);
        hashSet.add("AA");
        hashSet.add("cc");
        StringBuffer qwe = new StringBuffer("qwe");
        hashSet.add(qwe);

//        遍历发现打印的顺序和添加的顺序不一致。
//        但是并不是表示数据是随机性的。Set有自己的排序规则
        for (Object o : hashSet) {
    
    
            System.out.println(o);
        }

//        这里看的是HashSet的排序规则,
//        根据Hashcode的值大小排序
        System.out.println("查看各个元素的hashcode值");

        System.out.println("AA".hashCode());
        System.out.println("cc".hashCode());
        System.out.println(qwe.hashCode());


    }
}

在这里插入图片描述

唯一性

public class SetTestweiyi {
    
    
    public static void main(String[] args) {
    
    
//        创建HashSet在其中添加4个值,有一个值是重复的。
//        HashSet的检验重复是根据equals()方法
        HashSet hashSet = new HashSet();
        hashSet.add(123);
        hashSet.add("AA");
        hashSet.add("aa");
        hashSet.add("aa");

        System.out.println("hashSet中存在多少值"+hashSet.size());
    }
}

在这里插入图片描述

HashSet实现类

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

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

HashSet 具有以下特点:

  1. 不能保证元素的排列顺序
  2. HashSet 不是线程安全的
  3. 集合元素可以是 null

唯一性规则

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

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

HashSet中元素添加过程

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法 来得到该对象的 hashCode 值

然后根据 hashCode 值,通过某种散列函数决定该对象 在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在 数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布, 该散列函数设计的越好)

如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果 为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会通过链表的方式继续链接。

在这里插入图片描述

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

重写HashCode,equals

为了更好的避免重复。重写方法满足的条件

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

LinkedHashSet

LinkedHashSet是HashSet的子类

为什么LinkedHashSet作为无序可以打印有序的呢

Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("刘德华", 1001));

在这里插入图片描述

可以看到首选数据还是与HashSet一样存储的。

但是看到一个类型LinkedList中的一个东西node类型

所以可以知道,打印的顺序就是通过Node类型进行前后绑定。存在了一个无序列表,又可以有序的一个情况

TreeSet

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。

TreeSet底层使用红黑树结构存储数据

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

TreeSet和后面要讲的TreeMap 采用红黑树的存储结构

在这里插入图片描述

特点:有序,查询速度比List快

示例展示

public class TreeSetTest {
    
    
    public static void main(String[] args) {
    
    

//        尝试放入不同的类型进行遍历
        TreeSet treeSet = new TreeSet();
        treeSet.add("123");
        treeSet.add(123);

        for (Object o : treeSet) {
    
    
            System.out.println(o);
        }

    }
}

在这里插入图片描述

发现报了个异常String不能自定转换int,在compareTo排序中

所以说明TreeSet只能存放相同的类型数据,需要进行ComparedTo方法中进行排序

public class TreeSetTest {
    public static void main(String[] args) {

//        尝试放入相同的类型进行遍历
        TreeSet treeSet = new TreeSet();
        treeSet.add("123");
        treeSet.add("456");

        for (Object o : treeSet) {
            System.out.println(o);
        }

    }
}

在这里插入图片描述

排 序—自然排序

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

如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。

因为只有重写CompareTo()才能进行排序

实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。

Comparable 的典型实现:

BigDecimal、BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小 进行比较

Character:按字符的 unicode值来进行比较

Boolean:true 对应的包装类实例大于 false 对应的包装类

String:按字符串中字符的 unicode 值进行比较

Date、Time:后边的时间、日期比前面的时间、日期大

排 序—定制排序

TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没 有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照 其它属性大小进行排序,则考虑使用定制排序。

定制排序,通过Comparator接口来 实现。需要重写compare(T o1,T o2)方法。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Guess you like

Origin blog.csdn.net/weixin_46401545/article/details/121514902