1、容器

Java中的容器主要有Collection和Map,它们都是顶层接口,都位于java.util包下,实际使用地容器都是基于这两个接口延伸出来的。

java 创建容器对象的类 java容器的构件_线程安全

java 创建容器对象的类 java容器的构件_java 创建容器对象的类_02

java 创建容器对象的类 java容器的构件_java数据结构_03

拓展(数据结构)

java中的容器其实就是数据结构在java中的实现

常用数据结构

  1. 线性表
    连续存储,就是数组
  2. 链表
    分散存储,每个元素为头节点+内容+尾节点
  3. 队列
    双向通,两头开口,一边进一边出
    先进先出

  4. 单向通,一边开口,哪边进哪边出
    先进后出

  5. 二叉树、红黑树
    一个父节点,两个子节点,树形展开

想要详细了解java的数据结构可以参考

(小声bb:这个文章是要钱的,可以去网上再找找有没有其他的归纳的很好的)

2、Collection接口

Collection的继承树

java 创建容器对象的类 java容器的构件_java数据结构_04

Collection接口的类图

java 创建容器对象的类 java容器的构件_线程安全_05

拓展:如何在java中查看继承体系

选中需要查看的类名(或接口名),按Ctrl+T,就会显示如下信息

java 创建容器对象的类 java容器的构件_数组_06

(一)List

是一种已知顺序的有序数据集,可容纳重复的元素。

下面介绍几种常见List

(1)ArrayList(底层数组)

ArrayList是基于动态数组实现的非线程安全的集合(简单理解:所谓动态数组就是数组扩容)

ArrayList实现了List接口,常用操作有add、remove、get、size
其内部的数据采用数组进行存储
因此随机读写的速度很快,但删除、添加等操作相对会消耗比较多的时间,因为会有相关的一系列节点移动

ArrayList内部以数组的形式存储数据,默认的是一个空数组,当添加数据后,会扩充为一个长度为10的数组

(2)Vector(底层数组)

Vector类实现了可扩展的对象数组
像数组一样,它包含可以使用整数索引访问的组件
但是,Vector的大小可以根据需要增长或缩小,以适应在创建Vector之后添加和删除项目

Vector是同步的(线程安全)。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

(3)LinkedList(底层链表)

LinkedList是基于链表实现的非线程安全的集合

双链表实现了List和Deque接口。

请注意,此实现不同步(不安全) 如果多个线程同时访问链接列表,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。

该类定义代源码(可以看出实现了List与Queue,其中Queue的实现是通过实现Deque):

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
(4)CopyOnWriteArrayList

这个类解决了ArrayList多线程读写不安全的问题,这个类可以多线程一边读一边写

实现原理:每次对数组集合的修改会new一个新的数组集合,对新的修改完,再覆盖掉旧的,
在这期间如果有另外的线程需要访问会访问原数组(注意不可以同时修改,线程加锁了的,只限于写的同时读)

该类定义代源码(可以看出实现了List集合):

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

下面是别人解析的官方解释(看不懂的就按我上面的理解吧):

CopyOnWriteArrayList<E>类是ArrayList<E>的线程安全版本

一句话很清楚的说明了CopyOnWriteArrayList<E>的作用,注释中同样指出了实现了原理:
任何改变数组的操作都是在内部数组的一个新的拷贝上进行的。
这样的实现对于写来说代价很大,但如果多线程环境下遍历的操作,读操作远远多于写操作来说就比较高效了。
因此应用的场景就是:在多线程环境下一个读操作远远多于写操作的列表。

线程安全最简单的做法就是同步,那我们从源码的角度来看下ConpyOnWriteArrayList<E>相对于直接在ArrayList<E>上进行操作的同步比较起来的改进之处:写操作在可重入锁的保护下在内部数组的拷贝上进行,操作完成后将内部数组换成最新的数组。这样读操作就不许要进行同步,避免了直接同步ArrayList<E>时对读操作带来的开销

CopyOnWriteArrayList的实现原理
在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。
以下代码是向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList埋添加元素),
可以发现在添加的时候是需要加锁的,则多线程写的时候会Copy出N个副本出来。

(二)Set

是一种数学意义上的集合,最大的特点是不可容纳重复元素。

一个set集合不能包含重复的元素,set实现Collection并增加了hashCode和equals方法
set不允许存储值相同的元素,原因为在取值的时候set依靠的是元素本身取值
由于set提供了hashCode和Equals方法,所以set支持比较

set允许存储一个null值,并且不为空
在存储数据时应该满足:s1.hashCode = s2.hashCode 并且 s1.equals(s2)

set主要实现HashSet,TreeSet,LinkedHashSet

一般不怎么用,了解一下

(三)Queue

队列在java中的实现,与经典数据结构的队列一致。

Queue主要实现LinkedList、PriorityQueue(优先级队列 )

一般不怎么用,了解一下

3、Map接口

下面是Map接口的类图:

java 创建容器对象的类 java容器的构件_数组_07

(1)HashMap:即哈希表,与经典数据结构中哈希表一致,主要用于快速查找

(2)HashTable:与HashMap功能基本一致,不过是线程安全的,但是是锁一整个

(3)SortedMap:经过排序的Map

(4)ConcurrentMap:并发Map,主要用于多线程并发环境下,只锁其中访问的一个元素

4、Java中常用的三大容器(List、Set和Map)区别

List

Set

Map

可以存储重复元素

不可以存储重复元素

键值对方式存储,存储的键不能重复

基于数组或者链表实现

基于Map实现

基于哈希表或者红黑树实现

面试题:

1、集合和数组的区别

  1. 数组长度固定,如果要增删元素特别麻烦
    (注意不要说集合的长度是可以改变的,实现集合的类不同,也不一定)
  2. 集合封装了很多方法,用起来比较方便
  3. 集合只能存放引用类型(基本类型会自动拆装箱为其包装类),数组都可以存放
  4. 集合中可以装不同的类型,数组中只能装相同的类型

2、集合体系的理解

java 创建容器对象的类 java容器的构件_线程安全

java 创建容器对象的类 java容器的构件_java 创建容器对象的类_02

就这个图吧,这个图理解很全了

3、Collection 与 Collections 的区别

首先Collection和Collections是两个完全不同的范畴。正如下图,Collection是所有集合的root接口,而Collections仅仅是一个工具类,它提供了很多静态的工具方法来操作一个集合。

java 创建容器对象的类 java容器的构件_java 创建容器对象的类_10

Collection为集合的接口

Collections为集合的工具类:用来实现功能的,提供静态工具方法操作集合

一般后面加s的都是工具类

常用的Collections方法

  1. 随机列表的方法
    static void shuffle(List<?> list) 使用默认的随机源随机排列指定的列表
    static void shuffle(List<?> list, Random rnd) 使用指定的随机源随机排列指定的列表

4、ArrayList与LinkedList有什么区别

他们的区别体现了数组与链表两种存储结构的区别

  1. ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合
  2. 对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList
    因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止
  3. 新增和删除元素,一般LinkedList的速度要优于ArrayList
    因为ArrayList在新增和删除元素时,可能扩容和复制数组
    LinkedList实例化对象需要时间外,只需要修改指针即可
  4. LinkedList集合不支持 高效的随机随机访问(RandomAccess)
  5. ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

5、ArrayList与Vector有什么区别

  1. 线程安全:ArrayList是线程不安全的
    Vector是线程安全的
  2. ArrayList默认长度为10,里面底层是数组的扩容,每次扩容原来长度的1.5倍
    Vector底层也是数组,但是会扩容到原来长度的2倍
  3. Vector一般不用了,因为效率低

6、HashMap与HashTable有什么区别

  1. HashMap是线程不安全的
    HashTable是线程安全的
  2. HashMap允许null作为key
    HashTable不允许
  3. HashTable被淘汰

7、列举几个线程安全的集合

Vetor

HashTable

ConcurrentHashMap

CopyOnWriteArrayList

8、List与Set的区别

List可重复,Set不可重复

注意:有序无序不作为他们的区别

9、谈一谈CopyOnWriteArrayList

实现了读写分离,即:一个线程在进来写的时候(增删改),另外一个线程也可以进来读(查)

10、说一说HashMap的存储原理,为什么他的效率最高

理解为Python中的字典类型(学过Python的可以这么理解)

包含键值对,通过键来找值

通过键算出hashcode编码,通过这个编码找到值所在的存储位置

11、如何优化HashMap

  1. 在初始化时指定长度,HashMap在元素容量达到3/4时会扩容
    一旦扩容,之前的元素都要全部打乱,效率极低
  2. HashMap中元素存放的位置和Key的HashCode有关,也和HashMap的长度有关
  3. 尽量使用字符串作为Key,字符串的HashCode有缓存,不需要再重新再算HashCode
  4. Key一定要保证Key的HashCode方法和equsls方法一致,避免出现链表

12、谈一谈ConcurrentHashMap

线程安全,但是效率相对于HashTable更高

HashTable是锁整个map元素,ConcurrentHashMap是只锁我们访问的那一个元素

13、如何基于ArrayList自己写一个线程安全的集合

通过继承安全集合