容器

容器的组成

容器有两个接口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. Vip视频解析端口

    ------------恢复内容开始------------ 本教程仅供学习交流使用,请不要用于商业用途,支持正版,人人有责 我们怎么免费看VIP视频呢?一个简单的方法,就是通过解析接口 VIP视频解 ...

  2. 远程分支git换地址了,本地重新关联

    由于本人把github远程仓库的名字修改了所以做了以下步骤修改 步骤:两步 (1)先把之前关联的git清除掉 git remote rm origin (2)再关联新的地址 git remote ad ...

  3. 简述MySQL优化

    数据库的优化可以从四个方面来优化: 1.结构层: web服务器采用负载均衡服务器,mysql服务器采用主从复制,读写分离 2.储存层: 采用合适的存储引擎,采用三范式 3.设计层: 采用分区分表,索引 ...

  4. 在Visual Studio 中使用git——文件管理-上(四)

    在Visual Studio 中使用git--什么是Git(一) 在Visual Studio 中使用git--给Visual Studio安装 git插件(二) 在Visual Studio 中使用 ...

  5. 如何实现一个 System Services?

    <Android 系统开发做什么?>写到 Android System Services 是专注于特定功能的模块化组件,应用框架 API 所提供的功能可与系统服务通信,以访问底层硬件.An ...

  6. webpack 快速入门 系列 —— 实战一

    实战一 准备本篇的环境 虽然可以仅展示核心代码,但笔者认为在一个完整的环境中边看边做,举一反三,效果更佳. 这里的环境其实就是初步认识 webpack一文完整的示例,包含 webpack.devSer ...

  7. windows的SEH异常处理以及顶层异常处理

    前言 windows的SEH结构化异常处理是基于线程的,传统的SEH结构化异常会基于堆栈形成一条包含异常回调函数地址的链(SEH链).而fs:[0](TEB的第一个字段)指向这条链的链头,当有异常发生 ...

  8. eth-trunk

    ------------恢复内容开始------------ 1.eth-trunk 是什么 *链路 聚合技术 2.做什么用的 *作为一种链路捆绑技术,可以把多个独立物理接口绑定在一起,作为一个大带宽 ...

  9. [并发编程 - 多线程:信号量、死锁与递归锁、时间Event、定时器Timer、线程队列、GIL锁]

    [并发编程 - 多线程:信号量.死锁与递归锁.时间Event.定时器Timer.线程队列.GIL锁] 信号量 信号量Semaphore:管理一个内置的计数器 每当调用acquire()时内置计数器-1 ...

  10. Pytorch多卡训练

    前一篇博客利用Pytorch手动实现了LeNet-5,因为在训练的时候,机器上的两张卡只用到了一张,所以就想怎么同时利用起两张显卡来训练我们的网络,当然LeNet这种层数比较低而且用到的数据集比较少的 ...