容器

容器的组成

容器有两个接口Map和Collection。

collection接口有List类和set类。

List类可以分为:Vector、LinkedList、ArrayList、CopyOnWriteArrayList

Set类可以分为:HashSet、LinkedHashSet、TreeSet

Map接口拥有:HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap

结论:

  1. 如果是集合类型,有List和Set供我们选择。List的特点是插入有序的,元素是可重复的。Set的特点是插入无序的,元素不可重复的。
  2. 如果是key-value型,就可以选择Map。如果要保持插入顺序,则可以选择LinkedHashMap,如果不需要则选择HashMap,如果要排序则选择TreeMap。

选择什么样的容器来存储对象,关键在于了解每一个常用集合类的数据结构!

容器的初步了解

List

List集合基础

  • 实现了Collection接口
  • 特性:有序的,元素可重复的
  • 允许元素为null

List常用的子类

  • Vector

底层结构是数组,初始容量是10,每次增长2倍。

它是线程同步的,已被ArrayList代替。

  • LinkedList

底层结构是双向链表

实现了Deque接口,因此可以向操作栈和队列一样操作它。

线程非同步。

  • ArrayList

底层结构是数组,初始容量为10,每次增长1.5倍。

在增删的时候,需要数组的拷贝复制。

线程非同步。

  • CopyOnWriteArrayList

原理:在修改时,复制出一个新数组,修改的操作在新数组中完成,最后将新数组交由array变量指向。

写加锁,读不加锁。

缺点: CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

适合在读多写少的场景中使用。

Set

Set集合基础

  • 实现了Collection接口
  • 特性:无序的,元素不可重复
  • 底层大多数是Map结构的实现
  • 常用的三个子类都是非同步的

Set常用的子类

  • HashSet

底层数据结构是哈希表(是一个元素为链表的数组) + 红黑树。

实际上就是封装了HashMap。

元素无序,可以为null。

  • LinkedHashSet

底层数据结构由哈希表和双向链表组成。

父类是HashSet。

实际上就是LinkHashMap。

元素可以为null。

  • TreeSet

底层实际上是一个TreeMap实例(红黑树)。

可以实现排序的功能。

元素不能为null。

Map

Map基础知识

  • 存储的结构是key-value键值对,不像Collection是单列集合
  • 需要先了解一下散列表和红黑树

Map常用的子类

  • HashMap

底层是散列表 + 红黑树。

初始容量为16, 装载因子为0.75,每次扩容2倍。

允许为null,存储无序。

非同步。

散列表容量大于64且链表大于8时,转为红黑树。

Key的哈希值会与该值的高16位做异或操作,进一步增加随机性。

当散列表的元素大于容量 * 装载因子时,会再散列,每次扩容2倍。

如果hashCode相同,key不同则替换元素,否则就是散列冲突。

  • LinkedHashMap

底层是散列表 + 红黑树 + 双向链表,父类是HashMap。

允许为null,插入有序。

非同步。

提供插入顺序和访问顺序两种,访问顺序是符合LRU算法,一般用于扩展(默认是插入排序)

迭代与初始容量无关(迭代的是维护的双向链表)

大多使用HashMap的API,只不过在内部重写了某些方法,维护了双向链表。

  • TreeMap

底层是红黑树,保证了时间复杂度为log(n)。

可以对其进行排序,使用Comparator或者Comparable。

只要compare或者CompareTo认定该元素相等,那就相等。

非同步。

自然排序(手动排序),元素不能为null。

  • ConcurrentHashMap

底层是散列表 + 红黑树,支持高并发操作。

key和value都不能为空。

线程是安全的,利用CAS算法和部分操作上锁实现。

get方法是非阻塞,无锁的。重写Node类,通过volatile修饰next来实现每次获取都是最新设置的值。

在高并发环境下,统计数据(如计算size等)其实是无意义的,因为在下一个时刻size值就变化了。

Collection的功能

1.添加功能

boolean add(Object obj):添加一个元素
boolean addAll(Collection c): 添加一个集合的元素

2.删除功能:

void clear(): 移除所有元素
boolean remove(Object obj): 移除一个元素
boolean removeAll(Collection c): 移除一个集合的元素,只要一个元素被移除了就返回true。

3.判断功能

boolean contain(Object obj): 判断集合是否包含该元素
boolean containsAll(Collection c): 判断集合中是否包含指定的集合元素,只有包含所有元素才叫包含。
boolean isEmpty(): 判断集合是否为空

4.获取功能

Iterator<E> iterator(): 迭代器

5.长度功能

int size(): 元素的个数

6.交集功能

boolean retainAll(Collection c): 移除次collection中未包含在指定collection中的所有元素。集合A和集合B做交集,最终的结果保存在集合A,返回值表示的是A是否发生过变化。

迭代器Iterator

Iterator实际上就是在遍历集合。

遍历集合的步骤:

  1. 通过结合对象获取迭代器对象
  2. 通过迭代器对象的hasNext()方法判断是否有元素
  3. 通过迭代器对象的next()方法获取元素并移动到下一个位置

我们有一个集合:Collection c = new ArrayList();

给集合添加元素:c.add("hello"); c.add("world"); c.add("java");

通过集合获取迭代器对象:Iterator it = c.iterator();

while(it.hashNext()){
String s = (String)it.next();
System.out.println(s);
}

ArrayList解析

add方法

1.add(E e)

首先去检查一下数组的容量是否足够:

  • 足够:直接添加
  • 不足够:扩容:
    • 扩容到原来的1.5倍
    • 第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。

2.add(int index, E e)

  • 检查角标
  • 空间检查,如果有需要进行扩容
  • 插入元素

get方法

get(int index)

  • 检查角标
  • 返回元素

set方法

set(int index, E e)

  • 检查角标
  • 替代元素
  • 返回旧值

remove方法

remove(int index)

  • 检查角标
  • 删除元素
  • 计算出需要移动的个数,并移动
  • 设置为null,让Gc回收

总结:

  • ArrayList是基于动态数组实现的,在增删时候,需要数组的拷贝复制。
  • ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
  • 删除元素时不会减少容量,若希望减少容量则调用trimToSize()
  • 它不是线程安全的。它能存放null值。

Vector与ArrayList

  • Vector底层也是数组,与ArrayList最大的区别就是:同步(线程安全)

    Vector。
  • 在要求非同步的情况下,我们一般都是使用ArrayList来替代Vector的了。
  • 如果想要ArrayList实现同步,可以使用Collections的方法: List list = Collections.synchronizedList(new ArrayList(...));
  • Vector扩展2倍

LinkedList解析

底层是双向链表

方法的一些细节

  • add方法实际上就是往链表最后添加元素
  • remove方法实际上就是用equals看看这两个元素是否在里面

  • get方法查看下标,如果下标小于长度的一半就从头遍历,否则从尾遍历
  • set方法和get方法其实差不多,根据下标来判断是从头遍历还是从尾遍历

List总结:

  • ArrayList:

底层实现是数组

ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍

在增删时候,需要数组的拷贝复制(navite 方法由C/C++实现)

  • LinkedList:

底层实现是双向链表[双向链表方便实现往前遍历]

  • Vector:

底层是数组,现在已少用,被ArrayList替代,原因有两个:

Vector所有方法都是同步,有性能损失。

Vector初始length是10,超过length时,以100%比率增长,相比于ArrayList更多消耗内存。

总的来说:查询多用ArrayList,增删多用LinkedList。

Map的功能

1.添加功能

v put(K key, V value):添加元素
  • 如果键是第一次存储,就直接存储,返回null
  • 如果键不是第一次存储,就用值把它以前的值替换掉,返回以前的值

2.删除功能

void clear(): 移除所有的键值对元素
v remove(Object key): 根据键删除值,并把值返回

3.判断功能

boolean containsKey(Object key):判断集合是否包含指定的键
boolean containsValue(Object value):判断集合是否包含指定的值
boolean isEmpty(): 判断集合是否为空

4.获取功能

Set<Map.Empty<K key, V value>> entrySet():返回的是键值对对象的集合
v get(Object key): 根据键获取值
Set<K> keySet():获取集合中所有的键的集合
Collection<V> values(): 获取集合汇总所有的值的集合

5.长度功能

int size(): 返回集合汇总键值对的对数

散列表

首先我们回顾下数据和链表:

链表和数组都可以按照人们的意愿来排列元素的次序,他们可以说是有序的(存储的顺序和取出的顺序是一致的)。但同时,这会带来缺点:想要获取某个元素,就要访问所有的元素,直到找到为止。这会让我们消耗很多的时间在里边,遍历访问元素。

而还有另外的一些存储结构:不在意元素的顺序,能够快速的查找元素的数据

其中就有一种非常常见的:散列表

散列表为每个对象计算出一个整数,称为散列码。根据这些计算出来的整数(散列码)保存在对应的位置上!

在Java中,散列表用的是链表数组实现的,每个列表称之为桶。一个桶上可能会遇到被占⽤的情况(hashCode散列码相同,就存储在同一个位置上),这种情况是无法避免的,这种现象称之为:散列冲突。

  • 此时需要用该对象与桶上的对象进行比较,看看该对象是否存在桶上了——如果存在,就不添加了,如果不存在则添加到桶上
  • 当然了,如果hashcode函数设计得足够好,桶的数目也足够,这种比较是很少的
  • 在JDK1.8中,桶满时会从链表变成平衡二叉树

如果散列表太满,是需要对散列表再散列,创建一个桶数更多的散列表,并将原有的元素插入到新表中,丢弃原来的表:

  • 装填因子(load factor)决定了何时对散列表再散列
  • 装填因子默认为0.75,如果表中超过了75%的位置已经填入了元素,那么这个表就会用双倍的桶数自动进行再散列

HashMap

总结:

  • 在JDK8中HashMap的底层是:数组+链表(散列表)+红黑树
  • 在散列表中有装载因子这么一个属性,当装载因子*初始容量小于散列表元素时,该散列表会再散列,扩容2倍!
  • 装载因子的默认值是0.75,无论是初始大了还是初始小了对我们HashMap的性能都不好
    • 装载因子初始值大了,可以减少散列表再散列(扩容的次数),但同时会导致散列冲突的可能性变大(散列冲突也是耗性能的操作,得操作链表(红黑树)!
    • 装载因子初始值小了,可以减小散列冲突的可能性,但同时扩容的次数可能就会变多!
  • 初始容量的默认值是16,它也一样,无论初始大了还是小了,对我们的HashMap都是有影响的:
    • 初始容量过大,那么遍历时我们的速度就会受影响
    • 初始容量过小,散列表再散列(扩容的次数)可能就变得多,扩容也是一件非常耗费性能的事
  • 从源码上我们可以发现:HashMap并不是直接拿key的哈希值来用的,它会将key的哈希值的高16位进行异或操作,使得我们将元素放入哈希表的时候增加一定的随机性。
  • 还要值得注意的是:并不是桶上有8位元素的时候它就能变成红黑树,它得同时满足我们的散列表容量大于64才行。

TreeMap

  • TreeMap实现了NavigableMap接口,而NavigableMap接口继承着SortedMap接口,致使我们的TreeMap是有序的!
  • TreeMap底层是红黑树,它方法的时间复杂度都不会太高:log(n)
  • 非同步
  • 使用Comparator或者Comparable来比较key是否相等与排序的问题

注意:

  1. TreeMap有序是通过Comparator来进行比较的,如果comparator为null,那么就使用自然顺序。
  2. key值不能为null。

总结:

TreeMap底层是红黑树,能够实现该Map集合有序。如果在构造方法中传递了Comparator对象,那么就会以Comparator对象的方法进行比较。否则,则使

用Comparable的compareTo(T o)方法来比较。值得说明的是:

  • 如果使用的是compareTo(T o)方法来比较,key一定是不能为null,并且得实现了Comparable接口。
  • 即使是传入了Comparator对象,不用compareTo(T o)方法来比较,key也是不能为null的。

要点:

  1. 由于底层是红黑树,那么时间复杂度可以保证为log(n)
  2. key不能为null,为null为抛出NullPointException的
  3. 想要自定义比较,在构造方法中传入Comparator对象,否则使用key的自然排序来进行比较
  4. TreeMap非同步,想要同步可以使用Collections来封装

Set集合总结

  • HashSet:

    无序,允许为null,底层是HashMap(散列表+红黑树),非线程同步
  • TreeSet:

    有序,不允许为null,底层是TreeMap(红黑树),非线程同步
  • LinkedHashSet:

    迭代有序,允许为null,底层是HashMap+双向链表,非线程同步

java容器学习笔记的更多相关文章

  1. java—容器学习笔记

    一:迭代器 刚开始学容器,做了个简单的练习题.. import java.util.ArrayList; import java.util.Collection; import java.util.I ...

  2. 尚学堂JAVA基础学习笔记

    目录 尚学堂JAVA基础学习笔记 写在前面 第1章 JAVA入门 第2章 数据类型和运算符 第3章 控制语句 第4章 Java面向对象基础 1. 面向对象基础 2. 面向对象的内存分析 3. 构造方法 ...

  3. Java Web学习笔记之---JSP

    Java Web学习笔记之---JSP (一)JSP常用语法 (1)HTML注释 <!--所要注释的内容 --> 在客户端显示一个注释. (2)隐藏注释 <%--所要注释的内容--% ...

  4. 20145213《Java程序设计学习笔记》第六周学习总结

    20145213<Java程序设计学习笔记>第六周学习总结 说在前面的话 上篇博客中娄老师指出我因为数据结构基础薄弱,才导致对第九章内容浅尝遏止地认知.在这里我还要自我批评一下,其实我事后 ...

  5. [原创]java WEB学习笔记95:Hibernate 目录

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  6. java JDK8 学习笔记——助教学习博客汇总

    java JDK8 学习笔记——助教学习博客汇总 1-6章 (by肖昱) Java学习笔记第一章——Java平台概论 Java学习笔记第二章——从JDK到IDEJava学习笔记第三章——基础语法Jav ...

  7. java JDK8 学习笔记——第16章 整合数据库

    第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程 ...

  8. [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  9. [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

随机推荐

  1. 4.PHP正则表达式与数组

    PHP正则表达式相关 行定位符 开头 ^tm 结尾 tm$ 不限制 tm 单词定界符 \btm\b   单词tm,如果想取反的话就是大写的 \Btm\B 或的关系,[Tt][Mm] 可以表达 tm T ...

  2. 论文解读丨基于局部特征保留的图卷积神经网络架构(LPD-GCN)

    摘要:本文提出一种基于局部特征保留的图卷积网络架构,与最新的对比算法相比,该方法在多个数据集上的图分类性能得到大幅度提升,泛化性能也得到了改善. 本文分享自华为云社区<论文解读:基于局部特征保留 ...

  3. XCTF-supersqli

    supersqli 进来有个输入框,看内容应该是var_dump了sql查询结果 单引号有报错,万能语句能用,注释符#没被ban 打了个union select,给提示ban了一堆关键字,而且忽略大小 ...

  4. 3-5年以上的Android原生开发如何深入进阶?高级工程师必须要掌握哪些?

    前言 曾听过很多人说Android学习很简单,做个App就上手了,工作机会多,毕业后也比较容易找工作.这种观点可能是很多Android开发者最开始入行的原因之一. 在工作初期,工作主要是按照业务需求实 ...

  5. Redis数据结构—链表与字典的结构

    目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...

  6. Linux系统调用表

    Linux系统调用表 记录下来,免得到处找 32位 int 0x80 %eax Name Source %ebx %ecx %edx %esx %edi 1 sys_exit kernel/exit. ...

  7. 记录数据库被攻击.md

    昨天的数据库还是正常的,早上想连接mysql,一直报错1045,最后才发现数据库被攻击了 navicat连接mysql疯狂报错1045 因为1045的报错,一般都是密码设置的问题,但是我怎么修改也没有 ...

  8. layui select 动态赋值

    出现问题 赋值完成后页面不显示,没有效果 发现问题 赋值完成后需要重新渲染select 解决问题 form.render('select');

  9. vue中v-if与v-show的区别以及使用场景

    区别 1.手段:v-if是通过控制dom节点的存在与否来控制元素的显隐:v-show是通过设置DOM元素的display样式,block为显示,none为隐藏: 2.编译过程:v-if切换有一个局部编 ...

  10. 一句 Task.Result 就死锁, 这代码还怎么写?

    一:背景 1. 讲故事 前些天把 .NET 高级调试 方面的文章索引到 github 的过程中,发现了一个有意思的评论,详见 文章,截图如下: 大概就是说在 Winform 的主线程下执行 Task. ...