程序 = 数据结构 + 算法

本文概述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数据结构浅析的更多相关文章

  1. Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用

    Java数据结构之---Queue队列 队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在 ...

  2. JAVA数据结构系列 栈

    java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为 ...

  3. Java数据结构之树和二叉树(2)

    从这里始将要继续进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来 ...

  4. Java数据结构之树和二叉树

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  5. Java数据结构之线性表(2)

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  6. Java数据结构之线性表

    从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...

  7. java 数据结构 图

    以下内容主要来自大话数据结构之中,部分内容参考互联网中其他前辈的博客,主要是在自己理解的基础上进行记录. 图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为G(V,E),其中,G标示 ...

  8. java 数据结构 队列的实现

    java 数据结构队列的代码实现,可以简单的进行入队列和出队列的操作 /** * java数据结构之队列的实现 * 2016/4/27 **/ package cn.Link; import java ...

  9. java 数据结构 栈的实现

    java数据结构之栈的实现,可是入栈,出栈操作: /** * java数据结构之栈的实现 * 2016/4/26 **/ package cn.Link; public class Stack{ No ...

随机推荐

  1. 小D课堂 - 新版本微服务springcloud+Docker教程_5-04 feign结合hystrix断路器开发实战下

    笔记 4.Feign结合Hystrix断路器开发实战<下>     简介:讲解SpringCloud整合断路器的使用,用户服务异常情况     1.feign结合Hystrix       ...

  2. 一百零七:CMS系统之权限和角色模型定义

    模型与权限关系映射表 class CMSPersmission: """ 权限管理映射 """ # 255的二进制方式来表示 1111 11 ...

  3. Go项目实战:打造高并发日志采集系统(一)

    项目结构 本系列文章意在记录如何搭建一个高可用的日志采集系统,实际项目中会有多个日志文件分布在服务器各个文件夹,这些日志记录了不同的功能.随着业务的增多,日志文件也再增多,企业中常常需要实现一个独立的 ...

  4. k8s组件批量启动、查看状态

    查看所有master节点kube-apiserver ansible k8s -m shell -a 'systemctl status kube-apiserver' 重启所有master节点kub ...

  5. HBuilder X 中使用模拟器进行App开发

    第一步:下载HBuilder X(建议下载开发版) 第二步:下载个模拟器,我这里使用的是(MuMu模拟器) 第三步:在HBuilder X 中新建一个项目 然后,打开模拟器 如果 HBuilder x ...

  6. Linux下高cpu解决方案(转载)

    Linux下高cpu解决方案(转载 1.用top命令查看哪个进程占用CPU高gateway网关进程14094占用CPU高达891%,这个数值是进程内各个线程占用CPU的累加值.   PID USER  ...

  7. Linux下查看文件编码及批量修改编码

    查看文件编码在Linux中查看文件编码可以通过以下几种方式:1.在Vim中可以直接查看文件编码:set fileencoding即可显示文件编码格式.如果你只是想查看其它编码格式的文件或者想解决用Vi ...

  8. 【LOJ】#3088. 「GXOI / GZOI2019」旧词

    LOJ#3088. 「GXOI / GZOI2019」旧词 不懂啊5e4感觉有点小 就是离线询问,在每个x上挂上y的询问 然后树剖,每个节点维护轻儿子中已经被加入的点的个数个数乘上\(dep[u]^{ ...

  9. JS的精确简单的加减乘除

    简单的写法: <script> function decNum(a){/*获取小数位数*/ var r=0; a=a.toString(); if(a.indexOf(".&qu ...

  10. 接口踩坑:Status (blocked:other)

    1.请求接口时出现 Status (blocked:other) 2.原因分析:安装了Adblock 3.解决办法 1)关掉Adblock2)修改接口名称,不能用 ad 或者 XX ad XX 等名称 ...