所有技术的应用都离不开它的底层实现,今天博主给大家带来一期数据结构的分享,因内容量较大,本篇分享涉及数组及其拓展ArrayList、栈及其拓展撤回、队列及其拓展消息队列。余下的数据结构及拓展分享写在下一期里,话不多说,说干就干!
常用的数据结构分别有数组、堆栈、队列、链表、树、图、字典树、哈希表,那么接下来我们来详细地一个一个地看看他们的应用以及拓展。
一、数组Array——集合ArrayList
数组(Array) 不得不说是大家在学习之初就已经接触过再平凡不过的了吧,像栈和队列都是由数组衍生出来的。
下面展示了一个数组,它有五个元素,每一个数组元素的位置称为下标或者索引(index),那么第一个元素的下标即为0,如下图所示: 如上展示的是一个一维数组,那么二维数组是怎样的呢?顾名思义,二维数组就是一个数组中的某一个元素为一个数组,当然了,除了二维数组,更有多维数组的存在。那么如下就是一个二维数组: 接下来我们来说说集合ArrayList,这个集合也是我们平常写代码的时候最最最常见的一种集合,它的特点之一是可以自动扩容,实际上是一个动态数组,底层就是用数组实现的,随机访问的效率高,但是删除、修改效率低、是线程不安全。由于数组的长度是不可改变的,所以底层实现扩容的时候是新建一个数组,当元素的个数大于其容量的时候,就会新建一个1.5倍的数组,然后再将原来的数组用Arrays.copyOf方法复制到新的数组中去。ArrayList在IDEA中新建的时候,可以通过CTRL+左键点击ArrayList进去看他的具体实现源码,这里就不做过多的讲解了。
二、栈stack——撤回
对于 栈(stack) 来说最常见的就是Ctrl+Z撤回了,相信大家在各种应用中都用过撤回这个操作,其原理就是将之前的应用状态保存到内存中,但是要限制个数,最近的状态就放在第一个,这个时候就需要使用到栈来实现这个功能。 栈的特点:LIFO(Last In First Out),就是先进后进后出的意思。
三、队列(Queue)——消息队列
队列(Queue) 与栈相似,都是采用线性结构存储数据的,他们的区别就在于,栈采用LIFO即先进后出的方式,而队列是采用先进先出的方式,即FIFO(First In First Out)。如图: 说起队列,就不得不提起一下我们常说的消息队列了,即把要传输的数据放在队列中去,那么这里就有两个角色的产生了:生产者和消费者,生产者即把数据放到队列中的叫生产者,把数据从消息队列中取出来的叫消费者。那么我们为什么要用消息队列?
第一,解耦。 降低依赖程度。就好比B让A去生产一个对象B用来消费,恰好C也让A去生产一个对象来消费掉,那是不是就得在B中和C中新建一个A的对象,然后调用A的生产方法,这样的话,在程序运行过程中,B或者C任何一个出了问题,我们怎么知道是B和C中New的生产者A的问题,还是消费者B和C本身体的问题呢?所以我们就用到了消息队列,让A将生产出来的对象放进消息队列中,如果B和C需要A生产的对象的话,直接从消息队列中去取,通过这样的方式就降低了耦合。
第二,异步。 还是用生产者A和消费者B、C来讲,在系统A中,生产出对象,调用B中的方法去消费对象,再调用C中的方法去消费对象,假设调用B消费对象需要300ms,调用C去消费对象需要300ms,那么此时此刻代码由上向下执行,就需要耗费600ms。那么使用消息队列以后,生产者A生产出对象放进消息队列中,消费者B和C就可以同时去消息队列中取得生产的对象,再执行,这样的话,程序总耗时就是300ms。
第三,削锋/限流。 给出这样一个应用场景:某品牌的手机搞低价促销,定在了中午的12点开抢,那么此时此刻,在12点这一刻,突然来了3000个请求,而消费者B和消费者C每秒最大处理请求数量只有1000,那此时此刻怎么办?程序岂不是崩掉了?那么这个时候就需要把这一秒的3000个请求放在消息队列中,虽然消费者B和消费者C每秒只能处理1000个请求,但是当程序运行的时候,我们就让消费者B和消费者C从消息队列中取1000个请求进行处理,处理完成以后,再从消息队列中去取,这样就达到了削锋、限流的目的,也保证了程序的健康性。
消息队列的好处很多,但是我们也需要考虑到它的一些问题,比如说消息队列挂掉了,如图: 显而易见,此时此刻程序是不是就崩了呀,所以当我们使用消息队列的时候,是需要集群/分布式的,就得让这个消息队列能够提供现成的支持,而不是自己去写代码手动实现,当然既然有消息队列的产生,应对这个问题就存在对应的应对策略,大家可以深入了解一下。其次还有数据丢失、重复消费等等问题,消息队列是一个大头,这里博主也只是简单提及一下,有兴趣的朋友们可以了解一下消息队列。
今天的分享就到这里啦,欢迎大家一起交流学习。下一期给大家带来链表、树、图、字典树、哈希表及其拓展应用。
点赞关注加收藏,编程学习不迷茫!
|