ArrayList

1.动态数组

2.线程不安全

3.存储空间连续

4.查询快,添加删除慢

  • 构造方法
/**
+ Shared empty array instance used for default sized empty instances. We
+ distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
+ first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /**
+ Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

这个构造方法很简单,初始化了一个空的elementData,并没有赋予数组长度

  • 元素添加

/**
+ Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10; /**
+ The array buffer into which the elements of the ArrayList are stored.
+ The capacity of the ArrayList is the length of this array buffer. Any
+ empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
+ will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access /**
+ The size of the ArrayList (the number of elements it contains).
*
+ @serial
*/
private int size; /**
+ Appends the specified element to the end of this list.
*
+ @param e element to be appended to this list
+ @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 首先进行扩充
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将元素追加到最后
elementData[size++] = e;
return true;
} // 扩充
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} // 计算数组大小 第一次调用此处的elementData={},所以返回值为DEFAULT_CAPACITY=10,也就是默认的数组长度是10
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) // 当加上当前元素后的集合长度(size)大于现在数组长度(elementData.length)在进行扩充
grow(minCapacity);
} // 真正的扩充操作
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; // 此处oldCapacity=0
int newCapacity = oldCapacity + (oldCapacity >> 1); // 此处newCapacity=0
if (newCapacity - minCapacity < 0) // 此处minCapacity=10
newCapacity = minCapacity; // 此处newCapacity=10
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); //数组拷贝
}

真正的数组长度是在第一次添加的时候进行初始化的,默认为10

最主要的消耗是在扩容(数组拷贝)

当集合长度大于数组长度的时候进行扩充,扩充的标准是1.5倍[oldCapacity + (oldCapacity >> 1)]

  • 查询
public E get(int index) {
rangeCheck(index);// 校验 return elementData(index);
} E elementData(int index) {
return (E) elementData[index];
}

Vector

1.动态数组,类似于ArrayList

2.线程安全

3.消耗大

  • 构造方法
public Vector() {
this(10); // initialCapacity初始容量
}
  • 元素添加
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}

被synchronized修饰,线程安全,但是效率较低

  • 在指定位置添加元素
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
- " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}

LinkedList

1.双向链表:jdk1.7/8以后

2.插入快,查询慢

  • 构造函数
/**
* Constructs an empty list.
*/
public LinkedList() {
}

空的构造方法

  • 元素添加
public boolean add(E e) {
linkLast(e);
return true;
} void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}

默认添加到链表结尾,prev指向原结尾元素,原结尾元素next指针指向新添加元素,并记录结尾元素为新添加元素。只有指针移动,并没有数组拷贝,所以插入效率较快

  • 查询
public E get(int index) {
checkElementIndex(index);
return node(index).item;
} Node<E> node(int index) {
// assert isElementIndex(index); if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

查询采用二分法查找,先将数组拆分成一半,然后进行遍历。所以查询较慢。当index值接近二分之一size时,更慢。

HashMap

1.存储结构:数组+链表/数组+红黑树

2.线程不安全

  • 构造方法
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

没有初始化数组,负载因子为0.75

  • 添加元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //[1]
n = (tab = resize()).length; // [2]
if ((p = tab[i = (n - 1) & hash]) == null) [// [3]
tab[i] = newNode(hash, key, value, null); // [4]
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))// [5]
e = p; // [6]
else if (p instanceof TreeNode) // [7]
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //[8]
else { //[9]
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { // [10]
p.next = newNode(hash, key, value, null); // [11]
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st [12]
treeifyBin(tab, hash); // [13]
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) // [14]
break;
p = e;
}
}
if (e != null) { // existing mapping for key [15]
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) // [16]
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold) // [17]
resize();
afterNodeInsertion(evict);
return null;
}

[1]判断table是否为null,长度是否为0,table用于扩充时记录扩充后的新数组

[2]进行数组扩充,将新数组赋值给tab,n为新数组的长度

[3]判断新key需要存储的数组节点是否有值

[4]如果没有值,直接存储于该节点,如果当前数组节点有值

[5]判断新key与当前存储的key是否相同

[6]记录当前存储元素到e

[7]判断当前节点是否为数节点

[8]进行树节点操作

[9]当前节点存储的key与新key不同,并且不是树形结构(链表结构)

[10]循环遍历,找到链表的尾节点

[11]将新元素追加到链表的末尾,即原尾节点的next指针指向新元素

[12]当链表的长度达到8时,转为树形结构[13]

[14]循环过程中如果发现存储的key与新key相同,则中断循环

[15]如果存在匹配的key,则替换value

[16]返回旧值

[17]能走到这里说明是新增元素,并不是更新元素,判断当前集合长度是否大于threshold(threshold=当前集合长度*0.75),如果大于需要进行扩充

// 扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; // [1]
int oldCap = (oldTab == null) ? 0 : oldTab.length; //[2]
int oldThr = threshold; // [3]
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { // [4]
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) // [5]
newThr = oldThr << 1;
}
else if (oldThr > 0) // [6]
newCap = oldThr;
else { // [7]
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) { // [8]
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // [9]
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; // [10]
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {// [11]
Node<K,V> e;
if ((e = oldTab[j]) != null) {// [12]
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode) // [13]
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order [14]
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

[1]oldTab用来记录上次扩充的table

[2]oldCap用来记录上次扩充table的长度

[3]oldThr用来记录上次扩充的阈值

[4]如果oldCap大于等于最大值(230),threshold等于230-1,直接返回,不在进行扩充

[5]newCap等于(oldCap*2),如果newCap小于最大值(230)并且oldCap大于初始值(24),则newThr=oldThr*2

[6]如果oldThr大于0,则newCap等于oldThr,上次扩充的阈值

[7]如果oldCap和oldThr都不大于0,则newCap等于24,newThr等于24*0.75(首次扩充)

[8]当oldCap小于2^4的时,newThr等于0,newThr=2*oldCap*0.75

[9]threshold等于newThr,记录下次需要扩充的阈值

[10]创建新的Node数字,长度为newCap

[11]如果oldTab不为空,则遍历这个数组

[12]将原数组的元素散列到新数组中

[13]以红黑树的结构重新散列元素

[14]以链表的结构重新散列元素

  • get方法,先根据key计算出对应的数组指针位置,然后遍历链表或者红黑树获取相同key的元素
Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<String, Integer> next = entryIterator.next();
System.out.println("key=" + next.getKey() + " value=" + next.getValue());
}
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key=" + key + " value=" + map.get(key)); }
map.forEach((key,value)->{
System.out.println("key=" + key + " value=" + value);
});

hashmap只能在单线程中使用,并尽量减少扩容,循环链表的时间复杂度是O(n),O(logn)

多线程场景下推荐使用ConcurrentHashMap

ConcurrentHashMap

Object put(Object key, int hash, Object value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry first = tab[index];
HashEntry e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next; Object oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}

ConcurrentHashMap之所以是线程安全的是因为在添加元素的时候先上了一个锁,操作完成在解锁。

HashSet

1.hashmap存储数据

2.不允许存储重复元素的集合

  • 构造方法
public HashSet() {
map = new HashMap<>();
}
  • 添加元素
private static final Object PRESENT = new Object();

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

此方法将添加的元素e作为hashmap的key,value都是相同的PRESENT,因为hashmap的key是不允许重复的,所以相同的元素添加进来,后添加的会覆盖先添加的,这就是不允许重复的原因

java常用集合浅层解析-面试必备的更多相关文章

  1. JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

    文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...

  2. Java常用集合笔记

    最近事情比较少,闲暇之余温习巩固一下Java的一些基础知识,并做一些笔记, Java常用集合, 主要参考的这篇文章:Java常用集合 ArrayList/Vertor 1. ArrayList 的主要 ...

  3. Java 最常见 200+ 面试题答案全解析-面试必备

    本文分为十九个模块,分别是: Java 基础.容器.多线程.反射.对象拷贝.Java Web .异常.网络.设计模式.Spring/Spring MVC.Spring Boot/Spring Clou ...

  4. Java提高班(四)面试必备—你不知道的数据集合

    导读:Map竟然不属于Java集合框架的子集?队列也和List一样属于集合的三大子集之一?更有队列的正确使用姿势,一起来看吧! Java中的集合通常指的是Collection下的三个集合框架List. ...

  5. 面试突击(五)——Java常用集合

    为了勾起回忆,我画了一个常用集合类的结构关系图,话不多说,详见下图: 实际开发中ArrayList/HashMap/HashSet是三种最常用的集合工具类,通过其结构关系图也能清晰的了解他们的特性,所 ...

  6. 干货,阿里P8浅谈对java线程池的理解(面试必备)

    线程池的概念 线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成:ThreadPoolExecutor ...

  7. JAVA常用集合解析

    ArrayList : 底层基于数组实现,在使用add方法添加元素时,首先校验集合容量,将新添加的值放入集合尾部并进行长度加一,进行自动扩容,扩容的操作时将数据中的所有元素复制到新的集合中. 在指定位 ...

  8. JAVA 常用集合接口List、Set、Map总结

    java中频繁使用List.Set.Map接口,将其总结如下 它们的继承与实现关系如下: Collection├List│├LinkedList│├ArrayList│└Vector│ └Stack└ ...

  9. java常用集合详解 contains

    java集合是对常用数据集合的封装,差不多就是数组吧,验证某个元素是否在数据集合里,最原始的方法是,用个循环,"某个元素"与数据集合中的每个元素逐个进行比较. java 对常用的一 ...

随机推荐

  1. C语言博客作业3--函数

    C语言博客作业3--函数 1.本章学习总结 1.1思维导图 请以思维导图总结本周的学习内容,如下图所示: 1.2本章学习体会及代码量学习体会 1.2.1学习体会 描述本周学习感受,也可以在这里提出你不 ...

  2. [持续交付实践] Jenkins Pipeline 高可用设计方法

    前言 这篇写好一段时间了,一直也没发布上来,今天稍微整理下了交下作业,部分内容偷懒引用了一些别人的内容.使用Jenkins做持续集成/持续交付,当业务达到一定规模的时候,Jenkins本身就很容易成为 ...

  3. 2018-2019-2 20165205《网络对抗技术》Exp4 恶意代码分析

    2018-2019-2 20165205<网络对抗技术>Exp4 恶意代码分析 实验要求 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或Exp3中 ...

  4. 【学习】数据聚合和分组运算【groupby】

    分组键可以有多种方式,且类型不必相同 列表或数组, 某长度与待分组的轴一样 表示DataFrame某个列名的值 字典或Series,给出待分组轴上的值与分组名之间的对应关系 函数用于处理轴索引或索引中 ...

  5. Python第10天

    装饰器:本质上是函数,为其他函数添加附件功能. 装饰器 = 高阶函数 + 函数嵌套 + 闭包 原则(开放封闭原则):1,不修改被修饰函数代码.2,不修改被修饰函数调用方式. @方法名

  6. as3 加载进来的swf进行缩放

    private function swfComHdl(_loader:Loader, _data:*= null){ frameLder = _loader; gameMC = frameLder.c ...

  7. 函数(一.return)

    #return的用法:#return用于退出函数,选择性的返回一个表达式.def test1(a,b): total = a+b return total print(total) def test2 ...

  8. Linux安装OPENJDK配置环境变量

    # yum  search  openjdk  回车 找到open开发者环境jdk版本复制它的名字 安装java-1.8.0-openjdk-headless.x86_64 yum install j ...

  9. vue 动态添加 <style> 样式 vue动态添加 绑定自定义字体样式

    created(){ //动态添加自定义字体样式 let style = document.createElement('style'); style.type = "text/css&qu ...

  10. jQuery中删除方法empty(),remove()和detach()的区别

    empty():清空匹配的元素集合中所有的子节点,自身节点和事件都未被删除. remove():这个方法不会把匹配的元素从jQuery对象中删除,因而可以在将来再使用这些匹配的元素.但除了这个元素本身 ...