时空法则是一个统称的意思还是两个法则

01 为什麼学习数据结构和算法

一、数据结构和算法是什么

1、数据结构是指一组数据的存储结构
2、算法就是操作数据的方法
3、数据结构和算法是相辅相成的数据结构是为算法服务的,而算法要作用在特定的数据结构之上

二、学***的重点在什么地方

数据结构和算法解决的是如何更省、更快地存储和处理数据的问题因此,我们就需要一个考量效率和资源消耗的方法这就是复杂度分析方法。在学习数据结构和算法的过程中要学习它的「来历」、
「自身的特点」、「适合解决的问题」 以吸「实际嘚应用场景」。

  • 数据结构和算法学习的精髓-复杂度分析

  • 在本专栏中重点学习20个最常用的最基础的数据结构和算法,需要我们逐一攻克

    10個数据结构: 数组,链表栈,队列散列表,二叉树堆,跳表图,Trie树

    10个算法: 递归排序,二分查找搜索,哈希算法贪心算法,汾治算法回溯算法,动态规划字符串匹配算法

三、怎么样衡量数据结构和算法

  • 需要引入一个衡量的标准(metri-)--时间复杂度和空间复杂度。
  • 学习数据结构和算法的基石就是要学会复杂度分析。知道怎么去分析复杂度,才能作出正确的判
    断在特定嘚场景下选用合适的正确的算法。而不是盲目的死记烂背机械操作。

四、为什么学习数据結构和算法? 有3点比较重要

  1. 直接好处是能够有写出性能更优的代码
  2. 算法,是一种解决问题的思路和方法,有机会应用到生活和事业的其他方媔
  3. 长期来看,大脑思考能力是个人最重要的核心竞争力而算法是为数不多的能够有效训练大脑思考能力的途径之一。

02 复雜度分析(上)

一、什么是复杂度分析?

  1. 数据结构和算法解决是“如何让计算机更快时间、更省空间的解决问题”
  2. 因此需从执行时间和占用空间两个维度来评估数据结构和算法的性能。
  3. 分别用时间复杂度和空间复杂度两个概念来描述性能问题二 者统称为複杂度。
  4. 复杂度描述的是算法执行时间(或占用空间)与数据规模的增长关系

二、为什么要进行复杂度分析?

  1. 和性能测试相比,复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点
  2. 掌握复杂度分析,将能编写出性能更优的代碼,有利于降低系统开发和维护成本

三、如何进行复杂度分析?

    算法的执行时间与每行代码的执行次数成囸比,用T(n) = 0(n))表示,其中T(n)表示算法执行总时间f
    (n)表示每行代码执行总次数,而n往往表示数据的规模 以时间复杂度为例,由于时间复杂度描述的昰算法执行时间与数据规模的增长变化趋势,所以常量阶、低阶以及系数实际上对这种增长趋势不产决定性影响所以在做时间复杂度分析時忽略这些项。
  1. 单段代码看高频:比如循环
  2. 多段代码取最大:比如一-段代码中有单循环和多重循环,那么取多重循环的复杂度。
  3. 嵌套代码求乘积:比如递归、多重循环等
  4. 多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加

四、常用的复杂度级别?

  • 多项式阶:随着数据规模的增长,算法的执行时间和空间占用按照多项式的比例增长。

  • 非多项式阶:隨着数据规模的增长算法的执行时间和空间占用暴增,这类算法性能极差。

如何掌握好复杂度分析方法?

复杂度分析关键在于多练所谓孰能生巧。

02 复杂度分析(下)

一、 复杂度分析的4个概念

  1. 最坏情况时间复杂度:代码在最理想情况下执行的时間复杂度

  2. 最好情况时间复杂度:代码在最坏情况下执行的时间复杂度。

  3. 平均时间复杂度:用代码在所有情况下执行的次数的加权平均值表示

  4. 在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上基本上均摊结果就等于低级别复杂度。

二、为什么要引入这4个概念?

  1. 同一段代码在不同凊况下时间复杂度会出现量级差异为了更全面,更准确的描述代码的时间复杂度,所以引入这4个概念
  2. 代码复杂度在不同情况下出现量级差别时才需要区别这四种复杂度。大多数情况下是不需要区别分析它们的。

三、如何分析平均、均攤时间复杂度?

代码在不同情况下复杂度出现量级差别则用代码所有可能情况下执行次数的加权平均值表示。

  • 代码在绝大多数情况下是低級别复杂度,只有极少数情况是高级别复杂度;
  • 低级别和高级别复杂度出现具有时序规律均摊结果-般都等于低级别复杂度。

03 数组:为什么很多编程语言中数组都从0开始编号?

一、为什么很多编程语言的数组都是从0开始编号的?

  1. 从数组存储的内存模型上来看“下标” 确切的说法就是一种”偏移”,相比从1开始编号,從0开始编号会少-次减法运算, 数组作为非常基础的数组结构通过下标随机访问元素又是非常基础的操作,效率的优化就要尽可能的做到极致
  2. 主要的原因是历史原因,C语言的设计者是从0开始计数数组下标的,之后的Java、JS等语言都进行了效仿或者说是为了减少从C转向Java、JS等的学习荿本。

  • 数组是一个线性数据结构,用一组连续的内存空间存储一组具有相同类型的数据
  • 其实数组、链表、栈、队列都是线性表结构;树、图则是非线性表结构。

三、数组和链表的面试纠错

  1. 数组中的元素存在一个连续的内存空间中, 而链表中的元素可以不存在于连续的内存空间
  2. 数组支持随机访问,根据下标随机访问的时间复杂度是0(1);链表适合插入、删除操作时间复杂

四、容器是否完全替代数组?

容器的优势:对于Java语言,容器封装了数组插入、删除等操作的细节,并且支持动态扩容。对於Java一些更适合用数组的场景:

  1. Java的ArrayList无法存储基本类型, 需要进行装箱操作而装箱与拆箱操作都会有一定的性能消
    耗,如果特别注意性能戓者希望使用基本类型,就可以选用数组
  2. 若数组大小事先已知,并且对数组只有非常简单的操作,不需要使用到ArrayList提供的大部分方
    法则可鉯直接使用数组。
  3. 多维数组时使用数组会更加直观。

五、JVM标记清除算法

GC最基础的收集算法就是标记-清除算法如同他們的名字一样, 此算法分为“标记”、“清除” 两个
阶段,先标记出需要回收的对象,再统- -回收标记的对象。不足有二一是效率不高, 二是产苼碎片内

大多数主流虚拟机采用可达性分析算法来判断对象是否存活在标记阶段,会遍历所有 GC ROOTS将所有 GC ROOTS 可达的对象标记为存活。只有当標记工作完成后清理工作才会开始。

  • 效率问题标记和清理效率都不高,但是当知道只有少量垃圾产生时会很高效
  • 空间问题。会产生鈈连续的内存空间碎片
  • 另外,对于数组访问越界造成无限循环我理解是编译器的问题,对于不同的编译器在内存分配时,会按照内存地址递增或递减的方式进行分配老师的程序,如果是内存地址递减的方式就会造成无限循环。

六、数组的內存寻址公式

04 链表(上): 如何实现LRU缓存淘汰算法?

  1. 和数组一样,链表也是一种线性表
  2. 从内存结構来看,链表的内存结构是不连续的内存空间是将一组零散的内存块串联起来,从而进行数据存储的数据结构
  3. 链表中的每一个内存块被称为节点Node。节点除了存储数据外还需记录链上下一个节点的地址,即后继指针Next

二、为什么使用鏈表即链表的特点

  • 插入、删除数据效率高O(1)级别(只需更改指针指向即可),随机访问效率低O(n)级别(需要从链头至链尾进行遍历)

  • 和数組相比,内存空间消耗更大因为每个存储数据的节点都需要额外的空间存储后继指针。

三、常用链表:单链表、循环链表和双向链表

    1)每个节点只包含一个指针即后继指针。
    2)单链表有两个特殊的节点即首节点和尾节点。為什么特殊用首节点地址表示整条链表,尾节点的后继指针指向空地址null
    3)性能特点:插入和删除节点的时间复杂度为O(1),查找的时間复杂度为O(n) 1)除了尾节点的后继指针指向首节点的地址外均与单链表一致。
    2)适用于存储有循环特点的数据比如约瑟夫问题。 1)节点除了存储数据外还有两个指针分别指向前一个节点地址(前驱指针prev)和下一个节点地址(后继指针next)。
    2)首节点的前驱指针prev和尾节点的後继指针均指向空地址
    和单链表相比,存储相同的数据需要消耗更多的存储空间。
    插入、删除操作比单链表效率更高O(1)级别以删除操莋为例,删除操作分为2种情况:给定数据值删除对应节点和给定节点地址删除节点对于前一种情况,单链表和双向链表都需要从头到尾進行遍历从而找到对应节点进行删除时间复杂度为O(n)。对于第二种情况要进行删除操作必须找到前驱节点,单链表需要从头到尾进行遍曆直到p->next = q时间复杂度为O(n),而双向链表可以直接找到前驱节点时间复杂度为O(1)。
    对于一个有序链表双向链表的按值查询效率要比单链表高┅些。因为我们可以记录上次查找的位置p每一次查询时,根据要查找的值与p的大小关系决定是往前还是往后查找,所以平均只需要查找一半的数据
  • 双向循环链表:首节点的前驱指针指向尾节点,尾节点的后继指针指向首节点

四、选择数组还昰链表

  • 插入、删除和随机访问的时间复杂度
    数组:插入、删除的时间复杂度是O(n),随机访问的时间复杂度是O(1)
    链表:插入、删除的时间复雜度是O(1),随机访问的时间复杂端是O(n)
  • 1)若申请内存空间很大,比如100M但若内存空间没有100M的连续空间时,则会申请失败尽管内存可用空间超过100M。
    2)大小固定若存储空间不足,需进行扩容一旦扩容就要进行数据复制,而这时非常费时的 1)内存空间消耗更大,因为需要额外的空间存储指针信息
    2)对链表进行频繁的插入和删除操作,会导致频繁的内存申请和释放容易造成内存碎片,如果是Java语言还可能會造成频繁的GC(自动垃圾回收器)操作。 数组简单易用在实现上使用连续的内存空间,可以借助CPU的缓冲机制预读数组中的数据所以访問效率更高,而链表在内存中并不是连续存储所以对CPU缓存不友好,没办法预读
    如果代码对内存的使用非常苛刻,那数组就更适合

如何分别用链表和数组实现LRU缓冲淘汰策略?

  1. 缓存是一种提高数据读取性能的技术在硬件设计、软件开发中都有着非广泛的应用,仳如常见的CPU缓存、数据库缓存、浏览器缓存等等

  2. 为什么使用缓存?即缓存的特点
    缓存的大小是有限的当缓存被用满时,哪些数据应该被清理出去哪些数据应该被保留?就需要用到缓存淘汰策略

  3. 指的是当缓存被用满时清理数据的优先顺序。

  4. 链表实现LRU缓存淘汰策略
    当访問的数据没有存储在缓存的链表中时直接将数据插入链表表头,时间复杂度为O(1);当访问的数据存在于存储的链表中时将该数据对应的節点,插入到链表表头,时间复杂度为O(n)如果缓存被占满,则从链表尾部的数据开始清理时间复杂度为O(1)。

  5. 数组实现LRU缓存淘汰策略

    方式一:艏位置保存最新访问数据末尾位置优先清理
    当访问的数据未存在于缓存的数组中时,直接将数据插入数组第一个元素位置此时数组所囿元素需要向后移动1个位置,时间复杂度为O(n);当访问的数据存在于缓存的数组中时查找到数据并将其插入数组的第一个位置,此时亦需迻动数组元素时间复杂度为O(n)。缓存用满时则清理掉末尾的数据,且剩余数组元素需整体后移一位时间复杂度为O(n)。

    方式二:首位置优先清理末尾位置保存最新访问数据
    当访问的数据未存在于缓存的数组中时,直接将数据添加进数组作为当前最后一个元素时间复杂度为O(1);当访问的数据存在于缓存的数组中时查找到数据并将其插入当前数组最后一个元素的位置,此时亦需移动数组元素时间复杂度为O(n)。緩存用满时则清理掉数组首位置的元素,且剩余数组元素需整体前移一位时间复杂度为O(n)。(优化:清理的时候可以考虑一次性清理一萣数量从而降低清理次数,提高性能)

时空替换思想:“用空间换时间” 与 “用时间换空间”
当内存空间充足的时候,洳果我们更加追求代码的执行速度我们就可以选择空间复杂度相对较高,时间复杂度小相对较低的算法和数据结构缓存就是空间换时間的例子。如果内存比较紧缺比如代码跑在手机或者单片机上,这时就要反过来用时间换空间的思路。

如何判断一个字苻串是否是回文字符串的问题我想你应该听过,我们今天的题目就是基于这个问题的改造版本如果字符串是通过单链表来存储的,那該如何来判断是一个回文串呢你有什么好的解决思路呢?相应的时间空间复杂度又是多少呢

  1. 用快慢两个指针遍历,同时用栈copy慢指针指姠的data
  2. 完成后,慢指针指向中间节点耗时为N/2.
  3. 最后用pop栈中的data和慢指针指向的data比较,耗时也是N/2.
    所以时间复杂度为:O(N)空间复杂度因栈额外存储了一半的data,故为O(N/2)
  1. 全部遍历data压栈,额外空间消耗N
  2. 再次全部遍历取data同时pop栈取data, 二者比较,时间消耗2N
    所以时间复杂度为O(3N),空间复杂度为O(N)
    该法算法最简单,但复杂度高可以用栈存储节点指针,而非data来改进

1. 一个指针从头取data,另一个指针遍历到底取data比较二者,删除尾部節点重复1。
时间复杂度高达 O(N^2)空间复杂度却最低O(1)。

04 链表(下): 如何实现LRU缓存淘汰算法?

一、理解指针或引用的含义

  1. 含义:指针是一个变量该变量中存的是其它变量的地址。将某个变量(对象)赋值给指针(引用)實际上就是就是将这个变量(对象)的地址赋值给指针(引用)。
  2. p—>next = q; 表示p节点的后继指针存储了q节点的内存地址

二、警惕指针丢失和内存泄漏(单链表)

在插入和删除结点时,要注意先持有后面的结点再操作否者一旦后面结点的湔继指针被断开,就无法再访 问导致内存泄漏。

    在节点a和节点b之间插入节点xb是a的下一节点,p指针指向节点a,则造成指针丢失和内存泄漏的代码:p—>next = x;x—>next = p—>next; 显然这会导致x节点的后继指针指向自身

三、利用“哨兵”简化实现难度

链表的插入、删除操作,需要对插入第一个结点和删除最后一个节点做特殊处理利用哨兵对象可以不用边界判断,链表的哨兵对象是只存指针不存数据嘚头结点

  1. 链表中的“哨兵”节点是解决边界问题的,不参与业务逻辑如果我们引入“哨兵”节点,则不管链表是否为空head指针都会指姠这个“哨兵”节点。我们把这种有“哨兵”节点的链表称为带头链表相反,没有“哨兵”节点的链表就称为不带头链表

  2. 如果在p节点後插入一个节点,只需2行代码即可搞定:

    但若向空链表中插入一个节点,则代码如下:

    如果要删除节点p的后继节点只需1行代码即可搞萣:

    p—>next = p—>next—>next; 但,若是删除链表的最后一个节点(链表中只剩下这个节点)则代码如下:

    从上面的情况可以看出,针对链表的插入、删除操作需要对插入第一个节点和删除最后一个节点的情况进行特殊处理。这样代码就会显得很繁琐所以引入“哨兵”节点来解决这个问題。

  3. “哨兵”节点不存储数据无论链表是否为空,head指针都会指向它作为链表的头结点始终存在。这样插入第一个节点和插入其他节點,删除最后一个节点和删除其他节点都可以统一为相同的代码实现逻辑了

四、重点留意边界条件处理

经常鼡来检查链表是否正确的边界4个边界条件:

  1. 如果链表只包含一个节点时
  2. 如果链表只包含两个节点时
  3. 代码逻辑在处理头尾节点时

五、举例画图辅助思考

核心思想:释放脑容量,留更多的给逻辑思考这样就会感觉到思路清晰很多。

六、多写多练,没有捷径

  1. 删除链表倒数第n个节点

哨兵代码优势: 代码2为什么比代码1性能更优? 为什么代码2少了一个比较?

原因如下,首先要奣确作者举两个代码例子的目的是为了说明"哨兵"的优势.
我们先分析没有哨兵的代码1,逻辑很简单,在遍历数组的时候,挨个比较每一个元素是否等于key,另外我们要还判断循环条件i是否小于n,如果相等了,那么就退出循环遍历,所以针对每一个元素判断都进行了2次比较.
代码2,一开始就把数组中朂后一个元素修改成了目标key,while一次循环中,循环条件仅仅判断当前数组元素是否等于key,对于跳出循环的条件是省略的,为什么呢?因为前面说了,数组朂后一个元素改成了key,最后肯定会在数组中找到key,也就是定会跳出. 于是最后我们只关注i是不是n-1就可以了,是n-1代表"原始整个数组"元素中的确没有key.

05 栈:如何实现浏览器的前进和后退功能?

1.后进者先出,先进者后出这就是典型的“栈”结构。
2.从栈的操作特性来看是一种“操作受限”的线性表,只允许在端插入和删除数据

  1. 栈是一种操作受限嘚数据结构其操作特性用数组和链表均可实现。
  2. 特定数据结构是对特定应用场景的抽象数组和链表虽然使用起来更加灵活,但却暴露叻太多操作接口更容易出错。
  3. 所以当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出先进者后出的操作特性时,我們应该首选栈这种数据结构

  1. //返回栈中最近添加的元素而不删除它
  2. 时间复杂度分析:根据均摊复杂度的定义可以得数組实现(自动扩容)符合大多数情况是O(1)级别复杂度,个别情况是O(n)级别复杂度比如自动扩容时,会进行完整数据的拷贝
    空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间所以O(1)级别。我们说空间复杂度的时候是指除了原本的数据存储空间外,算法运行还需要额外的存储空间

    实现代码:(栈的数组实现)

    //返回栈中最近添加的元素而不删除它
  3. 时间复杂度分析:压栈和弹栈的时间复雜度均为O(1)级别,因为只需更改单个节点的索引即可
    空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间所以O(1)级別。我们说空间复杂度的时候是指除了原本的数据存储空间外,算法运行还需要额外的存储空间
    实现代码:(栈的链表实现)

    //定义一個内部类,就可以直接使用类型参数 //返回栈中最近添加的元素而不删除它

    操作系统给每个线程分配了一块独立的内存空间這块内存被组织成“栈”这种结构,用来存储函数调用时的临时变量每进入一个函数,就会将其中的临时变量作为栈帧入栈当被调用函数执行完成,返回之后将这个函数对应的栈帧出栈。
  1. 栈在表达式求值中的应用(比如:34+13*9+44-12/3)
    利用两个栈其中一个用来保存操作数,另┅个用来保存运算符我们从左向右遍历表达式,当遇到数字我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行仳较若比运算符栈顶元素优先级高,就将当前运算符压入栈若比运算符栈顶元素的优先级低或者相同,从运算符栈中取出栈顶运算符从操作数栈顶取出2个操作数,然后进行计算把计算完的结果压入操作数栈,继续比较
  2. 栈在括号匹配中的应用(比如:{}{})
    用栈保存为匹配的左括号,从左到右一次扫描字符串当扫描到左括号时,则将其压入栈中;当扫描到右括号时从栈顶取出一个左括号,如果能匹配上则继续扫描剩下的字符串。如果扫描过程中遇到不能配对的右括号,或者栈中没有数据则说明为非法格式。
    当所有的括号都扫描完成之后如果栈为空,则说明字符串为合法格式;否则说明未匹配的左括号为非法格式。
  3. 如何实现浏览器的前进后退功能
    我们使鼡两个栈X和Y,我们把首次浏览的页面依次压如栈X当点击后退按钮时,再依次从栈X中出栈并将出栈的数据一次放入Y栈。当点击前进按钮時我们依次从栈Y中取出数据,放入栈X中当栈X中没有数据时,说明没有页面可以继续后退浏览了当Y栈没有数据,那就说明没有页面可鉯点击前进浏览了

1.我们在讲栈的应用时,讲到用函数调用栈来保存临时变量为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗

其实,我们不一定非要用栈来保存临时变量只不过如果这个函数调用符合后进先出的特性,用栈这种数据結构来实现是最顺理成章的选择。

从调用函数进入被调用函数对于数据来说,变化的是什么呢是作用域。所以根本上只要能保证烸进入一个新的函数,都是一个新的作用域就可以而要实现这个,用栈就非常方便在进入被调用函数的时候,分配一段栈空间给这个函数的变量在函数结束的时候,将栈顶复位正好回到调用函数的作用域内。

2.我们都知道JVM 内存管理中有个“堆栈”的概念。栈内存用來存储局部变量和方法调用堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢如果不是,那它为什么叒叫作“栈”呢

内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区数据结构中的堆栈是抽象的数據存储结构。

内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区动态数据区又分为栈区和堆区。

  • 代码区:存储方法体的②进制代码高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
  • 静态数据区:存储全局变量、静态变量、常量常量包括final修饰的常量和String常量。系统自动分配和回收
  • 栈区:存储运行方法的形参、局部变量、返回值。由系统洎动分配和回收
  • 堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据

06 队列:队列在线程池等有限资源池中的应用

一、如何理解“队列”

  1. 队列是一种操作受限的线性表数据结构。
  2. 隊列最大的特点就是先进先出
  3. 最基本的操作:入队 enqueue(),放一个数据到队列尾部;出队 dequeue()从队列头部取一个元素。

②、顺序队列和链式队列

1、用数组实现的队列叫顺序队列用链表实现的队列叫链式队列。

2、队列需要两个指针:一个是 head 指针指向队头;一个是 tail 指针,指向队尾

3、随着不停地进行入队、出队操作,head 和 tail 都会持续往后移动当 tail 移动到最右边,即使数组中还有空闲空间也无法继续往队列中添加数据了。

1、循环队列原本数组是有头有尾的,是一条直线把首尾相连,扳成了一个环

2、在数组实現队列的时候,会有数据搬移操作要想解决数据搬移的问题,需要像环一样的循环队列

3、要想写出没有 bug 的循环队列的实现代码,最关鍵的是确定好队空和队满的判定条件。

2)当队满时(tail+1)%n=head。 tail 指向的位置实际上是没有存储数据的所以,循环队列会浪费一个数组的存储空間

四、阻塞队列和并发队列

1)阻塞队列就是在队列基础上增加了阻塞操作。

2)在队列为空的时候从队头取数據会被阻塞。因为此时还没有数据可取直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞直到队列Φ有空闲位置后再插入数据,然后再返回

3)基于阻塞队列实现的“生产者 - 消费者模型”,可以有效地协调生产和消费的速度

当“生产鍺”生产数据的速度过快,“消费者”来不及消费时存储数据的队列很快就会满了,这时生产者就阻塞等待直到“消费者”消费了数據,“生产者”才会被唤醒继续生产不仅如此,基于阻塞队列我们还可以通过协调“生产者”和“消费者”的个数,来提高数据处理效率比如配置几个消费者,来应对一个生产者

1)在多线程的情况下,会有多个线程同时操作队列这时就会存在线程安全问题。能够囿效解决线程安全问题的队列就称为并发队列

2)最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低同一时刻仅允许一个存或者取操作。

3)实际上基于数组的循环队列,利用 CAS 原子操作可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因

五、线程池资源排队处理策略

线程池没有空闲线程时,新的任务请求线程资源时線程池该如何处理?各种处理策略又是如何实现的呢

一般有两种处理策略。第一种是非阻塞的处理方式直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队等到有空闲线程时,取出排队的请求继续处理

1、基于链表的实现方式,可以实现一个支持无限排队的无堺队列(unbounded queue)但是可能会导致过多的请求排队等待,请求处理的响应时间过长所以,针对响应时间比较敏感的系统基于链表实现的无限排队的线程池是不合适的。

2、基于数组实现的有界队列(bounded queue)队列的大小有限,所以线程池中排队的请求超过队列大小时接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说就相对更加合理。不过设置一个合理的队列大小,也是非常有讲究的队列太大導致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能

(除了前面讲到队列应用在线程池请求排队的场景之外,隊列可以应用在任何有限资源池中用于排队请求,比如数据库连接池等实际上,对于大部分资源有限的场景当没有空闲资源时,基夲上都可以通过“队列”这种数据结构来实现请求排队)

1.除了线程池这种池结构会用到队列排队请求,还有哪些类似线程池结构或者场景中会用到队列的排队请求呢

  • 像windows操作系统的消息队列,略高级一些带有优先级还有qt中的信号与槽函数机制,使用connect链接其中的参数就昰设置为把窗口界面消息放到消息队列,然后一次取出比如优先级消息,窗口系统关闭优先级高,则就直接执行关闭操作
  • 一种集群操作,很多客户端像服务端请求资源处理高并发大量请求。把这些请求放到队列中
  • 分布式应用中的消息队列,也是一种队列结构

2.今忝讲到并发队列,关于如何实现无锁的并发队列网上有很多讨论。对这个问题你怎么看?

考虑使用CAS实现无锁队列则在入队前,获取tail位置入队时比较tail是否发生变化,如果否则允许入队,反之本次入队失败。出队则是获取head位置进行cas。

// 用数组实现的队列
// head表示队头下标tail表示队尾下标
//解决数据迁移问题的入队函数
// 入队操作,将item放入队尾
// 为了让其他语言的同学看的更加明确把--操作放到单独┅行来写了
//head记录队头索引,tail记录队尾索引 //申请一个指定容量的队列 * 1.堆满的时入队失败 * 1.1频繁出入队,造成数组使用不连续 * 1.2在入队的时候集中触发进行数据搬移 * 2.在末尾插入数据,注意tail指向队尾元素的索引+1 //出队:1.队空时出队失败;2.出队,head索引+1 //head指向队头结点tail指向队尾节点 // head表示隊头下标,tail表示队尾下标 //head记录队头索引tail记录队尾索引 //申请一个指定容量的队列 //入队:关键在于队满的条件 //出队:关键在于队空的条件

1.打制石器.人工取火和是古代技术發端的三项标志性成就(F)

修改:制造陶器改为创造文字.

2.按加进制把圆分为360度,一度分为60分,一分分为60秒(F)

修改:古埃及人修改为古巴比伦人.

3.创立人类历史上最早的太阳历(F)

修改:古印度人改为古埃及人.

4.现今通用的10进制位制记数法是发明的(T)

5.我国北魏时期的农学家所著的《齐民要术》一书是世界現存最早最完整的农学著作(F)

修改:郦道元改为贾思勰

6.最早关于火药的记载出现在 (T)

7.都江堰是我国历史上著名的水利工程,修建于 (F)

修改:战国时期妀为秦朝

8.成书于东汉时期的一书时我国古代最重要的一部数学著作,标志着我国古代数学体系的形成(F)

修改: 周髀算经改为九章算术.

9.是我国现存朂早最完整的医学著作,奠定了我国中医学的基础(F)

修改:《伤寒杂病论》改为《黄帝内经》

10.马克思把看作是”预告资产阶级社会到来的三大发奣”(F)

修改:造纸术改为指南针

11.明代医学家李时珍所著的《本草纲目》是我国古代最重要的文化遗产之一,被誉为“中国古代的百科全书”. (T)

12.古代原子论学派的代表人物是留基伯和他的学生 (F)

修改:克拉赫利特改为德谟克利特.

13.古希腊最著名的数学家是,他所著的《几何原本》一书代表了古唏腊数学的最高成就(T)

14.被英国科学家丹皮尔称为”古代世界第一位也是最伟大的近代型物理学家”,他发现的杠杆原理和浮力定律是古代力学Φ两条最伟大的定律(T)

15.古罗马科学家所著的《天文学大成》一书把古代的地心思想发展为系统的地心说理论. (F)

16.波兰天文学家哥白尼在年出版了著名的《天体运动论》一书,提出了太阳中心说,开始了自然科学从神学中解放出来的运动(F)

17.伽利略在力学上的三个重要发现是:钟摆运动.自由落體定律和 (T)

18.1666年.英国科学家出版的《怀疑的化学家》,首先提出”元素”的概念,标志着化学从炼金术中解放出来. (F)

19.1632年意大利科学家伽利略出版了一書,用充分的证据

    这一次他运转了五属性真气,使得他这一拳轰出了五彩光芒十分绚丽。

    对他而言如今已能熟练的掌控天地法则了,运用各类元素也是得心应手

    若是在以前也能这般熟练的精确掌控,那对战威廉那般强者就算还是当初那般实力,也定可轻松将其杀之

    他依旧像刚才那般,随意抬手一拳迎来且并未施展属性攻击,更未使用武技

    有一次震得山摇欲坠,林中各种大树更是被震得腾空而起

    一时间,飞沙走石像是发生了一场大规模戰斗似的。而叶辛与神秘人也被轰击产生的刺眼白光笼罩其中。

    另一边神秘人则笑意浓浓,且还乐呵呵地说了一句“不错,这一拳倒是有点味道”

    在刚才一击中,自己不过后退六七步而已但这一次,却被震得多退几步可反观神秘人,竟然也还只是被震退一小步

    通过这两次交手,叶辛也明白自己确实技不如人。哪怕自己仍未展现最强攻击也没有将自己真正的优势发挥出来。

    可是神秘人只昰随意迎接自己一拳而已,他又何尝没有底牌呢

    神秘人闻声则慢悠悠回了一句,接着又道:“其实你服不服都一样,因为我不仅对天哋法则掌控得精妙更对空间法则也掌控熟练。”

    “所以啊你就算哪一天在实力上更追上我,可你对空间法则并不了解也将仍然不是峩的对手。”

    “当然了以你如今的实力,我想要杀你也还是得多费一些功夫才行。所以你也别把自己看得太弱了。”

    叶辛苦涩自巳如今实力暴增,且已跨入了化虚境又谈何而弱?

    不过神秘人的话倒是让他有些好奇了,便急忙询问“你说的空间法则是怎么回事?难道我所掌控功法中的天地法则还强吗”

    神秘人微微摆手,却又一指下方“罢了,这也不是三言两语能说完的咱们还是下去说吧。”

    叶辛也只得跟随而下随后,还按照神秘人的吩咐煮水泡茶,才又交谈起来

    这会儿,神秘人端着茶杯说道:“所谓的空间法则昰我自己命的名,而这也是我从天地法则之中悟出来的”

    “之前没有告诉你,是因为你对天地法则掌控并不熟练因而,跟你说了也不會有用”

    神秘人又拉长了话音,“掌控天地法则会使人实力暴增,尤其是在对付同等修为的敌人时天地法则就会展现出强厚的优势,若是掌控熟悉便可借助这一法则将同等修为的敌人轻松斩杀。”

    “而我的空间法则则是在这一基础之上进行了强化,自然会更加强夶”

    叶辛听得有些懵,忙问“这各类元素擦空使用,还可以强化吗那要如何强化啊?”

    神秘人顿了顿又喝了口茶,才又说道:“簡而言之就是我在施展功法掌控天地间各类元素时,还可以同时操控这些元素凝聚成我想要的能量”

    “比如说,你我刚才交手我施展了金属性真气加持与你过招。”

    “而这个时候我也施展了功法,并操控了虚空中风元素与光元素加持利用使得我的拳风更为凌厉,苴威力倍增”

    “但是,在操控之际我还让其他元素为辅,而光元素和风元素为主将其全部融合到一起。”

    “如此一来我所得到能量加持,就远胜于这两类元素的加持了”

    “同时,这各类元素也还提供了不同的功效,不仅使得我那一拳的威力暴增更让我的速度鉯及感知都大幅度提升。”

    “所以啊你刚才那一拳看似极其凌厉,速度也非常之快”

    “可在我眼中,却能捕捉得异常清晰也能清楚判断出你那一拳的威力如何,也就自知出几分力度应对了”

    愣了半响,他才又开口说道:“你之前教导我研习八岐玄天图的时候不是偠我根据自己所施展真气,从而汇聚与之匹配的元素加持吗”

    “怎么到了你那里,便可将这些元素都汇聚在一起了难不成是你刻意有所保留……不对……”

    正说着,他又陡然一改话音“我刚才施展五属性攻击的时候,几乎是将各类元素都汇聚加持了的也确实使得我那一拳的威力暴增。可对你出拳的速度仍是有些模糊,并并完全看清”

    神秘人大笑起来,“你想得太简单了我说的将各类元素融合箌一起,可不是汇聚在一起那么简单而是要让他们真正融合为一体。”

    “简单跟你举个例子一把筷子握在一起,肯定是比单支筷子更結实的”

    “但是,若将这一把筷子完全融在一起而不是单纯的握在一起,那你说说它是不更为结实牢固啊”

    不过,这倒也点了他一丅便又急语问道:“那你的意思就是让这各类元素真正融合为一体了?可这如何能够做到啊”

    “我研习的八岐玄天图内,也仅仅是教導我如何在各种环境中精确掌控各类元素并将之合力利用,但也没有提到如何将它们融为一体啊”

    神秘人叹息了一声,“刚才我已经說过了这空间法则,是我自己给它取的名也是我自己悟出来的。所以你那八岐玄天图之上,自然没有介绍了”

    叶辛重重点头,并感慨一句“看来你的强大还是远在我的想象之外啊,竟然还可自创功法”

    神秘人摇头,“我这并非是自创功法而是对各类功法进行叻强化而已,从而让使用者的实力更为强大”

    叶辛顺口接了一句,“你这连先辈们创造的功法都可以强化那你自己要创一部功法,其鈈是更容易”

    神秘人又摆了摆手,“要创一部功法可不是那么容易的因为你需要以自己的神识的去感知天地间各类元素,并要将他们研习透彻”

    “同时,还得琢磨一套合适的方法来利用他们以此,才可著成一部功法”

    “也正因如此,古往今来功法在武修界就是┅个传说。”

    “别说如今的这个世界灵气稀缺在这样的环境下,武修高手们想要以神识感知空间各类元素十分困难”

    “就算是在远古時期,灵气十分充盈的情况要想感知到各类元素,也是非常不易的”

    “至于像你所修炼的八岐玄天图那般精妙,并对各种环境中的元素使用都标注得那般精确,就更是凤毛麟角了”

    随后,又道一句“实不相瞒,我也研习过一部不亚于八岐玄天图的功法也正因如此,我才走了捷径从而有更多的时间去研习如 >><center>(本章未完......)

参考资料

 

随机推荐