11_泛型与集合(笔记总结)

@User:woBuShiKeDaYa
@Date:2022/4/15
@Completion:false

泛型与集合

定义泛型类/泛型接口

  1. 定义泛型类

    public static class Stu<T>{
            private T t;
    
            public Stu(T t) {
                this.t = t;
            }
    
            public void show(){
                System.out.println(t);
            }
    
        }
    
  2. 定义泛型接口

public interface InterfaceDemo1<E>{
    void show(E e);
}

我们比较常见的一个泛型接口就是:
public interface List<E> extends Collection<E>{}
List接口继承了Collection接口,Collection是集合层次结构中的根接口。

集合

接口
接口
元素可以重复_接口
元素不能重复_接口
接口
具体实现类
具体实现类
具体实现类
具体实现类
底层改为链表
具体实现类
具体实现类
具体实现类
集合
Collection
Map
List
Set
Queue
LinkList
ArrayList
Vector
HashSet
LinkedHashSet
TreeSet
HashMap
TreeMap

Collection(接口)

  1. 基本操作

    方法名说明
    boolean add(E e)添加元素
    boolean remove(Object o)从集合中移除指定得元素
    boolean removeIf(Object o)根据条件进行删除
    void clear()清空集合
    boolean contains(Object o)判断集合中是否存在指定的元素
    boolean isEmpty()判断集合是否为空
    int size()集合的长度,也就是集合中元素的个数
  2. 容器的特点

    Java容器里只能放对象,对于基本类型(int, long, float, double等),需要将其包装成对象类型后(Integer, Long, Float, Double等)才能放到容器里。很多时候拆包装和解包装能够自动完成。这虽然会导致额外的性能和空间开销,所以我们在提高程序性能时,在具体需求上灵活使用包装类和基本数据类型,避免因为自动装箱和自动拆箱导致的额外性能消耗,尽管会造成额外的性能消耗,但简化了设计和编程。

List(接口)

List接口是一个有序的集合,它允许我们按顺序存储和访问元素。它扩展了集合接口。

ArrayList(具体实现类)

ArrayList_UML图
ArrayList

ArrayList概述

  • ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
  • ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。也可以使用Vector
  • ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输;实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问;实现了Cloneable接口,能被克隆。

tips:序列化

Java序列化是指把Java对象转换为有序字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程,也就是,实现了Serializable接口,可以在流中输出这个数组时输出这个数组的字节序列。然后反序列化之后就由可以得到一个一样的数组。transient关键字可以使该关键字修饰的内容不被序列化

Fail-Fast机制

当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。

  1. 结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组大小,仅仅只是设置元素的值不算结构发生变化。
  2. 在进行序列化或者迭代操作时,需要比较操作前后modCount是否改变,如果改变了需要抛出ConcurrentModificationException,modCount用来记录ArrayList结构发生变化的次数。

ArrayList有以下特点:

  1. ArrayList基于数组方式实现,无容量的限制(会扩容)
  2. 添加元素时可能要扩容(所以最好预判一下,最好不要发生扩容,因为会调用Arrays.copyOf()把原数组整个复制到新数组中,这个操作代价是非常高的),删除元素时不会减少容量(若希望减少容量可以使用trimToSize()这个方法可以既删除容量也删除元素,但是代价很高)。
  3. 线程不安全
  4. add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
  5. get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
  6. remove(Object o)需要遍历数组
  7. remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
  8. contains(E)需要遍历数组
LinkList(具体实现类)

LinkList_UML

LinkList

  • LinkedList 继承了 AbstractSequentialList 类。
  • LinkedList 实现了 List 接口,可进行列表的相关操作。
  • LinkedList 实现了 Deque 接口,可作为队列使用。
  • LinkedList 实现了 Cloneable 接口,可实现克隆。
  • LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输
  • LinkList是一个双向链表

Tips:AbstractSequentialList类

AbstractSequentialList 继承自 AbstractList,是 LinkedList 的父类,是 List 接口 的简化版实现。

简化在哪儿呢?简化在 AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问。
其实也就是让LinkList支持顺序迭代器遍历。因为AbstractSequentialList 只支持迭代器按顺序访问。

具体方法使用和ArrayList差不多,特殊的实现了Deque接口,所以可以当作队列进行使用,有一些特殊得方法,比如说在链表头部插入元素的方法public boolean offerFirst(E e)以及等等队列、堆栈相关得操作也在LinkList中做了具体的方法实现。但Java中对于堆栈是有具体的实现类Satck

特别要注意的是,区别于ArrayList,LinkList的插入和删除效率较高,而ArrayList的查找效率较高,所以在不同的使用场景,可以对这两个具体的实现做出相应的使用。

Vector(具体实现类)

Vector和ArrayList类似,但是还是存在一定的区别(否则这两个留一个就行了为啥要单独区分)

  • Vector是同步访问的,存在同步锁(后续多线程同步问题会说到),也就是Vector是线程安全的
  • Vector 包含了许多传统的方法,这些方法不属于集合框架。也就是除了从List实现的一些方法外,它有一些自己独特的方法
Queue(接口)

这是一个队列的一个顶层接口,定义了队列的基本操作。
Queue
LinkList实现了Deque接口,代表LinkList可以当作一个双端队列进行使用。

  1. LinkedBlockingQueue的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
  2. ArrayBlockingQueue在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
  3. PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。
  4. DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
Set(接口)

UML
Set

如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set。也就是说,Set用于存储不重复的值。

Set接口并不保证有序,而SortedSet接口则保证元素是有序的。
TreeSet实现了SortedSet接口,但是Sorted接口则继承了Set。

HashSet(具体实现类)

UML
HashSet

图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVGCCPPG-1650014884790)(/picture/HashSet_1.PNG)]

构造方法

HashSet(int initialCapacity)
构造一个新的空集;支持HashMap实例具有指定的初始容量和默认加载因子 (0.75)。
HashSet(int initialCapacity, float loadFactor)
构造一个新的空集;后备HashMap实例具有指定的初始容量和指定的负载因子

加载因子是自动扩容时的一个参数,HashSet默认的容量值为16,在数组容量到达16*0.75=12的时候,就会自动扩容,扩容成原来大小的两倍。这里要注意:为了避免过多的自动扩容带来的额外负载,最好在初始化的时候指定一个相对合理的容量值

因为HashSet是西安了Set,所以HashSet中的元素是不能重复的,我们来看看如下这个例子:

public class HashSetDemo1 {
    public static void main(String[] args) {
        HashSet<Stu> Stus = new HashSet<>();
        HashSet<String> strings = new HashSet<>();
        Stu stu1 = new Stu("1","张三",20);
        Stu stu2 = new Stu("1","张三",20);
        String s1 = new String("add");
        String s2 = new String("add");

        strings.add("yuyanjia");
        strings.add("yuyanjia");

        strings.add(s1);
        strings.add(s2);

        System.out.println(strings);

        Stus.add(stu1);
        Stus.add(stu2);
        System.out.println(Stus);

    }
}

结果如下:

[add, yuyanjia]
[Stu{no=‘1’, name=‘张三’, age=20}, Stu{no=‘1’, name=‘张三’, age=20}]

为什么会出现这样的结果呢?这就是因为HashSet底层的判断原理。
在底层HashSet调用了HashMap的put(K key, V value)方法:在向HashMap中添加元素时,先判断key的hashCode值是否相同,如果相同,则调用equals()==进行判断,若相同则覆盖原有元素;如果不同,则直接向Map中添加元素;也就是说HashMap添加元素要保证键和值不相同,而判断的开始是先比较两者的哈希值是否相同,如果哈希值不相同则再用equals或者==进行比较。
在我自己写的Stu类中,我们虽然重写了equals方法,但是没有进行重写hashCode()方法。因为两个Stu都是new出来的对象,所以系统自己的哈希值往往是通过地址值进行计算的,所以这两个Stu对象的哈希值自然也不相同,所以HashSet在进行判断时,也会判定这两个元素不是相同的元素,都会进行储存。而String类冲洗了hashCode和equals方法,所以无论是在常量池的字符串,还是在堆区new出来的字符串,都会进行正确判断是否一样,所以在HashSet中只被储存了一次。

LinkedHashSet(具体实现类)

首先:LinkedHashSet是线程安全的

UML
LinkedHashSet

HashSet内部的基本部分是一个数组,牵引链表,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXhHQU5L-1650014884791)(/picture/LinkedHashSet.PNG)]

LinkedHashSet 和 HashSet的区别:

  • LinkedHashSet的默认容量和HashSet的默认容量一样,都为16.加载因子都为0.75
  • LinkedHashSet和HashSet都实现Set接口。 但是,它们之间存在一些差异。
  • LinkedHashSet在内部维护一个链表。因此,它保持其元素的插入顺序。
  • LinkedHashSet类比HashSet需要更多的存储空间。这是因为LinkedHashSet在内部维护链表。
  • LinkedHashSet的性能比HashSet慢。这是因为LinkedHashSet中存在链表。

但是,对于数据量庞大且增删很频繁的操作,LinkedHashSet不偿是一种很好的选择。

TreeSet(具体实现类)

TreeSet集合特点

  • 不包含重复元素的集合
  • 没有带索引的方法
  • 可以将元素按照规则进行排序

注意:如果想要使用TreeSet集合,必须要指定排序的规则

如何排序?有两种方法,分别是比较器排序,和自然排序

我们先来介绍自然排序:

public interface Comparable<T>:
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。

Map(接口)

HashMap(具体实现类)
HashTable(具体实现类)

这个类和HashMap类似,但是它是线程安全的。

TreeMap(具体实现类)

何排序?有两种方法,分别是比较器排序,和自然排序

我们先来介绍自然排序:

public interface Comparable<T>:
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。

Map(接口)

HashMap(具体实现类)
HashTable(具体实现类)

这个类和HashMap类似,但是它是线程安全的。

TreeMap(具体实现类)

@Precautions:笔记引用了多篇文章,仅做参考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卧槽你有120斤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值