一、计算机发展四个阶段
第一阶段:电子管计算机
特点:
集成度小,占用空间大
功耗高,运行速度慢
操作复杂,更换程序需要接线
第二阶段:晶体管计算机
特点:
集成度相对提高,空间占用相对小
功耗相对较低,运行速度较快
操作相对简单,交互更加方便
第三阶段:集成电路计算机
特点:
体积更小
功耗更低
计算速度更快
操作系统雏形: IBM 推出的 System/360
第四阶段:超大规模集成电路计算机
特点:
一个芯片集成了上百万的晶体管
速度更快、体积更小、价格更低、更被大众接受
用途更丰富:文本处理、表格处理、高交互的游戏与应用
第五阶段:未来计算机
1. 生物计算机
2. 量子计算机
二、微型计算机发展史
单核 CPU
多核 CPU
三、计算机分类
1. 超级计算机
特点:
功能强、运算速度快、存储容量大
多用于国家高科技领域和尖端技术研究(天气预报、海洋监测、科学计算、...)
标记运算速度的单位:TFlop/s
1TFlop/s = 每秒一万亿次浮点计算
超级计算机排名:
Summit (IBM)
太湖之光 (中国)
Sierra (IBM)
国内超级计算机排名:
太湖之光
天河二号
天河一号
2. 大型计算机
特点:
大型机、大型主机、主机
性能高、可处理大量数据与复杂运算
3. 迷你计算机(服务器)
特点:
也称为小型机、普通服务器
不需要特殊的空调场所
具备不错的算力,可以完成较复杂的运算
去 IOE 行动:把大型机替换为普通服务器的集群。
4. 工作站
特点:
高端的通用微型计算机,比个人计算机性能更强大
类似于普通台式电脑,体积较大,但性能强劲
5. 微型计算机
特点:
个人计算机
台式机、笔记本电脑、一体机
包含计算机所必备的硬件
四、计算机体系结构
1. 冯诺依曼体系
将程序指令和数据一起存储的计算机设计概念结构。
存储程序指令,设计通用电路
冯诺依曼体系:
必须有存储器
必须有控制器
必须有运算器
必须有输入设备
必须有输出设备
冯诺依曼瓶颈
CPU 和 存储器速率之间的问题
CPU = 运算器 + 控制器
2. 现代计算机结构
现代计算机结构
在冯诺依曼体系结构基础上进行修改
解决 CPU 与 存储设备之间的性能差异问题
CPU = 运算器 + 控制器 + 存储器(寄存器)
五、计算机的层次与编程语言
1. 程序翻译(编译) 与 程序解释
编译:生成可执行文件
解释:不生成可执行文件
2. 计算机的层次与编程语言
虚拟机器
应用软件
应用层
系统软件
高级语言层
汇编语言层
操作系统层
实际机器
传统机器层
微程序机器层
硬件逻辑层
硬件逻辑层
- 门、触发器等逻辑电路组成
- 属于电子工程领域
微程序机器层
- 编程语言是微指令集
- 微指令所组成的微程序直接交由硬件执行
- 生产硬件公司程序员编写
传统机器层
- 编写语言是 CPU 指令集(机器指令)
- 编程语言直接与硬件相关(例如:Intel 与 AMD 的CPU指令集不同)
微指令、微程序、机器指令
一条机器指令对应一个微程序
一个微程序对应一组微指令
操作系统层
- 向上提供了简易的操作界面
- 向下对接了指令系统,管理硬件资源
- 操作系统是位于软件和硬件之间的适配层
汇编语言层
- 编程语言是汇编语言
- 汇编语言可以翻译成可执行的机器语言
- 汇编器,完成翻译过程
高级语言
- Python、Java、C++、C、Golang、......
应用层
- office系列软件等
- 满足计算机针对某种用途而专门设定
六、计算机的计算单位
1、容量单位
1 byte = 8 bit
1 KB = 1024 byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB
1 EB = 1024 PB
1024 = 2^10
1 GB = 1024 * 1024 * 1024 KB
1 GB = 1024 * 1024 * 1024 * 8 bit
2、速度单位
网络速度
100M = 100M/s
网络常用单位为:Mbps
100M/s = 100Mbps = 100Mbit/s
100Mbit/s = (100/8)MB/s = 12.5MB/s
CPU 速度
*** CPU 速度:***
- CPU 速度一般体现为 CPU 的时钟频率
- CPU 的时钟频率单位一般是 赫兹(Hz)
- 主流 CPU 的时钟频率都是 2GHz 以上
- 2GHz = 2*1000^3 Hz = 每秒20亿次,表示 CPU 每秒钟高低电平变换的频率
赫兹Hz
- 秒分之一,每秒钟的周期性变动重复次数的计量
- 赫兹并不是描述计算机领域所专有的单位
七、计算机的字符与编码集
1、字符编码集的历史
ASCII 码
1、使用 7 个bit就可以完全表示 ASCII 码
2、包含 95 个可打印字符, 33个不可打印字符(包含控制字符),共计 128 个
Extended ASCII 码
用 8 bit 代替 7 bit,128 个字符扩充为 256 个字符
包含了:
- 常见的数学运算符
- 带音标的欧洲字符
- 其他常用字符、表格符等
字符编码集的国际化
2、中文编码集
中文编码集
一、GB2312 《信息交换用汉字编码集——基本集》
- 共收录了 7445 个字符(6763个汉字 和 682个其他符号)
二、GBK
- 《汉字内码扩展规范》
- 向下兼容 GB2312,向上支持国际 ISO 标准
- 收录了 21003 个汉字,支持全部中日韩文字
三、Unicode (兼容全球的字符编码集)
- 统一码、万国码、单一码
- Unicode定义了世界通用的符号集,UTF-* 实现了编码
- utf-8 以字节为单位对 Unicode 进行编码
-
Windows 系统默认使用 GBK 编码
-
编程推荐使用 utf-8 编码
八、计算机总线与 I/O 设备
1、计算机总线 Bus
总线概述
总线概述
解决不同设备间通信问题。
总线分类
- 片内总线:芯片内部的总线。高集成度芯片内部的信息传输线
- CPU 内部链接寄存器与寄存器、寄存器与控制器、运算器、高速缓存等
- 系统总线
- 链接计算机外围设备的的各种总线(硬盘、IO设备、USB插槽、...
- 数据总线、地址总线、控制总线
数据总线
- 双向传输各个部件的数据信息
- 数据总线的位数(总线宽度)是数据总线的重要参数。一般与 CPU 位数相同(32位、64位)
地址总线
- 指定源数据或目的数据在内存中的地址
- 地址总线位数与存储单元有关
- 地址总线位数=n,寻址范围:0~2^n
控制总线
- 发出各种控制信号的传输线
- 控制信号经由控制总线从一个组件发给另外一个组件
- 控制总线可以监视不同组件之间的状态(就绪/未就绪)
USB : Universal Serial Bus, 通用串行总线。
总线仲裁
1、为什么需要仲裁
解决不同设备使用总线的优先级问题。
通过仲裁控制器实现
2、总线仲裁的方法
① 链式查询方法
好处:
- 电路复杂度低,仲裁方式简单
坏处:
- 优先级低的设备难以获得总线使用权
- 对电路故障敏感
② 计时器定时查询法
仲裁控制器对设备编号并使用计数器累计计数
2、接收到仲裁信号后,往所有设备发出计数值
计数值与设备编号一致则获得总线使用权
③ 独立请求方法
1、每个设备均有总线独立连接仲裁器
2、设备可单独向仲裁器发送请求和接收请求
3、当同时收到多个请求信号,仲裁器有权按优先级分配使用权
4、好处:
- 响应速度快,优先顺序可动态改变
5、缺点:
- 设备连线多,总线控制复杂
2、计算机输入/输出设备
1、常见的输入输出设备
字符输入设备
键盘
图形输入设备
鼠标、数位板(手写板)、扫描仪、
图像输出设备
显示器、打印机、投影仪
2、输入输出接口的通用设计
输入输出接口的通用设计
数据线
- 是IO设备与主机之间进行数据交换的传送线
- 单向传输数据的数据线
- 双向传输数据的数据线
状态线
- 向主机报告IO设备状态的数据线
- 查询设备是否已经正常连接并就绪
- 查询设备是否已经被占用
命令线
- CPU 向设备发送命令的信号线
- 发送读写信号
- 发送启动停止信号
设备选择线
- 主机选择IO设备进行操作的信号线
- 对连接在总线上的设备进行选择
3、CPU 与 IO 设备的通信方法
1. 程序中断法
1、当外围IO设备就绪时,向 CPU 发出中断信号
2、CPU 有专门的电路响应中断信号
程序中断法:
提供低速设备通知 CPU 的一种异步的方式
CPU 可以高速运转同时兼顾低速设备的响应
2. DMA(直接存储器访问)
1、DMA 直接连接主存与IO设备
2、DMA 工作时不需要 CPU 参与
CPU ←→ 主存 ←→ DMA ←→ IO设备
当主存与IO设备交换信息时,不需要中断 CPU
九、计算机存储器
1、计算机存储器概览
存储器的分类
一、存储介质分类:
① 半导体存储器:内存、U盘、固态硬盘
② 磁存储器:磁带、磁盘
二、存取方式分类:
① RAM(随机存储器):随机读取、与位置无关。
② 串行存储器:与位置有关、按顺序查找。
③ ROM(只读存储器):只读不写。
存储器的层次结构
1、购买存储器时考虑因素:读写速度、存储容量、价格。
2、容量 + 价格 → 位价:每比特位价格。
存储器层次结构:
1、缓存:速度最快,价格最高。
2、主存:速度适中,价格适中。
3、辅存:速度慢,价格低。
缓存-主存层次:
1、原理:局部性原理
2、实现:在 CPU 与主存之间增加一层速度快(容量小)的 Cache
3、目的:解决主存速度不足的问题。
局部性原理:
CPU 访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
时间局部性、空间局部性
主存-辅存层次:
1、原理:局部性原理
2、实现:主存之外增加辅助存储器(磁盘、SD卡、U盘等)
3、目的:解决主存容量不足问题
2、计算机的主存储器与辅助存储器
主存储器:内存。
辅助存储器:磁盘。
主存储器(内存):
一、基本概念:
1、RAM:Random Access Memory
2、RAM 通过电容存储数据,必须每隔一段时间刷新一次
3、如果断电,那么一段时间后将丢失所有数据
二、结构:
半导体存储体、读写电路、控制电路、驱动器、译码器
32位系统:最大支持 4GB内存(因为地址总线最多32位)
64位系统:最大支持 234 GB内存
辅助存储器(磁盘)
1、术语:磁道、扇区、磁头、磁头位置、磁头方向
2、磁盘表面是可磁化的硬磁特性材料
3、移动磁头径向运动读取磁道信息
磁盘调度算法:
1、先来先服务算法
按顺序访问进程的磁道读写需求
2、最短寻道时间算法
优先访问离磁头最近的磁道
3、扫描算法(电梯算法)
每次只往一个方向移动(由内向外或者由内向外)
到达一个方向需要服务的尽头再反方向移动
4、循环扫描算法
基于扫描算法,但只由一个方向读取(从内向外或从外向内,读取多次)
3、计算机的高速缓存
高速缓存目的:解决 CPU 与主存的速度不匹配问题。
一、高速缓存的工作原理
字:
是指存放在一个存储单元中的二进制代码组合。
字块:
存储在连续存储单元中而被看做是一个单元的一组字。
字的地址包含两个部分:
1、前 m 位指定字块的地址
2、后 b 位指定字在字块中的地址
例:
高速缓存的工作原理指标:
1、命中率
2、访问效率
命中率:
1、命中率是衡量缓存的重要性能指标
2、理论上 CPU 每次从高速缓存取数据都能取到时,命中率为 1
3、命中率计算:
访问效率:
命中率、访问效率、平均访问时间计算案例:
二、高速缓存的替换策略
高速缓存替换策略
1、随机算法
2、先进先出算法(FIFO)
1、将高速缓存看做是一个先进先出的队列
2、优先替换最先进入队列的字块
3、最不经常使用算法(LFU)
1、优先淘汰最不经常使用的字块
2、需要额外的空间记录字块的使用频率
4、最近最少使用算法(LRU)
1、优先淘汰一段时间内没有使用的字块
2、有多种实现方法,一般使用双向链表实现
3、把当前访问节点置于链表前面(保证链表头部节点是最近使用的)
十、计算机指令系统
1、机器指令形式
机器指令形式:
1、机器指令主要由两部分组成:操作码、地址码
2、操作码:
① 指明指令所要完成的操作
② 操作码的位数反映了机器的操作种类(比如 8 位,最多有 28 = 256 种操作)
3、地址吗:
① 地址码直接给出操作数或者操作数的地址
② 分三地址指令、二地址指令 和 一地址指令
三地址指令: 操作码(OP)、地址1、地址2、地址3。
(addr1) OP (addr2) → (addr3)
二地址指令: 操作码(OP) 、地址1、地址2
(addr1) OP (addr2) → (addr1) 或 (addr2),即将结果放入 addr1 或 addr2 中
一地址指令: 操作码(OP)、地址1
零地址指令(无地址指令):
在机器指令中无地址码
空操作、停机操作、中断返回操作等
2、机器指令的操作类型
机器指令的操作类型:
1、数据传输类型:
① 可以发生在寄存器之间、寄存器与存储单元、存储单元之间传送
② 数据读写、交换地址数据、清零置一等操作
2、算数逻辑操作类型
操作数之间的加减乘除运算
操作数的与、或、非等逻辑位运算
3、移位操作
数据左移(相当于乘以2)、数据右移(相当于除以2)
完成数据在算数逻辑单元的必要操作
4、控制指令
等待指令、停机指令、空操作指令、中断指令等
3、机器指令的寻址方式
一、指令寻址
1、顺序寻址
2、跳跃寻址
二、数据寻址
1、立即寻址
指令中包含操作数
运行时无需访问存储器
优点:
速度快
缺点:
地址码位数限制操作数表示范围
2、直接寻址
机器指令中包含操作数在主存的地址
寻找操作数简单,无需计算数据地址
优点:
寻找操作数简单
缺点:
地址码位数限制操作数寻址范围
3、间接寻址
指令地址码给出的是操作数地址的地址
优点:
操作数寻址范围大
缺点:
速度较慢
十一、计算机的控制器
控制器是协调和控制计算机运行的
程序计数器
- 程序计数器用来存储下一条指令的地址
- 循环从程序计数器中拿出指令
- 当指令被拿出时,指向下一条指令
时序发生器
- 电气工程领域,用于发送时序脉冲
- CPU 依据不同的时序脉冲有节奏的进行工作(CPU 的 “节拍器”)
指令译码器
- 指令译码器是控制器的主要部件之一
- 计算机指令由操作码和地址码组成
- 翻译操作码对应的操作以及控制传输地址码对应的数据
指令寄存器
- 指令寄存器也是控制器的主要部件之一
- 从主存或高速缓存取计算机指令
主存地址寄存器
- 保存当前 CPU 正要访问的内存单元的地址
主存数据寄存器
- 保存当前 CPU正要读或写的主存数据
通用寄存器
- 用于暂时存放或传送数据或指令
- 可保存 ALU 的运算中间结果
- 容量比一般专用寄存器要大
十二、计算机的运算器
运算器是用来进行数据运算加工的
数据缓冲器
1、输入缓冲
暂时存放外设送来的数据
2、输出缓冲
暂时存放送往外设的数据
ALU (算数逻辑单元)
- 运算器的主要组成
- 常见的位运算(左右移、与或非)
- 算术运算(加减乘除等)
通用寄存器
- 用于暂时存放或传送数据或指令
- 可保存 ALU 的运算中间结果
- 容量比一般专用寄存器要大
状态字寄存器
- 存放运算状态(条件码、进位、溢出、结果正负等)
- 存放运算控制信息(调试跟踪标记位、允许中断位)
十三、计算机指令的执行过程
1、指令执行过程
1、取指令
从缓存取指令
送到指令寄存器
2、 分析指令
指令译码器译码
发出控制信号
程序计数器 +1
3、执行指令
装载数据到寄存器
ALU 处理数据
记录运算状态
送出运算结果
取指令、分析指令,由控制器负责;执行指令由运算器负责。二者不能同时工作,导致CPU综合利用率不高。
2、CPU 的流水线设计
CPU 流水线设计:
(串行,非流水线模式)取指令→分析指令→执行指令→ 取指令→分析指令→执行指令......
下图为 CPU 流水线模式演示:
十四、进制运算基础知识
1、进制运算基础
*** 进制概述***
1、进制定义:
① 进位制是一种计数方式,亦称进位计数法或位值记数法
② 有限种数字符号来表示无限的数值
③ 使用的数字符号的数目称为这种进位制的基数或底数
2、常见的进制:
十进制、八进制(0o...)、十六进制(0x....)、二十进制、二进制...(0b)、六十进制
二进制运算的基础
1、二进制转十进制:按权展开法。
2、(整数)十进制转二进制:重复相除法。(重复除以2,得商取余数,将余数倒着拼接起来。)
3、(小数)二进制转十进制:按权展开法。
(0.11001)从小数点右边第一位开始。1 * 2-1 + 1 * 2-2 + 0 * 2-3 + 0 * 2-4 + 1 * 2-5
4、(小数)十进制转二进制:重复相乘法。(重复乘以2,得积,取1或0,从上到下将1和0拼起来)
十五、二进制数据的表示方法
1、有符号数与无符号数
计算机表示正负数,使用 原码表示法(0 表示正数,1表示负数)
原码表示法:
1、使用 0 表示正数、1 表示负数
2、规定符号位位于数值第一位
3、表达简单明了,是人类最容易理解的表示法
4、原码表示法存在的问题:
① 按照原码表示法,0 可以有两种表示:00(+0)、10(-0),存在歧义。
② 原码进行运算非常复杂,特别是两个操作数符号不同的时候。
1> 判断两个操作数绝对值大小
2> 使用绝对值大的数减去绝对值小的数
3> 对于符号值,以绝对值大的数为准。
2、二进制的补码表示
补码定义:
3、二进制的反码表示
使用反码目的:
找出原码和补码之间的规律,消除转换过程中的减法。
反码定义:
1、大于等于 0 时,反码等于原码
2、小于 0 时,反码等于
1、正数的原码、补码、反码都一样。
2、负数的反码等于:原码除符号位以外,按位取反。
3、负数的补码等于:反码 + 1。
4、小数的二进制补码表示
规则同正数
十六、二进制数据的运算
1、定点数与浮点数
1、定点数的表示方法:
定点数概念:
小数点固定在某个位置的数,称之为定点数。
定点数的两种表示方法:
1、小数点在符号位与数值为之间。(纯小数)
2、小数点在数值位最后。(纯整数)
对于非纯小数 or 非纯整数需要乘以比例因子以满足定点数的保存格式
2、浮点数表示方法
① 浮点数的表示格式
科学计数法:
尾数必须使用纯小数
② 浮点数的表示范围
浮点数表示范围
1、单精度浮点数(float):使用 4 字节,32位来表达浮点数
2、双精度浮点数:(double):使用 8 字节,64位来表达浮点数
③ 浮点数的规格化
1、尾数规定使用纯小数
2、尾数最高位必须是 1
案例:
3、定点数与浮点数对比
1、当定点数与浮点数位数相同时,浮点数表示的范围更大
2、当浮点数尾数为规格化数时,浮点数的精度更高
3、浮点数运算包含阶码和尾数,浮点数的运算更为复杂
4、浮点数在数的表示范围、精度、溢出处理、编程等方面均优于定点数
5、浮点数在数的运算规则、运算速度、硬件成本方面不如定点数
2、定点数的加减法运算
① 定点数的加法运算
判断溢出
双符号位判断法
1、原来表示正数的符号位 0,用 00 表示。原来表示负数的符号位 1,用 11 表示。
2、双符号位产生的进位丢弃掉。
3、结果的双符号位不同则表示溢出。
② 定点数的减法运算(转换为加法操作)
3、浮点数的加减法运算
浮点数加减法计算步骤:
1、对阶操作
使两个浮点数的阶码一致,使得尾数可以进行运算
2、尾数求和
1、使用补码进行运算
2、减法运算转换为加法运算
3、尾数规格化
对补码进行规格化需要判断两种情况:尾数 > 0 和 尾数 < 0
4、舍入
1、"0 舍 1 入" 法(二进制的四舍五入)
5、溢出判断
1、定点运算双符号位不一致为溢出,浮点运算尾数双符号位不一致不算溢出。
2、浮点运算主要通过阶码的双符号位判断是否溢出。如果规格化后,阶码双符号位不一致,则认为是溢出。
4、浮点数的乘除法运算
浮点数乘法运算法则:
阶码相加,尾数求积
浮点数除法运算法则:
阶码相减,尾数求商
案例:
十七、实现双向链表
1、双向链表的原理与实践
双向链表的优点:
1、可以快速找到一个节点的下一个节点
2、可以快速找到一个节点的上一个节点
3、可以快速去掉链表中的某一个节点
# 双向链表 Python demo
#
# doubly_linked_list.py
# pythonProject
#
# Created by Z. Steve on 2023/1/28.
#
# 实现一个链表节点类
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
def __str__(self):
val = '{%d: %d}' % (self.key, self.value)
return val
def __repr__(self):
val = '{%d: %d}' % (self.key, self.value)
return val
# 实现一个双向链表类
class DoublyLinkedList:
# 构造函数
def __init__(self, capacity=0xffff):
self.capacity = capacity
# 头部节点引用
self.head = None
# 尾部节点引用
self.tail = None
# 保存当前链表所存储的节点数, 默认值是 0
self.size = 0
# 从头部添加节点
def __add_head(self, node):
# 如果 self.head 为空,则表示链表中没有节点
if not self.head:
# 设置当前链表的头部、尾部节点均为 node
self.head = node
self.tail = node
# 设置链表的 head 节点的上一个和下一个节点为 None
self.head.prev = None
self.head.next = None
else:
# 执行 else 表示当前链表的 head 节点不为 空
node.next = self.head
self.head.prev = node
self.head = node
self.head.prev = None
# 添加节点结束后,让链表 size + 1,同时返回这个节点
self.size += 1
return node
# 从尾部添加节点
def __add_tail(self, node):
if not self.tail:
self.head = node
self.tail = node
self.tail.prev = None
self.tail.next = None
else:
node.prev = self.tail
node.next = None
self.tail.next = node
self.tail = node
self.size -= 1
return node
# 删除第一个节点 head
def __rm_head(self):
# 如果头节点为 None,则直接 return
if not self.head:
return
# 将要删除的 head 节点 赋值给 node
node = self.head
if node.next:
self.head = node.next
node.next.prev = None
else:
self.head = self.tail = None
self.size -= 1
return node
# 删除最后一个节点 tail
def __rm_tail(self):
if not self.tail:
return
# 将要删除的节点获取到 node 中
node = self.tail
if node.prev:
self.tail = node.prev
self.tail.next = None
else:
self.tail = self.head = None
self.size -= 1
return node
# 删除给定的任意一个节点
def __remove(self, node):
# 判断如果 node == None 的话,则将 tail 节点赋值给 node 进行删除
if not node:
node = self.tail
if node == self.tail:
self.__rm_tail()
elif node == self.head:
self.__rm_head()
else:
node.prev.next = node.next
node.next.prev = node.prev
self.size -= 1
return node
# public: 弹出 head 节点
def pop(self):
return self.__rm_head()
# public: 添加节点
def append(self, node):
return self.__add_tail(node)
# public: 向头部添加节点
def insert_at_beginning(self, node):
return self.__add_head(node)
# public: 删除节点
def remove(self, node=None):
return self.__remove(node)
# public: 打印链表所有节点
def print(self):
p = self.head
line = ''
while p:
line += '%s' % p
p = p.next
if p:
line += '=>'
print(line)
# 测试双向链表
if __name__ == '__main__':
# 初始化一个双向列表对象
dllist = DoublyLinkedList(10)
# 向 nodes 中添加 10 个节点
nodes = []
for i in range(10):
node = Node(i, i)
nodes.append(node)
# 对双向链表对象 dllist 操作
dllist.append(nodes[0])
dllist.print()
dllist.append(nodes[1])
dllist.print()
dllist.pop()
dllist.print()
dllist.append(nodes[2])
dllist.print()
dllist.insert_at_beginning(nodes[3])
dllist.print()
dllist.append(nodes[4])
dllist.print()
dllist.remove(nodes[2])
dllist.print()
dllist.remove()
dllist.print()
'''
输出结果:
{0: 0}
{0: 0}=>{1: 1}
{1: 1}
{1: 1}=>{2: 2}
{3: 3}=>{1: 1}=>{2: 2}
{3: 3}=>{1: 1}=>{2: 2}=>{4: 4}
{3: 3}=>{1: 1}=>{4: 4}
{3: 3}=>{1: 1}
'''
十八、实现置换算法
1、先进先出算法(FIFO, First in First out)
把告诉缓存看做是一个先进先出的队列
优先替换最先进入队列的字块
# FIFO demo
#
# fifo_cache.py
# pythonProject
#
# Created by Z. Steve on 2023/1/28.
#
from doubly_linked_list import DoublyLinkedList, Node
class FIFOCache(object):
def __init__(self, capacity):
self.capacity = capacity
self.size = 0
# FIFOCache 对象中维护的一个键值对列表
self.map = {}
self.list = DoublyLinkedList(self.capacity)
def get(self, key):
if key not in self.map:
return -1
else:
node = self.map.get(key)
return node.value
def put(self, key, value):
if self.capacity == 0:
return
if key in self.map:
node = self.map.get(key)
self.list.remove(node)
node.value = value
self.list.append(node)
else:
# 如果 FIFOCache 已经满了,则删除一个节点
if self.size == self.capacity:
node = self.list.pop()
del self.map[node.key]
self.size -= 1
# 创建一个 Node 对象
node = Node(key, value)
# 将该节点添加到双向列表尾部
self.list.append(node)
self.map[key] = node
self.size += 1
def print(self):
self.list.print()
# 测试 FIFOCache
if __name__ == '__main__':
cache = FIFOCache(2)
cache.put(1, 100)
cache.print()
cache.put(2, 200)
cache.print()
print(cache.get(1))
cache.put(3, 300)
cache.print()
'''
输出结果:
{1: 100}
{1: 100}=>{2: 200}
100
{2: 200}=>{3: 300}
'''
2、最近最少使用算法(LRU, Least Recently Used)
- 优先淘汰一段时间内没有使用的字块
- 有多种实现方法, 一般使用双向链表
- 把当前访问节点置于链表前面(保证链表头部节点是最近使用的)
# LRU demo
#
# lru_cache.py
# pythonProject
#
# Created by Z. Steve on 2023/1/28.
#
from doubly_linked_list import DoublyLinkedList, Node
class LRUCache(object):
def __init__(self, capacity):
self.capacity = capacity
self.map = {}
self.list = DoublyLinkedList(self.capacity)
def get(self, key):
if key in self.map:
# 将该节点取出
node = self.map[key]
# 删除原位置节点
self.list.remove(node)
# 将该节点添加到首位置
self.list.insert_at_beginning(node)
# 返回该节点的值
return node.value
else:
# 如果 key 在 map 中不存在,则返回 -1
return -1
def put(self, key, value):
# 判断 key 是否已经在缓存中了
if key in self.map:
old_node = self.map.get(key)
self.list.remove(old_node)
old_node.value = value
self.list.insert_at_beginning(old_node)
else:
# 创建一个新节点
node = Node(key, value)
# 判断当前链表 size 是否已满,如果满了则删除最后一个
if self.list.size >= self.list.capacity:
# 链表已满
# 1. 删除尾部节点, remove() 函数不传参数表示删除最后一个节点
old_node = self.list.remove()
# 2. 在 map 映射中也删除该节点
self.map.pop(old_node.key)
# 添加新节点
self.list.insert_at_beginning(node)
# map 中保存新的映射关系
self.map[key] = node
def print(self):
self.list.print()
# 测试
if __name__ == '__main__':
# 1. 创建一个 LRUCache
cache = LRUCache(2)
# 2. 操作 cache
cache.put(1, 100)
cache.print()
cache.put(2, 200)
cache.print()
# (1, 100) 会被淘汰
cache.put(5, 500)
cache.print()
print(cache.get(2))
# (1, 100) 已经被淘汰
print(cache.get(1))
'''
输出结果:
{1: 100}
{2: 200}=>{1: 100}
{5: 500}=>{2: 200}
200
-1
'''
3、最不经常使用算法(LFU, Least Frequently Used)
- 优先淘汰最不经常使用的字块
- 需要额外的空间记录字块的使用频率
在多个使用频率同样低的字块中(把相同频率的节点连城一个链表),再用 FIFO 算法进行淘汰。
# LFUCache demo
#
# lfu_cache.py
# pythonProject
#
# Created by Z. Steve on 2023/1/28.
#
from doubly_linked_list import DoublyLinkedList, Node
# 带有记录节点使用频率的 Node 类
class LFUNode(Node):
def __init__(self, key, value):
# 记录节点使用频率
self.freq = 0
# 调用父类构造函数
super(LFUNode, self).__init__(key, value)
class LFUCache(object):
# 构造函数
def __init__(self, capacity):
self.capacity = capacity
self.map = {}
self.size = 0
# 保存不同使用频率对应的双向链表
# key: 节点使用频率; value: 双向链表
self.freq_map = {}
# 更新节点使用频率
def __update_node_freq(self, node):
# 1. 获取节点 node 的使用频率
freq = node.freq
# 2. 删除(如果对应的频率在 freq_map 中的双向链表 size 为 0,则删除这个键值对)
node = self.freq_map[freq].remove(node)
if self.freq_map[freq].size == 0:
del self.freq_map[freq]
# 3. 更新频率
freq += 1
node.freq = freq
if freq not in self.freq_map:
self.freq_map[freq] = DoublyLinkedList()
self.freq_map[freq].append(node)
def get(self, key):
if key not in self.map:
return -1
# 根据 key 获得该节点
node = self.map.get(key)
# 更新节点访问频率
self.__update_node_freq(node)
# 返回该节点的值
return node.value
def put(self, key, value):
if self.capacity == 0:
return
if key in self.map:
# 缓存命中
# 1. 从缓存中取数据
node = self.map.get(key)
# 2. 更新节点的value
node.value = value
# 3. 更新节点访问频率
self.__update_node_freq(node)
else:
# 缓存未命中
# 1. 检查是否满了
if self.size == self.capacity:
# 找到使用频率最少的节点删除
min_freq = min(self.freq_map)
# 从使用频率最少的 list 中删除一个
node = self.freq_map[min_freq].pop()
# 从 self.map 中删除这个 node
del self.map[node.key]
self.size -= 1
# 创建 LFUNode 节点
node = LFUNode(key, value)
# 设置该节点访问频率
node.freq = 1
# 将该节点加到 map 和 freq_map 中
self.map[node.key] = node
if node.freq not in self.freq_map:
# 新建一个链表
self.freq_map[node.freq] = DoublyLinkedList()
# 将新节点加到 freq_map 中
node = self.freq_map[node.freq].append(node)
self.size += 1
def print(self):
# 打印 self.freq_map
print('***********************************************')
for k, v in self.freq_map.items():
print('freq = %d' % k)
self.freq_map[k].print()
print('***********************************************')
print()
print()
# 测试
if __name__ == '__main__':
# 1. 创建一个 LFUCache 对象
cache = LFUCache(4)
# 2. 对 cache 进行操作
cache.put(1, 100)
cache.print()
cache.put(2, 200)
cache.print()
print(cache.get(1))
cache.print()
cache.put(3, 300)
cache.print()
print(cache.get(2))
cache.print()
print(cache.get(3))
cache.print()
cache.put(4, 400)
cache.print()
print(cache.get(1))
cache.print()
print(cache.get(3))
cache.print()
print(cache.get(4))
cache.print()
'''
输出结果:
***********************************************
freq = 1
{1: 100}
***********************************************
***********************************************
freq = 1
{1: 100}=>{2: 200}
***********************************************
100
***********************************************
freq = 1
{2: 200}
freq = 2
{1: 100}
***********************************************
***********************************************
freq = 1
{2: 200}=>{3: 300}
freq = 2
{1: 100}
***********************************************
200
***********************************************
freq = 1
{3: 300}
freq = 2
{1: 100}=>{2: 200}
***********************************************
300
***********************************************
freq = 1
freq = 2
{1: 100}=>{2: 200}=>{3: 300}
***********************************************
***********************************************
freq = 1
{4: 400}
freq = 2
{1: 100}=>{2: 200}=>{3: 300}
***********************************************
100
***********************************************
freq = 1
{4: 400}
freq = 2
{2: 200}=>{3: 300}
freq = 3
{1: 100}
***********************************************
300
***********************************************
freq = 1
{4: 400}
freq = 2
{2: 200}
freq = 3
{1: 100}=>{3: 300}
***********************************************
400
***********************************************
freq = 1
freq = 2
{2: 200}=>{4: 400}
freq = 3
{1: 100}=>{3: 300}
***********************************************
'''
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2011-01-30 jQuery.ajax()调用asp.net后台方法