程序 = 数据结构 + 算法

本文概述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. 自定义zabbix脚本--网卡平均流量

    自定义zabbix脚本--网卡平均流量1. 在客户端修改配置文件 /etc/zabbix/zabbix_agentd.conf需要改动两个地方:(1) UnsafeUserParameters=1(2 ...

  2. springmvc集成swagger

    1.保证项目为maven项目 2.导入jar包依赖 <dependency> <groupId>io.springfox</groupId> <artifac ...

  3. 数据分析电子商务B2C全流程_数据分析师

    数据分析电子商务B2C全流程_数据分析师 目前,绝大多数B2C的转化率都在1%以下,做的最好的也只能到3.5%左右(比如以卖图书为主的当当) 我想,所有的B2C都会关心三个问题:究竟那97%去了哪里? ...

  4. iOS开发UIkit动力学UIDynamicAnimator一系列动画

    UIDynamicAnimator类,通过这个类中的不同行为来实现一些动态特性. UIAttachmentBehavior(吸附),UICollisionBehavior(碰撞),UIGravityB ...

  5. playbook部署coredns

    playbook部署coredns 说明test1是主控节点,目的是给test4 node节点安装coredns, 1.coredns-1.2.2.tar.gz安装包放到主控节点/server/sof ...

  6. php进程创建慢导致的502

    转自: 作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除地址:http://www.jackxiang.com/post/926 ...

  7. Java虚拟机new对象

    类加载检查java虚拟机在遇到一条 new 指令时,首先会检查是否能在常量池中定位到这个类的符号引用,并且是否已被加载过.解析和初始化过.如果没有,那必须先执行类加载过程 类加载的相关知识可参考:JV ...

  8. LeetCode.1175-质数排列(Prime Arrangements)

    这是小川的第413次更新,第446篇原创 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第264题(顺位题号是1175).返回1到n的排列数,以使质数处于质数索引(索引从1开始).(请 ...

  9. SGI STL源码stl_bvector.h分析

    前言 上篇文章讲了 STL vector 泛化版本的实现,其采用普通指针作为迭代器,可以接受任何类型的元素.但如果用来存储 bool 类型的数据,可以实现功能,但每一个 bool 占一个字节(byte ...

  10. [转帖]判断Linux进程在哪个CPU核运行的方法

    判断Linux进程在哪个CPU核运行的方法   原文网址:http://www.embeddedlinux.org.cn/html/xinshourumen/201601/30-5013.html 问 ...