0
点赞
收藏
分享

微信扫一扫

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

迪莉娅1979 2022-04-15 阅读 25
eclipse

@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(接口)

  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概述

tips:序列化

Fail-Fast机制

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类

具体方法使用和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是西安了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);

    }
}

结果如下:

为什么会出现这样的结果呢?这就是因为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 条评论