1.常见的数据结构介绍
https://www.jianshu.com/p/230e6fde9c75(转)
2.常见的5种并发集合
1、常用的五种并发包
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- ArrayBlockingQueue
- LinkedBlockingQueue
2、ConcurrentHashMap
- 线程安全的HashMap的实现
- 数据结构:一个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable(一个HashEntry[])
- 扩容的话,只需要扩自己的Segment而非整个table扩容
- key与value均不可以为null,而hashMap可以
- 向map添加元素
- 根据key获取key.hashCode的hash值
- 根据hash值算出将要插入的Segment
- 根据hash值与Segment中的HashEntry的容量-1按位与获取将要插入的HashEntry的index
- 若HashEntry[index]中的HashEntry链表有与插入元素相同的key和hash值,根据onlyIfAbsent决定是否替换旧值
- 若没有相同的key和hash,直接返回将新节点插入链头,原来的头节点设为新节点的next(采用的方式与HashMap一致,都是HashEntry替换的方法)
- ConcurrentHashMap基于concurrencyLevel划分出多个Segment来存储key-value,这样的话put的时候只锁住当前的Segment,可以避免put的时候锁住整个map,从而减少了并发时的阻塞现象
- 从map中获取元素
- 根据key获取key.hashCode的hash值
- 根据hash值与找到相应的Segment
- 根据hash值与Segment中的HashEntry的容量-1按位与获取HashEntry的index
- 遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e
- 如没找到e,返回null
- 如找到e,获取e.value
- 如果e.value!=null,直接返回
- 如果e.value==null,则先加锁,等并发的put操作将value设置成功后,再返回value值
- 对于get操作而言,基本没有锁,只有当找到了e且e.value等于null,有可能是当下的这个HashEntry刚刚被创建,value属性还没有设置成功,这时候我们读到是该HashEntry的value的默认值null,所以这里加锁,等待put结束后,返回value值
- 加锁情况(分段锁):
- put
- get中找到了hash与key都与指定参数相同的HashEntry,但是value==null的情况
- remove
- size():三次尝试后,还未成功,遍历所有Segment,分别加锁(即建立全局锁)
3、CopyOnWriteArrayList
- 线程安全且在读操作时无锁的ArrayList
- 采用的模式就是"CopyOnWrite"(即写操作-->包括增加、删除,使用复制完成)
- 底层数据结构是一个Object[],初始容量为0,之后每增加一个元素,容量+1,数组复制一遍
- 遍历的只是全局数组的一个副本,即使全局数组发生了增删改变化,副本也不会变化,所以不会发生并发异常。但是,可能在遍历的过程中读到一些刚刚被删除的对象
- 增删改上锁、读不上锁
- 读多写少且脏数据影响不大的并发情况下,选择CopyOnWriteArrayList
4、CopyOnWriteArraySet
- 基于CopyOnWriteArrayList,不添加重复元素
5、ArrayBlockingQueue
- 基于数组、先进先出、线程安全,可实现指定时间的阻塞读写,并且容量可以限制
- 组成:一个对象数组+1把锁ReentrantLock+2个条件Condition
- 三种入队对比
- offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false-->不阻塞
- put(E e):如果队列满了,一直阻塞,直到数组不满了或者线程被中断-->阻塞
- offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果数组已满,则进入等待,直到出现以下三种情况:-->阻塞
- 三种出对对比
- poll():如果没有元素,直接返回null;如果有元素,出队
- take():如果队列空了,一直阻塞,直到数组不为空或者线程被中断-->阻塞
- poll(long timeout, TimeUnit unit):如果数组不空,出队;如果数组已空且已经超时,返回null;如果数组已空且时间未超时,则进入等待,直到出现以下三种情况:
- 需要注意的是,数组是一个必须指定长度的数组,在整个过程中,数组的长度不变,队头随着出入队操作一直循环后移
- 锁的形式有公平与非公平两种
- 在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高
6、LinkedBlockingQueue
- 基于链表实现,读写各用一把锁,在高并发读写操作都多的情况下,性能优于ArrayBlockingQueue
- 组成一个链表+两把锁+两个条件
- 默认容量为整数最大值,可以看做没有容量限制
- 三种入队与三种出队与上边完全一样,只是由于LinkedBlockingQueue的的容量无限,在入队过程中,没有阻塞等待
3.列举java的集合以及集合之间的继承关系?
https://blog.csdn.net/snow_7/article/details/51791112(转)
4.集合类以及集合框架?
https://blog.csdn.net/u012152619/article/details/42673689(转)
5.List,Set,Map的区别
https://blog.csdn.net/ABBuggy/article/details/7720666(转)
6.List和Map的实现方式以及存储方式
List和Set和Map的实现方式以及存储方式?
List常用实现方式有:ArrayList和LinkedList
ArrayList 的存储方式:数组,查询快
LinkedList的存储方式:链表,插入,删除快
Set常用实现方式有:HashSet和TreeSet
HashSet的存储方式:哈希码算法,加入的对象需要实现hashcode()方法,快速查找元素
TreeSet的存储方式:按序存放,想要有序就要实现Comparable接口
附加:
集合框架提供了2个实用类:collections(排序,复制、查找)和Arrays对数组进行(排序,复制、查找)
Map常用实现方式有:HashMap和TreeMap
HashMap的存储方式:哈希码算法,快速查找键值
TreeMap存储方式:对键按序存放
具体:
1:集合
Collection(单列集合)
List(有序,可重复)
ArrayList
底层数据结构是数组,查询快,增删慢
线程不安全,效率高
Vector
底层数据结构是数组,查询快,增删慢
线程安全,效率低
LinkedList
底层数据结构是链表,查询慢,增删快
线程不安全,效率高
Set(无序,唯一)
HashSet
底层数据结构是哈希表。
哈希表依赖两个方法:hashCode()和equals()
执行顺序:
首先判断hashCode()值是否相同
是:继续执行equals(),看其返回值
是true:说明元素重复,不添加
是false:就直接添加到集合
否:就直接添加到集合
最终:
自动生成hashCode()和equals()即可
LinkedHashSet
底层数据结构由链表和哈希表组成。
由链表保证元素有序。
由哈希表保证元素唯一。
TreeSet
底层数据结构是红黑树。(是一种自平衡的二叉树)
如何保证元素唯一性呢
根据比较的返回值是否是0来决定
如何保证元素的排序呢
两种方式
自然排序(元素具备比较性)
让元素所属的类实现Comparable接口
比较器排序(集合具备比较性)
让集合接收一个Comparator的实现类对象
Map(双列集合)
A:Map集合的数据结构仅仅针对键有效,与值无关。
B:存储的是键值对形式的元素,键唯一,值可重复。
HashMap
底层数据结构是哈希表。线程不安全,效率高
哈希表依赖两个方法:hashCode()和equals()
执行顺序:
首先判断hashCode()值是否相同
是:继续执行equals(),看其返回值
是true:说明元素重复,不添加
是false:就直接添加到集合
否:就直接添加到集合
最终:
自动生成hashCode()和equals()即可
LinkedHashMap
底层数据结构由链表和哈希表组成。
由链表保证元素有序。
由哈希表保证元素唯一。
Hashtable
底层数据结构是哈希表。线程安全,效率低
哈希表依赖两个方法:hashCode()和equals()
执行顺序:
首先判断hashCode()值是否相同
是:继续执行equals(),看其返回值
是true:说明元素重复,不添加
是false:就直接添加到集合
否:就直接添加到集合
最终:
自动生成hashCode()和equals()即可
TreeMap
底层数据结构是红黑树。(是一种自平衡的二叉树)
如何保证元素唯一性呢
根据比较的返回值是否是0来决定
如何保证元素的排序呢
两种方式
自然排序(元素具备比较性)
让元素所属的类实现Comparable接口
比较器排序(集合具备比较性)
让集合接收一个Comparator的实现类对象
2.关于集合选取原则
是否是键值对象形式:
是:Map
键是否需要排序:
是:TreeMap
否:HashMap
不知道,就使用HashMap。
否:Collection
元素是否唯一:
是:Set
元素是否需要排序:
是:TreeSet
否:HashSet
不知道,就使用HashSet
否:List
要安全吗:
是:Vector
否:ArrayList或者LinkedList
增删多:LinkedList
查询多:ArrayList
不知道,就使用ArrayList
不知道,就使用ArrayList
3:集合的常见方法及遍历方式
Collection:
add()
remove()
contains()
iterator()
size()
遍历:
增强for
迭代器
|--List
get()
遍历:
普通for
|--Set
Map:
put()
remove()
containskey(),containsValue()
keySet()
get()
value()
entrySet()
size()
遍历:
根据键找值
根据键值对对象分别找键和值
7.
ArrayList和LinkedList的区别,以及应用场景
https://www.cnblogs.com/EasonJim/p/7967138.html(转)
8.数组和链表的区别
数组静态分配内存,链表动态分配内存;
数组在内存中连续,链表不连续;
数组元素在栈区,链表元素在堆区;
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
9.二叉树的深度优先遍历和广度优先遍历的具体实现
https://www.jianshu.com/p/473090b9490d(转)
10.堆的实现
https://blog.csdn.net/tuke_tuke/article/details/50357939(转)
11.堆和栈的区别
http://www.cleey.com/blog/single/id/776.html(转)
12.堆和树的区别
以小根堆为例,堆的特点是双亲结点的关键字必然小于等于孩子结点的关键字,而两个孩子结点的关键字没有次序规定
而二叉排序树中,每个双亲结点的关键字均大于左子树结点的关键字,均小于右子树j结点的关键字,也就是说,每个双亲结点的左右孩子的关键字有次序关系
这样,当对两种树执行中序遍历后,二叉排序树会得到一个有序的序列,而堆不一定。
堆是一种特殊的树,它每个结点都有一个值,堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。就类似一堆东西一样,按照由大到小(或由小到大)“堆”起来。
堆是一种逻辑结构,树是一种存储结构,两者是不同层面的东西,就像“中国人"和“成年人”,本来就不矛盾。
heap一词反映了一种上小下大的金字塔状特征。
heap和tree结合,生了个孩子叫treap
中文名叫树堆。
首先它每个节点有2个值value和weight
其中只看weight的话,满足heap二叉堆的特性(父亲比儿子都小/大),只看value的话,满足排序二叉树特性(以左儿子为根的子树元素都比父亲小,右儿子为根的子树都比父亲大)
value是要维护的值,weight是随机生成的值。由于随机生成的堆使整棵treap变得平衡(严格证明请谷歌百度~),所以treap是一种比较短小精悍的平衡树的实现~
废话结束,回归题目(莫名押韵)
只要无环无向联通图都叫树,具体就是n个点n-1条无向边连接且任意两点联通的一种拓扑结构
如果我们选定一个节点作为根,那么这棵树就是有根树,遍历一遍就可以确定所有的父亲-儿子的关系了。。。
如果一棵有根树的每一个结点至多有两个儿子,那么这棵树称为二叉树
如果一棵二叉树的每一个节点都带着一个值,且父亲的值总是比儿子的值要大,我们称这棵树为大顶二叉堆,如果是父亲比儿子都要小,那就是小顶二叉堆,统称为二叉堆。(其实一般都把二叉两个字省略掉,毕竟通常说的堆都是二叉堆,然而堆不止二叉堆)。这一个良好的性质注定了堆可以用来当作优先队列使用。
优先队列支持以下操作
1.放一个元素进去
2.总是能取出一个最大的元素出来(大,小的规矩可以通过一个比较函数来定义)
显然堆就是可以这么做。
当然啦,之前说过堆不止二叉堆,还有更复杂的二项堆,斐波那契堆,配对堆等等。。。具体可查阅论文or算法导论
总结,堆是一种特殊的树。(结尾点题!)
堆的定义:在1到n/2的元素中,有k(i)<=k(2i),k(i)<=k(2i+1)
* 或k(i)>=k(2i),k(i)>=k(2i+1)
* 简单来说:就是假如将此序列看成一棵完全二叉树,要使这个无序列表
* 变成堆,则小于等于n/2(最后一个非终端节点就是n/2)的某个节点i的左右子节点均大于此节点
* 即堆的定义k(i)<=k(2i),k(i)<=k(2i+1)