Java的容器归纳-1

这篇笔记从数据结构的角度对常用容器进行归纳,基于java14。

常用数据结构:

有序型: 栈、堆、队列、双端队列;
有序表在实现上有两个选择,链表或者数组(顺序表)。

数组:物理空间连续,可以使用偏移量随机访问 x [ i ] x[i] x[i],时间复杂度O(1),但是容量固定,所以存在扩容的问题,这点是基于数组实现的容器类都需要关注的
链表显然没有扩容问题,但是访问 x [ i ] x[i] x[i]需要遍历前 i i i个元素,时间复杂度O(n)。

无序型: Map,Set;
实现上也有两种选择,基于Hash表(实际上还是数组)和基于树模型。
基于Hash表的方法通过键的哈希值可以得到位置偏移量,访问时间O(1),用了数组就存在扩容问题。基于树模型的在Java中使用红黑树,访问时间O(lgn)。java在Set的实现上偷懒,直接使用Map存储,将值置为一个固定值。

栈的实现:
Stack:先进后出

Stack
Vector实现类
AbstractList
AbstactCollection

AbstractList是List接口对应的抽象类,AbstractCollection同理。主要是实现了部分方法,降低继承的难度 (List接口的方法太多啦!!!)
其中Vector是可实例化的类,内部的存储结构是Object[] (需要扩容),值得一提的是Vector实现了RandomAccess接口,这是一个标记接口 (什么方法也没有,只表明我能干什么,果然是强语言类型)

两个常用的基础数据结构实现

ArrayList:

ArrayList
AbstractList
AbstactCollection

看名字显然内部实现是Object[], 同样实现了RandomAccess。默认容量为10,初始为空,在添加第一个时才grow。每次扩容都是1.5倍增长。

LinkedList:

ArrayList
AbstractSequentialList
AbstractList
AbstactCollection

  链表实现, 没有扩容问题。无法用位置偏移量随机访问,所以加了一个AbstractSequentialList支持链表的访问逻辑(遍历),只需要子类中实现Iterator。实际上 LinkedList 还是重写了get方法,用遍历的方式来做,每次调用都会从头遍历
   链表随机访问get(i)需要逐个遍历直到找到相应位置的元素,如果只想顺序遍历整个链表显然调用get不是一个好方法。
具体的方法应该使用增强的for语句(实现上也是迭代器,需要实现Iterable接口)或者调iterator()获得一个迭代器,自己实现存取控制,比for的可操作性更高。

下面的Deque(Interface)、Queue(Interface)都是接口
堆的实现:
PriorityQueue 小根堆

PriorityQueue
AbstractQueue
Queue

存储由Object[]实现,放入的对象要实现Comparable接口,或者构建时传入一个Comparator对象用于比较两个对象的大小。

双端队列:

ArrayDeque
Deque
LinkedList
Queue

数组实现: ArrayDeque
链表实现: LinkedList
双端队列完全可以用上述的两个基本结构来实现,(当然我没研究线程安全的问题)。

Map
HashMap 用数组存储,拉链法处理冲突,当一个坑位的元素过多(冲突),个数超过8,这个坑位会被转换成Tree(红黑树)。(实际上不一定,还会还会判断下总个数,总个数少的时候会选择扩容)。
数组扩容为阈值0.75(为啥不像ArrayList一样满了再扩容,等满了再扩容就要冲突到死了,想象下只剩一个坑位的时候),每次扩容坑位数目变成2倍(ArrayList是1.5倍,因为hashMap有冲突问题,希望更大的空间),初始容量为16。

TreeMap: 基于红黑树的实现,每个键要么本身可比较,要么传入一个comparator对象,专门负责比大小。因为是个二叉搜索树,搜索起来也不算太慢,并且不会像HashMap那样占空间,总的来说就是时间换空间。

HashSet: 由HashMap实现存储,用的时候value被设置为一个Object常量对象(可真会偷懒)。

TreeSet:同样内部使用HashSet实现存储。

你可能感兴趣的:(Java基础)