java基础 --- Collection

集合框架接口

一般考察集合的内容主要有5个方面:
1.底层数据结构
2.增删改查方式
3.初始容量,扩容方式,扩容时机。
4.线程安全与否
5.是否允许空,是否允许重复,是否有序

1.集合结构图
1.collection类集合结构图
Collection集合大体结构
2.map类集合结构图
Map类结合结构图
2.Iterable接口
1.作用

Iterable接口表示一组对象,Iterable接口需要实现的iterator方法的功能是“返回”一个迭代器,我们常用的实现了该接口的子接口有: Collection, Deque, List, Queue, Set 等,实现这个接口允许对象成为 Foreach 语句的目标,就可以通过Foreach语法遍历底层序列。 同时该对象还需要给定一个Iterator的实现类

public interface Iterator<E> {
	// 判断是否有下一个元素
	boolean hasNext();
	// 获取下一个元素
	E next();
	// 将迭代器新返回的元素删除
	default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    // lambda表达式循环迭代
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}
2.结构
public interface Iterable<T> {
    // 获取Iterator对象
	Iterator<T> iterator();
    // default是java8出现的关键字、在接口中也可以写方法实现
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
    // forEach可使用lambda表达式快速消费
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}
3.总结

(1) 对象要想用foreach必须要实现Iterable接口,并且自定义Iterator处理逻辑

(2) 增强for循环的底层是迭代器,任何实现了iterable接口的类都可以使用增强for循环来遍历

3.collection接口
1.作用

Collection接口作为集合的一个根接口,它提供了对集合对象进行基本操作的通用接口方法,接口在Java 类库中有很多具体的实现。其意义是为各种具体的集合提供了最大化的统一操作方式,JDK不提供此接口的任何直接实现,而是提供更具体的子接口,有Set接口、List接口、Queue接口。Set接口存放的元素是无序的且不包含重复元素(对象);List接口存放的元素是有序的且允许有重复的元素;Queue接口存放的元素顺序符合先入先出FIFO的规则

2.结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsNzqAPm-1617350261792)(\image\collection.png)]

  1. addAll:在集合中添加一个集合
  2. clear: 删除当前Collection中的所有元素
  3. containsAll: 判断此Collection是否包含指定Collection中所有元素
  4. parallelStream/stream:转为流对象
  5. removeAll:删除集合中的所有元素
  6. removeIf:条件删除集合中的元素
  7. rertainAll: 从当前Collection中删除collec中不包含的元素 (求交集)
  8. toArray:将集合转换为数组对象
3.重点补充
  1. parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。简单理解就是多线程异步任务的一种实现、Stream是普通的串行流、使用parallelStream要考虑线程安全问题

    public static void main(String[] args) {
            List<Integer>  list  =  new ArrayList<>();
            for (int j = 0; j < 1000; j++) {
                list.add(j);
            }
            System.out.println("源数据长度:"+list.size());
            for (int i = 0; i < 10 ; i++) {
                List<Integer> parseList = new ArrayList<>();
                // List<Integer> synchronizedList = Collections.synchronizedList(parseList);
                list.parallelStream().forEach(integer -> {
                    synchronizedList.add(integer);
                });
                System.out.println("每次遍历的集合长度:"+ synchronizedList.size());
            }
        }
    结果:会出现集合长度减少、多次运行还会出现角标越界现象
    串行流:适合存在线程安全问题、阻塞任务、重量级任务,以及需要使用同一事务的逻辑
    并行流:适合没有线程安全问题、较单纯的数据处理任务
    
4.List接口
1.作用
  1. 有序的集合,存储元素和取出元素的顺序是一致的(存储123 取出123)
  2. 有索引,包含了一些带索引的方法(特有)
  3. 允许存储重复的元素
2.结构
public void add(int index,E element) // 将指定的元素,添加到该集合中的指定位置上。
public E get(int index) // 返回集合中指定位置的元素。
public E remove(int index) // 移除列表中指定位置的元素,返回的是被移除的元素。
public E set(int index,E element)// 用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
int indexOf(Object o); // 最开始出现元素的位置
int lastIndexOf(Object o); // 最后出现元素的位置
default void sort(Comparator<? super E> c) { // Comparator排序 先将集合转为数组、排序好之后再替换
	Object[] a = this.toArray();
	Arrays.sort(a, (Comparator) c);
	ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
}
5.Set接口
1.作用

​ 由于Set接口的特殊性,所有传入Set集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。实现了Set接口的集合有:EnumSet、HashSet、TreeSet。

  1. Set是一种不包括重复元素的Collection
  2. 没有索引,通过对象的hashcode来维护自己的顺序,对外表现为无序
  3. 可以存null、但只能有一个
2.结构

​ 里面的方法全部都是collect中的继承、仅仅为了维护自己的特性

6.Queue接口
1.作用

​ Queue接口是队列接口、在FIFO或LIFO情景下使用;主要的接口有

  1. 阻塞接口 ( BlockingQueue)

    java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
    五个队列所提供的各有不同:

    * ArrayBlockingQueue :一个由数组支持的有界队列。
      * LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
      * PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
      * DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
      * SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。

  2. 非阻塞接口 ( AbstractQueue)

    内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue、PriorityQueue 和 ConcurrentLinkedQueue 类在 Collection Framework 中加入两个具体集合实现,PriorityQueue 类实质上维护了一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位,ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大 小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列

  3. 双端队列(Deque)

2.结构

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nj8s0OT2-1617350261794)(image\queue.png)]

add       // 增加一个元索,如果队列已满,则抛出一个IIIegaISlabEepeplian异常
offer     // 添加一个元素并返回true,如果队列已满,则返回false
remove    // 移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
element   // 返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常
poll      // 移除并返问队列头部的元素,如果队列为空,则返回null
peek      // 返回队列头部的元素,如果队列为空,则返回null
--- 阻塞队列特殊方法
put       // 添加一个元素,如果队列满,则阻塞
take      // 移除并返回队列头部的元素,如果队列为空,则阻塞

集合框架核心类

1.ArrayList
1.特点
  • 底层基于数组实现,容量大小动态变化
  • 允许 null 的存在
  • 基于索引查找元素速度快、插入和删除的效率比较低
  • 每次扩容现有容量的50%
  • 非线程安全
2.结构
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, Serializable{   
	private static final int DEFAULT_CAPACITY = 10; // 初始化容量
    transient Object[] elementData; // 容器
    private int size; // 记录数组大小
	protected transient int modCount = 0; // 来源于abstractList 用于快速失败
}    
3.总结
  1. ArrayList是如何自动容量控制的?

    jdk1.7是初始化就创建一个容量为10的数组(饿汉式),1.8后是初始化先创建一个空数组(懒汉式),第一次add时才扩容为10 ,每次在添加的时候会检查数组的容量、如果容量不够会新建一个数组、长度为将原来数组的1.5倍(1.7是取余、1.8是右移两位 >> 2),这样实现了自动扩容。ArrayList本身没有实现自动缩容机制、但有一个trimToSize方法可以看做是缩容

    public void trimToSize() {
            // 修改次数自增
            modCount++;
            // 判断当前是否需要缩容
            if (size < elementData.length) {
                // 如果size为0,直接给elementData赋值内置的空数组
                // 不为0则创建一个size长度的新数组
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
    
  2. ArrayList添加数据的过程?

    	public boolean add(E e) {
        	ensureCapacityInternal(size + 1);  // size + 1 代表本次最小需要的容量
        	elementData[size++] = e;
        	return true;
    	}
        private void ensureCapacityInternal(int minCapacity) {
            // calculateCapacity 计算出最小期望容量
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    	// 计算容量最小值
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }	
    	// 扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity; // 防止扩容了之后比最小需要容量小
            if (newCapacity - MAX_ARRAY_SIZE > 0) // 溢出
                newCapacity = hugeCapacity(minCapacity);
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    1.计算最小容量值、如果elementData数组为空数组、则最小容量至少为10,如果不是空数组、最小容量为最小需要容量
    2.校验扩容、如果最小需要容量减去数组的长度大于零,则扩容,并将elementData指向扩容的新数组
    3.添加元素elementData[size++] = e并且返回true
    
  3. ArrayList删除数据的过程?

            public E remove(int index) {
                rangeCheck(index);
                checkForComodification();
                E result = parent.remove(parentOffset + index);
                this.modCount = parent.modCount;
                this.size--;
                return result;
            }
    1.校验索引
    2.校验modCount、快速失败
    3.最优化、删除、遍历移动 AccessRandom
    4.modCount同步
    5.size减一 、返回结果
    
  4. 快速失败作用和原理?

    一、快速失败(fail—fast)
    在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出 Concurrent Modification Exception。
    
    原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
    
    注意:这里异常的抛出条件是检测到 modCount != expectedmodCount 这个条件。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。
    
    场景:java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
    
    二、安全失败(fail—safe)
    采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
    
    原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception。
    
    >缺点:基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
    场景:java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
    
2.LinkedList
1.特点
  • 底层基于 双向链表实现的,没有初始化大小,容量大小动态变化,也没有扩容的机制,可以包含重复的元素
  • LinkedList实现了Queue、Deque接口
  • LinkedList适合增加和删除操作,不会发生移位 ,在查找上要逐个遍历,性能不如ArrayList
  • LinkedList是非线程安全的
  • LinkedList维护了元素插入时的顺序
2.结构

​ 同时实现了List、queue、Deque接口,所以LinkedList同时具备三种接口的共同特征,本身比较简单

3.总结

​ 略…

3.Vector
1.特点
  • 古老的集合since JDK1.0

  • 底层基于数组实现,

  • vector底层数组不加transient,序列化时会全部复制

  • 线程安全、全部都加上了 synchronized

2.结构
扩容机制、如果制定了capacityIncrement就每次增加多少,如果没有指定就翻倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); 
3.总结

代码结构跟ArrayList相识度90% , 略…

3.CopyOnWriteArrayList
1.特点
  • 底层基于数组实现
  • 增加和删除都是加了锁、查询基于索引、直接返回
  • 初始容量为0、扩容方式为加一、扩容时机每次add的时候加一
  • 线程安全、全部都加上了 ReentrantLock
  • 允许为空、允许重复、有序
  • 虽然Array元素数组上加了volatile保证了可见性,但是在新增删除的过程中都是copy了一个新数组进行操作,所以能保证最终一致性,但是不能保证实时性
2.结构
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    final transient ReentrantLock lock = new ReentrantLock();
    private transient volatile Object[] array;
    ...
}
添加元素
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
删除元素
public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
3.总结
  1. CopyOnWriteArrayList的缺点?

    1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc

    2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求

  2. ReentrantLock原理?

4.HashSet
1.特点
  • 底层数据结构是HashMap,key为记录的值、value统一为同一个Object对象
  • 增删改查都是间接用的HashMap对象的增删改查
  • 初始容量、扩容方式、扩容时机和HashMap一样
  • 线程不安全
  • 允许null值
  • 基于hash存储、存入的元素没有顺序
2.重点结构
1.基于集合构造初始化方式
public HashSet(Collection<? extends E> c) {
    // 用集合的size除于0.75 + 1 来作为入参 目的就是得到最接近16倍数的容量值
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
2.default的构造方法为子类LinkedHashSet做铺垫
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
3.总结

​ 一切基于HashMap(或 LinkedHashMap)、自己就是个壳子

未完待续…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值