Java数据结构浅析
程序 = 数据结构 + 算法
本文概述Java中常用的数据结构,并简述其使用场景
1. 数据结构的定义
数据结构是一种逻辑意义,指的是逻辑上的数据组织方式及相应的处理,与数据在磁盘的具体存储方式不完全相关。磁盘存储数据的方式可能是顺序存储也可能是链式存储。
逻辑上的数据组织方式有:队列、树、图、哈希等。
数据的处理:增删改查、遍历 。
2. 数据结构的分类
以数据是否存在前继和后继对数据结构做出如下分类 :
线性结构:0至1个直接前继和直接后继。如:顺序表(数组)、链表、栈、队列。
树结构:0至1个直接前继和0至n(n大于等于2)个直接后继。
图结构:0至n(n大于等于2)个直接前继和直接后继。
哈希结构:没有直接前继和直接后继,通过特定的哈希函数将索引与存储的值关联起来。
3.Java中的集合
List集合:存在明确的上一个和下一个元素,也存在明确的第一个元素和最后一个元素
ArrayList:非线程安全,内部使用数组进行存储;扩容时需要创建新的数组,并复制数据。访问数据较快,插入和删除较慢;
LinkedList:本质是双向链表,添加和删除速度较快,随机访问较慢。
Queue(队列)集合:队列是一种先进先出的数据结构,也是线性表的一种,只允许在表的一端获取数据,在另一端插入数据
自BlockingQueue(阻塞队列)发布后,队列多在高并发场景作为Buffer(数据缓冲区)使用
Map集合:Key-Value键值对作为存储元素实现的哈希结构,Key按哈希函数计算后保证唯一,Value则是可重复的
HashMap不是线程安全的,ConcurrentHashMap是线程安全的,TreeMap是Key有序的Map集合类
Set集合:不允许出现重复元素的集合类型
HashSet是使用HashMap来实现的,TreeSet是使用TreeMap实现的;LinkedHashSet继承自HashSet,具有HashSet的优点,内部使用链表维护元素的插入顺序。
* 集合初始化
集合初始化时指定集合初始值的大小可以减少扩容成本,有助于提高性能。
ArrayList默认初始值为10,每次扩容的大小大约为之前大小的1.5倍,oldCapacitiy + (oldCapacitiy >> 1)
HashMap默认初始值为16,扩容时需重建hash表,非常影响性能。HashMap中有两个重要参数:Capacity和Load Factor(值默认为0.75),
HashMap基于这两个数的乘积表示能放入集合的元素个数;HashMap的容量是在第一次put时才创建,并不是在new时分配。
4.Java中的数组
数组是一种顺序表,下标从0开始。数组是固定容量大小的,数组内的值可以修改,但数组大小不能改变。
* 数组转集合
Arrays.asList返回的是Arrays的内部类ArrayList,此内部类仍指向原数组,并不是java.util.ArrayList集合,在进行修改操作时会抛异常。
正确的转换方式:
List<Object> objectList = new java.util.ArrayList<Object>(ArrayList.asList(数组));
集合转数组时注意数组类型与集合数据类型保持一致,数组的大小为集合大小list.size()。
5. 集合与泛型
引入泛型是为了保证类型安全,集合间相互赋值时是传递引用,直接操作赋值后的集合会影响原集合。
List :无任何类型限制和赋值限定
List<Object>:用法等同于List,但是在接受其他泛型赋值时会编译报错;List<Integer>不能赋值给List<Object>
List<?> :通配符集合,可以接受任何类型的集合引用赋值,但可以remove和clear;常作为形参或返回值类型
public void collectionTest() {
List<Integer> listInteger = new ArrayList<Integer>(10);
listInteger.add(1); List list = listInteger;
list.add("hello"); for(Object obj : listInteger ) {
//打印结果为: 1 hello
//原集合的值被修改了,不使用泛型可能造成隐藏Bug
System.out.println(obj);
} List<?> a = listInteger;
//add编译报错
a.add(new Integer(2));
}
List<? extends T> :类型为T或T的子类;Get First,适用于消费集合元素为主的场景
List<? super T> :类型为T或T的父类;Put First,适用于生产集合元素为主的场景
6. 元素的比较
6.1 Comparable和Comparator
约定俗成,比较器的返回结果:小于的情况返回-1;等于的情况返回0;大于的情况返回1。
Comparable接口:与自己比,比较方法是compareTo
Comparator接口:平台性质的比较器,比较方法是compare
Arrays.sort中使用了TimSort算法,该算法是归并排序和插入排序优化后的算法,时间复杂度最优可达O(n),平均时间复杂度为O(nlogn)
6.2 hashCode和equals
hashCode和equals用来标识对象,两个方法协同工作可用来判断两个对象是否相等。
若只复写equals方法,调用equals比较对象时,实质比较的是equals方法中的属性是否相同,并不判断对象本质是否相等。
若同时复写equals方法和hashCode,则调用equals时相当于使用"=="
尽量避免通过实例对象引用来调用equals方法,否则容易抛出空指针异常,建议使用Objects中的equals方法,源码如下:
public static boolean equals(Object a, Object b) {
return (a==b) || (a != null && a.equals(b));
}
7. fail-fast与fail-safe机制
fail-fast机制是集合中常见的错误检测机制,即检查集合的元素在遍历过程中的一致性,若遍历时集合产生了变化则产生fail-fast。java.util包下的所有集合类都是fail-fast的,ArrayList.subList()方法产生的子列表与主列表间会相互影响,如产生子列表后再删除主列表元素会导致子列表的操作异常。
在遍历删除集合元素时,使用Iterator机制;多线程并发的场景应加锁,源码如下:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
synchronized(对象) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
}
fail-safe机制可简单理解副本快照的模式,遍历时对副本进行操作,修改操作在原始数据上进行,修改完成后同步到副本中。concurrent包中的集合都是fail-safe的。
COW(Copy-On-Write):读写分离的设计思想,修改数据时复制一个新的集合,在新集合上进行修改,完成后将旧集合的引用指向新集合。仅适用于读多写极少的场景,若频繁进行写操作会导致效率急速下降;另一缺点为数据的实时性无法保证。
/**
* 此代码执行时间为几十s,使用ArrayList执行时间为ms级别
*/
public static void main(String[] args){
List<Long> copy = new CopyOnWriteArrayList<Long>(); long start = System.nanoTime();
for(int i=0; i < 20 * 10000; i++ ) {
copy.add(System.nanoTime());
}
}
8. Map集合
ConcurrentHashMap在性能上与HashMap区别不大,但是Key-Value均不能为空,两者互换使用时需注意空指针(NPE)问题。
8.1 树
节点的深度指当前节点到根节点的距离,节点的高度指当前节点到最远子叶子节点的距离。
二叉树
每个节点至多有两个子节点的树称为二叉树。
平衡二叉树
任何节点的左右子树高度差不超过1,空树或只有根节点的树也是平衡二叉树。
二叉查找树(Binary Search Tree)
二叉查找树的所有节点满足该节点左子树的值均小于该节点,右子树的值均大于该节点。
遍历方式:前序、中序、后序
(1)在任何递归子树中,左节点一定在右节点之前遍历;
(2)前中后序仅指父节点在遍历时的位置顺序。
二叉查找树在数据改变时容易失衡,导致操作效率变低;为了保证二叉树的平衡性,有如下算法实现,如AVL树,红黑树,SBT等等,现在常用的是红黑树。
AVL树通过对不平衡的树进行左旋或者右旋以达到平衡,属于平衡二叉树,在删除数据后可能发生大量旋转导致时间成本增加。
红黑树也是通过左旋或右旋以实现平衡,非绝对平衡。红黑树的特点(有红必有黑,红红不相连):
(1)节点非黑即红
(2)根节点必须为黑
(3)所有NIL节点都是黑色的(Nothing In Leaf,叶子节点上虚构的子节点)
(4)红色节点不相邻
(5)任何递归子树内,根节点到叶子节点的所有路径上包含相同数目的黑色节点
8.2 TreeMap
TreeMap的Key是有序不重复的,支持获取头尾元素。TreeMap依靠Comparable或Comparator来实现Key的去重;HashMap使用hashCode和equals方法去重。
8.3 HashMap
HashMap的两个主要问题:死链问题和扩容数据丢失。(多线程高并发场景)
哈希类集合的三个基本概念:
HashMap高并发场景新增对象丢失的原因:
* 并发赋值时被覆盖
* 已遍历区间新增元素会丢失
* “新表”被覆盖
* 迁移丢失。在迁移过程中,有并发时,next被提前置成null
HashMap死链形成原因(数据在桶内以链表的形式存储):
死链形成的原因是链表中指向下一个元素的next值被并发修改,导致形成对象间互链或对象自己互链。
8.4 ConcurrentHashMap
JDK对该类进行的优化:
(1)取消分段锁机制,进一步降低冲突概率。
(2)引入红黑树结构。同一个哈希槽上的元素超过一定阈值后,单向链表转化为红黑树结构。元素数量减少到一定的个数时,红黑树会退化成单向链表。
(3)使用了更加优化的方式统计集合内元素数量。使用CAS机制。
CAS(Compare And Swap):JDK提供的非阻塞原子性操作,通过硬件保证了比较-更新操作的原子性。解决轻微冲突的多线程并发场景下使用锁造成性能损耗的一种机制。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 JDK中的Unsafe类提供了一系列的compareAndSwap方法。
Java数据结构浅析的更多相关文章
- Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用
Java数据结构之---Queue队列 队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在 ...
- JAVA数据结构系列 栈
java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为 ...
- Java数据结构之树和二叉树(2)
从这里始将要继续进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来 ...
- Java数据结构之树和二叉树
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- Java数据结构之线性表(2)
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- Java数据结构之线性表
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- java 数据结构 图
以下内容主要来自大话数据结构之中,部分内容参考互联网中其他前辈的博客,主要是在自己理解的基础上进行记录. 图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为G(V,E),其中,G标示 ...
- java 数据结构 队列的实现
java 数据结构队列的代码实现,可以简单的进行入队列和出队列的操作 /** * java数据结构之队列的实现 * 2016/4/27 **/ package cn.Link; import java ...
- java 数据结构 栈的实现
java数据结构之栈的实现,可是入栈,出栈操作: /** * java数据结构之栈的实现 * 2016/4/26 **/ package cn.Link; public class Stack{ No ...
随机推荐
- Linux新增开放端口
CentOS系统 开放端口的方法: 方法一:命令行方式 1. 开放端口命令: /sbin/iptables -I INPUT -p tcp --dport 8080 -j ...
- 《A Survey on Transfer Learning》迁移学习研究综述 翻译
迁移学习研究综述 Sinno Jialin Pan and Qiang Yang,Fellow, IEEE 摘要: 在许多机器学习和数据挖掘算法中,一个重要的假设就是目前的训练数据和将来的训练数据 ...
- Mybatis Guide
每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为中心的.SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得.而SqlS ...
- Rowlock、UPDLOCK
ROWLOCK 使用行级锁,而不使用粒度更粗的页级锁和表级锁. UPDLOCK 读取表时使用更新锁,而不使用共享锁,并将锁一直保留到语句或事务的结束.UPDLOCK 的优点是允许您读取数据(不阻塞 ...
- PJzhang:kali linux安装virtualbox虚拟机和chrome浏览器
猫宁!!! 参考链接: https://www.cnblogs.com/zhishuai/p/8007410.html kali linux 安装virtualbox. 查询系统的版本 apt-cac ...
- 不同种类的PI膜及其特性
一.PI膜种类及其特性 1.聚酰亚胺材料分类 聚酰亚胺材料可分为热塑性聚酰亚胺和热固性聚酰亚胺(包括双马来酰亚胺型和单体反应聚合型聚酰亚胺及各自改性产品)两大类 其中,热塑性聚酰亚胺材料一般采用两步法 ...
- 【计算机视觉】UCLA开源图像检测器
UCLA (加州大学洛杉矶分校)发布了一个强大的图像检测软件的源码 ,该软件可以非常高速的检测每个图像的细节,例如可用于检测指纹和虹膜,或者用于自动驾驶.通过识别物体的边界进行提取.首先确定一个物体的 ...
- Appendix 1- LLN and Central Limit Theorem
1. 大数定律(LLN) 设Y1,Y2,……Yn是独立同分布(iid,independently identically distribution)的随机变量,A = SY /n = (Y1+...+ ...
- 【Python】if __name__ == '__main__' 含义解析
相信大家在看别人的python程序时,可能会在大部分的程序后看到标题这段代码,这里解释下它的意义.总的来说,这句代码的作用就是既能保证当前的.py文件直接运行,也能保证其可以作为模块被其他.py文件导 ...
- 【一个蒟蒻的挣扎】最小生成树—Kruskal算法
济南集训第五天的东西,这篇可能有点讲不明白提前抱歉(我把笔记忘到别的地方了 最小生成树 概念:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的 ...