java基础复习笔记 数据结构_Java/Android常用数据结构总结(面试复习)

本文主要针对开发中常用的数据结构做个总结,主要还是源码原理相关的内容,尤其是面试需要复习的同学可以多研究一下。

线性表

ArrayList、LinkedList

ArrayList

基于动态数组实现的,初始化的时候没有指定大小的话,默认容量是10,添加元素的时候会判断是否需要对数组扩容,每次扩容大小为1.5倍,如果扩容1.5倍不够就用目标的size作为扩容后的容量,扩容是通过数组的拷贝实现的,这是一个非常耗性能的操作,所以如果可以确定大概的数据量,可以直接指定list大小。由于list是基于数组实现的,数组的特点是在堆内存中用连续的内存空间来存储,所以访问/查找效率比较高,而在列表中间添加/删除元素的话,需要数组拷贝来完成,所以添加/删除效率会比较低,如果是在末尾操作的话,效率没什么影响,因为不需要数组拷贝,时间复杂度为O(1)。

LinkedList

通过一个双向链表来实现,大概结构如下:

fc22eb17ae43

双向链表结构

双向链表的数据存储单元是节点(前驱节点、后继节点、本节点数据),它不需要像数组那样知道数据大小,在内存存储上不需要连续的内存空间,双向链表通过指针来指向前后数据;它插入/删除数据是靠改变指针指向来实现。链表结构决定了它没有大小限制,也不会有什么初始化值和扩容。

插入/删除效率

LinkedList中定义了头部和尾部2个Node,如果插入/删除的位置是头部/尾部的话,时间复杂度是O(1);如果插入/删除位置是在中间的话,它会先做一次二分法查找,判断索引是在前半段还是在后半段,从短的那头开始遍历,找到之后,新建一个节点,建立新的前置节点和后置节点的关系,时间复杂度最大是O (n/2);

访问效率

也是同样的查找方法,时间复杂度也是最大是O (n/2)。

Deque

LinkedList实现了Deque接口,这个接口同时实现了栈和队列的功能,所以,LinkedList还可以当作栈或者队列来使用。

ArrayList 和LinkedList简单对比

ArrayList是基于动态数组实现的,而LinkedList是双向链表;

堆内存存储上,ArrayList连续的内存空间,LinkedList不需要;

ArrayList随机访问比较快,因为可以通过首地址+偏移量直接计算出我想访问的第x个元素在内存中的位置,用时间复杂度来表示的话是O(1),而LinkedList是o(n/2);

插入/删除来看,ArrayList效率不如LinkedList,因为前者要做数组扩容和拷贝(耗性能),后者只是相关节点前后指针的移动;

二者都是线程不安全

LinkedList真的比ArrayList添加/删除快吗?如何判断二者的使用场景?

注:这里的快和慢是相对的。并不是LinkedList的插入和删除就一定比ArrayList快。明白其快慢的本质:ArrayList快在定位,慢在数组复制。LinkedList慢在定位,快在指针修改。

Java基础篇(四):ArrayList和LinkedList内部实现、区别、使用场景

线程安全的 CopyOnWriteArrayList

从源码分析可以看出ArrayList不是线程安全的,要变成线程安全方法很多,比如说替换成Vector,再或者是使用 Collections,将 ArrayList 包装成一个线程安全的类。不过这两种方法也有很大的缺点,那就是他们使用的都是独占锁,独占式锁在同一时刻只有一个线程能够获取,效率太低。于是CopyOnWriteArrayList 应用而生了。

(1) 、独占锁效率低:采用读写分离思想解决

读操作不加锁,所有线程都不会阻塞。写操作加锁,线程会阻塞。

(2) 、写线程获取到锁,其他线程包括读线程阻塞

但是这时候又出现了另外一个问题了:写线程获取到锁之后,其他的读线程会陷入阻塞。

(3)、解决思路:CopyOnWrite思想

当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。简单来说就是更新数据的时候做一次数组拷贝,对拷贝的新对象操作完了,再让原来的容器对象指向这个新对象。

(4)、优点

不会存在读数据阻塞线程的情况,适合多线程中读多写少的场景

(5)、缺点

数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据

内存消耗大。由于在写的时候会做数组拷贝,当2个数组内容都比较多的时候,内存占用就比较高。

CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

栈、队列

Stack

注意,这里说的栈是指java.util.Stack。栈是线性表的一种,底层是通过数组来实现的,它只能在栈顶操作数据,数据的进出方式是先进后出(FILO)。继承于Vector,是线程安全的。

fc22eb17ae43

栈.png

Queue

注意,这里说的栈是指java.util. Queue。队列也是线性表的一种,它是从队尾插入数据,从队列头部取数据,数据进出方式先进先出(FIFO)。Queue是一个接口,所以队列的实现由其他实现类完成,比如LinkedList,是线程不安全的。

fc22eb17ae43

队列.png

Deque

双端队列,继承于java.util.Queue,也是一个接口,同时具备中栈和队列的特点,它可以从两端分别进入插入,删除操作。我们熟知的LinkedList就实现了Deque。

HashMap、LinkedHashMap、ConcurrentHashMap

HashMap

数据结构是:数组+链表(jdk1.8引入了红黑树)的形式来存放键值对的。首先对key做hash()运算来确定元素在数组中的位置,但是不同的key做hash运算得到的值可能是一样的,如果不同的key计算之后得到的索引相同,那么在这个位置会创建一个单向链表来存储相同索引的元素,在jdk1.8中,如果这个链表的元素超过8个,会引入红黑树来提升效率,说HashMap是兼具数组访问快和链表添加/删除快的优点,由于HashMap 是根据key的hash值在数组上随机分布的,所以hashmap是无序的。

hashmap之所以要设置长度必须为2的n次方,是因为这样可以减少碰撞,使元素分散的更均匀。

int index= hash & (length- 1)

具体来说,当length=2^n,那么length-1转化为2进制数,低位的每一位都是1,这样在做与运算的时候,index就能分散到更多的位置,否则的话就有一些位置永远都不会存储元素。当然如果用取模运算就不必有这个限制,hash % (length- 1),但是这样呢运算效率很低。

hashmap的加载因子为什么是0.75f?

因为过小的话,会造成扩容频繁;过大的话会产生更多碰撞

LinkedHashMap

LinkedHashMap继承于HashMap,它是在HashMap的基础上通过双向链表维护元素节点间的顺序,默认是按插入顺序排序,原理是把新加入的元素放在双向列表的尾部;如果是访问顺序排序的话,访问元素的时候会把元素放在链表尾部,这样就可以维护访问顺序,最常见的应用就是LRU算法。

ConcurrentHashMap

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。所以ConcurrentHashMap是线程安全的,且在性能上不输HashMap。

Set

首先Set是一个接口,它的实现类主要有HashSet、TreeSet,它的特点是:不允许存储的元素重复。

HashSet

HashSet内部是使用HashMap来实现的元素存储,特点:无序、不允许元素重复。利用了hashmap的key不会重复的特性来实现元素的唯一性。

TreeSet

使用的TreeMap来实现的元素存储。同样是以存储的元素作为TreeMap的key,所以元素也是不能重复的。另外因为TreeMap的key是有序的,所以TreeSet是一个有序的集合。

树形结构Tree

深度优先遍历:

对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。而二叉树的深度优先遍历分为先序遍历,中序遍历和后续遍历。

先序遍历:先访问根,在访问左子树,最后访问右子树,总结就是“根左右”;

中序遍历:先访问左子树,再访问根,最后访问右子树,总结就是“左根右”;

后序遍历:先访问左子树,再访问右子树,最后访问根,总结就是“左右根”;

通常采用递归的方式实现遍历,非递归方式需要结合栈(后进先出)的特点实现。

广度优先遍历:

又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止。

广度优先遍历的非递归的通用做法是采用队列(先入先出)。

二叉查找树

二叉查找树(Binary Search Tree),或者是一颗空树,或者是具有下列性质的二叉树:

1、若它的左子树不空,则其左子树上的所有结点的值均小于它根结点的值;

2、若它的右子树不空,则其右子树上的所有结点的值均大于它根结点的值;

3、它的左、右子树也分别为二叉查找树。

树形结构就是利用二叉查找树这种特性来实现快速查询,效率上和线性表的二分查找差不多。

数据结构面试篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值