python-5

SQL(2) - 开窗函数

开窗函数(Window Functions)是SQL中一种强大的功能,用于在一组相关行(窗口)上执行聚合计算,同时保留每行的详细信息。与传统的聚合函数(如 SUM, AVG, COUNT 等)不同,开窗函数不会将结果聚合成单一的行,而是为每一行返回一个计算结果。

基本语法

开窗函数的基本语法如下:

1
2
3
4
5
function_name(expression) OVER (
[PARTITION BY partition_expression]
[ORDER BY sort_expression [ASC | DESC]]
[ROWS BETWEEN start AND end]
)

主要组成部分

  1. 函数名:常见的开窗函数包括 ROW_NUMBER(), RANK(), DENSE_RANK(), LEAD(), LAG(), SUM(), AVG(), MIN(), MAX() 等。
  2. OVER子句:定义窗口的范围。
    • PARTITION BY:将数据分成多个分区,每个分区独立计算。
    • ORDER BY:在每个分区内对数据进行排序。
    • ROWS BETWEEN:定义窗口的行范围。

常见的开窗函数

1. 行号函数

  • ROW_NUMBER():为每个分区内的行分配一个唯一的行号。

    1
    ROW_NUMBER() OVER (PARTITION BY column1 ORDER BY column2)
  • RANK():为每个分区内的行分配一个排名,如果有相同的值,排名会跳过。

    1
    RANK() OVER (PARTITION BY column1 ORDER BY column2)
  • DENSE_RANK():为每个分区内的行分配一个排名,如果有相同的值,排名不会跳过。

    1
    DENSE_RANK() OVER (PARTITION BY column1 ORDER BY column2)

2. 前后行函数

  • LEAD():获取当前行之后的某一行的值。

    1
    LEAD(column, offset, default) OVER (PARTITION BY column1 ORDER BY column2)
  • LAG():获取当前行之前的某一行的值。

    1
    LAG(column, offset, default) OVER (PARTITION BY column1 ORDER BY column2)

3. 聚合函数

  • SUM():计算窗口内的总和。

    1
    SUM(column) OVER (PARTITION BY column1 ORDER BY column2)
  • AVG():计算窗口内的平均值。

    1
    AVG(column) OVER (PARTITION BY column1 ORDER BY column2)
  • MIN():计算窗口内的最小值。

    1
    MIN(column) OVER (PARTITION BY column1 ORDER BY column2)
  • MAX():计算窗口内的最大值。

    1
    MAX(column) OVER (PARTITION BY column1 ORDER BY column2)

示例

假设有一个销售数据表 sales,包含以下字段:id, product_id, sale_date, amount

1. 行号函数示例

1
2
3
SELECT id, product_id, sale_date, amount,
ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY sale_date) as row_num
FROM sales;

这个查询为每个产品的销售记录按日期排序,并分配一个行号。

2. 排名函数示例

1
2
3
SELECT id, product_id, sale_date, amount,
RANK() OVER (PARTITION BY product_id ORDER BY amount DESC) as rank
FROM sales;

这个查询为每个产品的销售记录按金额降序排序,并分配一个排名。

3. 前后行函数示例

1
2
3
4
SELECT id, product_id, sale_date, amount,
LAG(amount, 1, 0) OVER (PARTITION BY product_id ORDER BY sale_date) as prev_amount,
LEAD(amount, 1, 0) OVER (PARTITION BY product_id ORDER BY sale_date) as next_amount
FROM sales;

这个查询为每个产品的销售记录按日期排序,并获取前一行和后一行的金额。

4. 聚合函数示例

1
2
3
SELECT id, product_id, sale_date, amount,
SUM(amount) OVER (PARTITION BY product_id ORDER BY sale_date) as cumulative_sum
FROM sales;

这个查询为每个产品的销售记录按日期排序,并计算累计销售额。

窗口范围

  • ROWS BETWEEN:定义窗口的行范围。
    1
    SUM(amount) OVER (PARTITION BY product_id ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING)
    这个查询计算每个产品销售记录按日期排序时,当前行及其前后两行的总和。

信息检索(1)- BM25

BM25(Best Matching 25)是一种广泛应用于信息检索和文本挖掘的算法,它改进了传统的TF-IDF(Term Frequency-Inverse Document Frequency)方法,考虑了文档长度等因素,使得搜索结果更加准确。

TF-IDF

TF-IDF(Term Frequency-Inverse Document Frequency)是一种用于信息检索和文本挖掘的常用加权技术。它通过评估一个词在文档中的重要性来帮助识别文档的关键内容。TF-IDF 的主要思想是:如果某个词在文档中出现的频率高,并且在其他文档中出现的频率低,则该词对于该文档具有很高的区分能力。

基本概念

  1. 词频 (TF)

    • 定义:词频是指某个词在文档中出现的次数。
    • 公式
  2. 逆文档频率 (IDF)

    • 定义:逆文档频率反映了词的普遍重要性。如果一个词在很多文档中都出现,则它的 IDF 值较低;如果一个词只在少数文档中出现,则它的 IDF 值较高。
    • 公式其中,加 1 是为了避免分母为 0 的情况。
  3. TF-IDF

    • 定义:TF-IDF 是词频和逆文档频率的乘积,用于评估一个词在文档中的重要性。
    • 公式

BM25

基本思想

  1. TF-IDF 的改进
    • 饱和函数:BM25 引入了一个饱和函数来调整词项的出现次数(TF),防止某个词项在文档中出现次数过多导致权重过大。
    • 文档长度因子:BM25 考虑了文档的长度,引入了文档长度因子,使得文档长度对权重的影响不是线性的,这样可以更好地适应不同长度的文档。

计算公式

BM25 的具体计算公式如下:

其中:

  • $ n $ 是查询中的词项数。
  • $ q_i $ 是查询中的第 $ i $ 个词项。
  • $ \text{IDF}(q_i) $ 是逆文档频率,计算方式通常是 $ \log \frac{N - n(q_i) + 0.5}{n(q_i) + 0.5} $,其中 $ N $ 是文档总数,$ n(q_i) $ 是包含词项 $ q_i $ 的文档数。
  • $ f(q_i, D) $ 是词项 $ q_i $ 在文档 $ D $ 中的出现次数(TF)。
  • $ \text{len}(D) $ 是文档 $ D $ 的长度。
  • $ \text{avg_len} $ 是所有文档的平均长度。
  • $ k_1 $ 和 $ b $ 是调整参数,通常设置为 $ k_1 = 1.5 $ 和 $ b = 0.75 $。

Python 实现

以下是一个简单的 Python 实现 BM25 算法的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import math
from collections import Counter

class BM25:
def __init__(self, corpus, k1=1.5, b=0.75):
self.k1 = k1
self.b = b
self.corpus = corpus
self.doc_lengths = [len(doc) for doc in corpus]
self.avg_doc_length = sum(self.doc_lengths) / len(self.doc_lengths)
self.doc_count = len(corpus)
self.doc_term_freqs = [Counter(doc) for doc in corpus]
self.inverted_index = self.build_inverted_index()

def build_inverted_index(self):
inverted_index = {}
for doc_id, doc_term_freq in enumerate(self.doc_term_freqs):
for term, freq in doc_term_freq.items():
if term not in inverted_index:
inverted_index[term] = []
inverted_index[term].append((doc_id, freq))
return inverted_index

def idf(self, term):
doc_freq = len(self.inverted_index.get(term, []))
if doc_freq == 0:
return 0
return math.log((self.doc_count - doc_freq + 0.5) / (doc_freq + 0.5) + 1.0)

def bm25_score(self, query_terms, doc_id):
score = 0
doc_length = self.doc_lengths[doc_id]
for term in query_terms:
tf = self.doc_term_freqs[doc_id].get(term, 0)
idf = self.idf(term)
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * (doc_length / self.avg_doc_length))
score += idf * (numerator / denominator)
return score

def rank_documents(self, query):
query_terms = query.split()
scores = [(doc_id, self.bm25_score(query_terms, doc_id)) for doc_id in range(self.doc_count)]
sorted_scores = sorted(scores, key=lambda x: x[1], reverse=True)
return sorted_scores

# Example usage
corpus = [
"The quick brown fox jumps over the lazy dog",
"A quick brown dog outpaces a swift fox",
"The dog is lazy but the fox is swift",
"Lazy dogs and swift foxes"
]

bm25 = BM25(corpus)
query = "quick brown dog"
result = bm25.rank_documents(query)
print(f"BM25 Scores for the query '{query}':")
for doc_id, score in result:
print(f"Document {doc_id}: {score}")

解释

  1. 初始化

    • corpus 是文档集合。
    • k1b 是调整参数。
    • doc_lengths 存储每个文档的长度。
    • avg_doc_length 是所有文档的平均长度。
    • doc_term_freqs 存储每个文档中每个词项的出现次数。
    • inverted_index 是倒排索引,用于快速查找词项在哪些文档中出现。
  2. 构建倒排索引

    • build_inverted_index 方法构建倒排索引,记录每个词项出现在哪些文档中及其频率。
  3. 计算逆文档频率

    • idf 方法计算词项的逆文档频率。
  4. 计算 BM25 得分

    • bm25_score 方法计算查询中每个词项在指定文档中的 BM25 得分。
  5. 排序文档

    • rank_documents 方法计算查询与每个文档的 BM25 得分,并按得分从高到低排序。

测试用例

  • 查询"quick brown dog"
  • 结果:输出每个文档的 BM25 得分,并按得分从高到低排序。

Web(1) - Cookie 和 Session

Cookie 和 Session 是 Web 开发中用于管理用户会话状态的两种重要机制。它们各有特点和适用场景。下面详细介绍 Cookie 和 Session 的概念、工作原理以及它们之间的区别。

1. Cookie

定义

Cookie 是存储在客户端(通常是浏览器)的小型文本文件,用于保存用户会话信息。服务器可以通过 HTTP 响应头设置 Cookie,并在后续请求中通过 HTTP 请求头读取 Cookie。

工作原理

  1. 设置 Cookie:服务器通过 Set-Cookie 响应头向客户端发送 Cookie。
  2. 读取 Cookie:客户端在每次请求中通过 Cookie 请求头将 Cookie 发送到服务器。
  3. 存储 Cookie:客户端浏览器负责存储和管理 Cookie。

示例

1
2
HTTP/1.1 200 OK
Set-Cookie: session_id=123456; Path=/; HttpOnly; Secure

客户端在后续请求中会自动包含这个 Cookie:

1
2
3
GET /example HTTP/1.1
Host: example.com
Cookie: session_id=123456

特点

  • 存储位置:客户端(浏览器)。
  • 大小限制:每个域名下的 Cookie 总大小通常限制在 4KB 左右。
  • 安全性:可以通过 HttpOnlySecure 标志增强安全性。
    • HttpOnly:防止 JavaScript 通过 document.cookie 访问 Cookie。
    • Secure:要求 Cookie 只能通过 HTTPS 协议传输。

2. Session

定义

Session 是存储在服务器端的会话数据。每个用户会话有一个唯一的会话标识符(通常是一个长字符串),该标识符存储在客户端的 Cookie 中,用于在服务器端查找对应的会话数据。

工作原理

  1. 生成会话标识符:服务器为每个用户生成一个唯一的会话标识符。
  2. 设置 Cookie:服务器通过 Set-Cookie 响应头将会话标识符发送给客户端。
  3. 存储会话数据:服务器将用户的会话数据存储在服务器端的会话存储中(如内存、数据库、文件系统等)。
  4. 读取会话数据:客户端在每次请求中通过 Cookie 请求头将会话标识符发送给服务器,服务器根据会话标识符查找并读取会话数据。

示例

1
2
HTTP/1.1 200 OK
Set-Cookie: session_id=abcdef123456; Path=/; HttpOnly; Secure

客户端在后续请求中会自动包含这个会话标识符:

1
2
3
GET /example HTTP/1.1
Host: example.com
Cookie: session_id=abcdef123456

服务器根据 session_id 查找会话数据。

特点

  • 存储位置:服务器端。
  • 大小限制:理论上没有大小限制,但实际取决于服务器的存储能力。
  • 安全性:会话数据存储在服务器端,相对更安全。
  • 管理复杂度:需要管理会话数据的存储和过期机制。

3. Cookie 与 Session 的区别

特点 Cookie Session
存储位置 客户端(浏览器) 服务器端
大小限制 每个域名下通常限制在 4KB 左右 理论上没有大小限制,但实际取决于服务器的存储能力
安全性 可以通过 HttpOnlySecure 标志增强安全性 会话数据存储在服务器端,相对更安全
管理复杂度 相对简单,由浏览器自动管理 需要管理会话数据的存储和过期机制
适用场景 保存少量的会话数据,如用户偏好设置 保存大量的会话数据,如购物车内容、登录状态

4. 使用场景

  • Cookie

    • 保存用户偏好设置(如语言、主题等)。
    • 保存简单的会话信息(如登录状态)。
    • 跟踪用户行为(如访问统计)。
  • Session

    • 保存敏感的会话数据(如用户身份信息、购物车内容)。
    • 保存大量或复杂的会话数据。
    • 需要高度安全性的场景。

5. 示例代码

设置和读取 Cookie(JavaScript)

1
2
3
4
5
6
7
8
9
10
11
// 设置 Cookie
document.cookie = "username=John Doe; Path=/; HttpOnly; Secure";

// 读取 Cookie
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}

console.log(getCookie("username")); // 输出: John Doe

设置和读取 Session(Node.js with Express)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
secret: 'your_secret_key',
resave: false,
saveUninitialized: true,
cookie: { secure: true, httpOnly: true }
}));

app.get('/set-session', (req, res) => {
req.session.username = 'John Doe';
res.send('Session set');
});

app.get('/get-session', (req, res) => {
res.send(req.session.username || 'No session data');
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

总结

Cookie 和 Session 是 Web 开发中管理用户会话状态的两种重要机制。Cookie 适合保存少量的会话数据,而 Session 适合保存大量的会话数据,尤其是需要高度安全性的场景。通过合理使用 Cookie 和 Session,可以有效提升 Web 应用的用户体验和安全性。

C++(2)- 静态成员函数

在 C++ 中,静态成员函数(static member function)是与类相关联而不是与类的任何特定对象相关联的函数。静态成员函数有以下几个特点:

1. 声明和定义

静态成员函数使用 static 关键字声明。它们可以访问类的静态成员,但不能直接访问非静态成员(因为非静态成员属于具体的对象,而静态成员函数没有对象实例)。

声明

在类声明中使用 static 关键字声明静态成员函数:

1
2
3
4
class MyClass {
public:
static void myStaticFunction();
};

定义

在类外部定义静态成员函数时,不需要再次使用 static 关键字:

1
2
3
void MyClass::myStaticFunction() {
// 函数体
}

2. 访问静态成员函数

静态成员函数可以通过类名直接调用,也可以通过对象调用,但推荐使用类名调用以明确其静态特性。

1
2
3
MyClass::myStaticFunction();  // 通过类名调用
MyClass obj;
obj.myStaticFunction(); // 通过对象调用

3. 静态成员函数的特点

  • 无隐式 this 指针:静态成员函数没有隐式的 this 指针,因此不能直接访问非静态成员变量和非静态成员函数。
  • 共享数据:静态成员函数可以访问类的静态成员变量,这些变量在所有对象之间共享。
  • 全局作用域:静态成员函数在类的作用域内,但可以看作是全局函数,因为它们不依赖于任何特定的对象实例。

4. 示例

以下是一个简单的示例,展示了静态成员函数的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <string>

class MyClass {
public:
static int objectCount; // 静态成员变量

MyClass() {
++objectCount; // 每创建一个对象,计数加1
}

~MyClass() {
--objectCount; // 每销毁一个对象,计数减1
}

static void printObjectCount() {
std::cout << "Number of objects: " << objectCount << std::endl;
}
};

// 初始化静态成员变量
int MyClass::objectCount = 0;

int main() {
MyClass::printObjectCount(); // 输出: Number of objects: 0

{
MyClass obj1;
MyClass obj2;
MyClass::printObjectCount(); // 输出: Number of objects: 2
}

MyClass::printObjectCount(); // 输出: Number of objects: 0

return 0;
}

解释

  1. 静态成员变量objectCount 是一个静态成员变量,它在所有 MyClass 对象之间共享。
  2. 构造函数和析构函数:在构造函数中增加 objectCount,在析构函数中减少 objectCount
  3. 静态成员函数printObjectCount 是一个静态成员函数,它可以访问 objectCount 并打印当前的对象数量。
  4. 调用静态成员函数:通过类名 MyClass::printObjectCount() 调用静态成员函数,也可以通过对象调用,但推荐使用类名调用。

5. 静态成员函数的用途

  • 工具函数:静态成员函数可以用于实现与类相关的工具函数,这些函数不依赖于具体的对象实例。
  • 工厂方法:静态成员函数可以用作工厂方法,用于创建类的实例。
  • 全局状态管理:静态成员函数可以用于管理类的全局状态,例如计数器、配置选项等。

C++(1)- 虚函数

在 C++ 中,虚函数(virtual function)是实现多态的一种机制。通过虚函数,可以在派生类中重写(override)基类中的函数,从而在运行时根据对象的实际类型调用相应的函数。以下是关于 C++ 中虚函数的详细介绍和示例。

1. 基本概念

  • 虚函数:在基类中声明为 virtual 的成员函数。虚函数允许派生类重写该函数,从而实现多态。
  • 纯虚函数:在基类中声明为 virtual 并且没有实现的函数,形式为 virtual 返回类型 函数名() = 0;。含有纯虚函数的类称为抽象类,不能实例化。
  • 多态:通过基类指针或引用调用虚函数时,实际调用的是派生类中重写的函数,而不是基类中的函数。

2. 虚函数的声明和使用

声明虚函数

在基类中声明虚函数:

1
2
3
4
5
6
class Base {
public:
virtual void display() {
std::cout << "Base class display function" << std::endl;
}
};

派生类中重写虚函数

在派生类中重写基类的虚函数:

1
2
3
4
5
6
class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

3. 多态的实现

通过基类指针或引用调用虚函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

class Base {
public:
virtual void display() {
std::cout << "Base class display function" << std::endl;
}
};

class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

int main() {
Base baseObj;
Derived derivedObj;

Base* basePtr = &baseObj;
Base* derivedPtr = &derivedObj;

basePtr->display(); // 输出: Base class display function
derivedPtr->display(); // 输出: Derived class display function

return 0;
}

4. 纯虚函数和抽象类

声明纯虚函数

在基类中声明纯虚函数:

1
2
3
4
class Base {
public:
virtual void display() = 0; // 纯虚函数
};

派生类中实现纯虚函数

在派生类中实现基类的纯虚函数:

1
2
3
4
5
6
class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

5. 构造函数和析构函数中的虚函数

  • 构造函数:虚函数在构造函数中不具有多态性,即在构造函数中调用虚函数时,只会调用当前类的虚函数,不会调用派生类的虚函数。
  • 析构函数:虚函数在析构函数中具有多态性,因此基类的析构函数通常声明为虚函数,以确保派生类的析构函数也能被调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

class Base {
public:
virtual ~Base() {} // 虚析构函数
virtual void display() {
std::cout << "Base class display function" << std::endl;
}
};

class Derived : public Base {
public:
~Derived() {} // 派生类的析构函数
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

int main() {
Base* basePtr = new Derived();
basePtr->display(); // 输出: Derived class display function
delete basePtr; // 调用 Derived 的析构函数

return 0;
}

6. 虚函数表(VTable)

C++ 编译器在内部使用虚函数表(Virtual Table, VTable)来实现虚函数的多态调用。每个包含虚函数的类都有一个虚函数表,表中存放了该类所有虚函数的地址。每个对象都有一个指向其类的虚函数表的指针(vptr),通过 vptr 可以找到对应的虚函数地址。

操作系统(2) - 进程管理

操作系统的进程管理是操作系统的核心功能之一,负责创建、调度、同步、通信和终止进程。进程管理的目标是确保系统资源的有效利用,提高系统的性能和可靠性。以下是操作系统进程管理的主要概念和技术的详细说明。

1. 进程的基本概念

  • 进程:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位。一个进程包含程序代码、数据、堆栈和执行上下文等。
  • 进程状态:进程在生命周期中会经历不同的状态,主要包括:
    • 就绪态(Ready):进程已经准备好,等待 CPU 资源。
    • 运行态(Running):进程正在 CPU 上执行。
    • 阻塞态(Blocked):进程因等待 I/O 操作完成或其他事件而暂停执行。
    • 终止态(Terminated):进程已经完成或被终止。

2. 进程控制块(PCB)

进程控制块(Process Control Block, PCB)是操作系统用来管理和控制进程的数据结构,包含以下信息:

  • 进程标识符(PID):唯一标识进程的编号。
  • 处理器状态:包括程序计数器(PC)、寄存器内容等。
  • 内存管理信息:包括内存分配情况、页表等。
  • I/O 状态信息:包括打开的文件、I/O 设备状态等。
  • 会计信息:包括进程的优先级、运行时间等。
  • 状态信息:包括进程的当前状态(就绪、运行、阻塞、终止)。

3. 进程创建

  • 创建新进程:操作系统通过 fork()CreateProcess() 等系统调用创建新进程。
  • 父子关系:创建新进程的进程称为父进程,新创建的进程称为子进程。
  • 资源继承:子进程可以继承父进程的资源,如打开的文件、信号处理函数等。

4. 进程调度

  • 调度算法:操作系统使用不同的调度算法来决定下一个执行的进程。常见的调度算法包括:
    • 先来先服务(FCFS):按进程到达的顺序调度。
    • 短作业优先(SJF):优先调度预计运行时间最短的进程。
    • 优先级调度:根据进程的优先级进行调度。
    • 时间片轮转(RR):每个进程按固定的时间片轮流执行。
    • 多级反馈队列:结合多种调度策略,根据进程的行为动态调整优先级。

5. 进程同步

  • 临界区:一段代码区域,同一时刻只允许一个进程执行。
  • 互斥锁(Mutex):用于实现互斥访问的机制。
  • 信号量(Semaphore):用于控制对共享资源的访问,包括二进制信号量和计数信号量。
  • 条件变量:用于进程间的条件同步。

6. 进程通信

  • 共享内存:允许多个进程共享同一块内存区域,提高数据传输效率。
  • 消息传递:进程间通过发送和接收消息进行通信,包括管道(Pipe)、消息队列等。
  • 信号(Signal):用于进程间的异步通信,发送信号可以中断进程的正常执行。
  • 套接字(Socket):用于网络进程间的通信。

7. 进程终止

  • 正常终止:进程完成任务后正常退出。
  • 异常终止:进程因错误或外部干预而被终止。
  • 僵尸进程:进程已经终止,但其父进程尚未调用 wait()waitpid() 获取其退出状态。
  • 孤儿进程:父进程终止后,子进程成为孤儿进程,由 init 进程(PID 为 1)接管。

db0

操作系统(1) - 内存管理

操作系统的内存管理是计算机系统中的一个核心组件,负责管理和分配计算机的内存资源。有效的内存管理可以提高系统的性能和稳定性。以下是操作系统内存管理的主要概念和技术的详细说明。

1. 内存层次结构

现代计算机系统中的内存层次结构通常包括以下几个层级:

  • 寄存器(Registers):CPU内部的高速缓存,访问速度最快,但容量最小。
  • 高速缓存(Cache):位于CPU和主内存之间,分为L1、L2和L3缓存,访问速度较快,容量适中。
  • 主内存(Main Memory):RAM(随机存取存储器),直接由CPU访问,用于存储正在运行的程序和数据。
  • 辅助存储(Secondary Storage):硬盘、SSD等,用于存储大量的数据和程序,访问速度较慢,但容量大。

2. 内存管理的主要任务

  • 内存分配:为进程分配所需的内存空间。
  • 内存回收:回收不再使用的内存空间。
  • 地址转换:将程序中的逻辑地址转换为物理地址。
  • 内存保护:防止进程访问不属于它的内存区域。
  • 内存共享:允许多个进程共享同一块内存区域。
  • 虚拟内存:扩展物理内存,使用磁盘空间作为虚拟内存的一部分。

3. 内存分配技术

  • 连续内存分配

    • 单一连续分配:整个内存分为系统区和用户区,每个用户进程独占用户区。
    • 分区分配:内存被划分为多个固定或动态大小的分区,每个分区分配给一个进程。
  • 非连续内存分配

    • 分页(Paging):将内存和磁盘空间划分为固定大小的页面,通过页表进行地址转换。
    • 分段(Segmentation):将内存划分为可变大小的段,每个段对应程序的一个逻辑部分,通过段表进行地址转换。
    • 段页式(Segmented-Paging):结合分段和分页,先将内存划分为段,再将每个段划分为页。

4. 虚拟内存

  • 概念:虚拟内存是一种内存管理技术,允许程序使用的地址空间大于实际物理内存的大小。
  • 页面置换算法
    • FIFO(先进先出):最早进入内存的页面最先被替换。
    • LRU(最近最少使用):最近最少使用的页面最先被替换。
    • Optimal(最佳置换算法):选择将来最长时间内不会被访问的页面进行替换。
    • Clock(时钟算法):类似于FIFO,但增加了“使用位”来决定是否替换页面。
    • LFU(最不经常使用):使用次数最少的页面最先被替换。

5. 地址转换

  • 逻辑地址:程序中的地址,也称为虚拟地址。
  • 物理地址:内存中的实际地址。
  • 页表:用于将逻辑地址转换为物理地址的表格。
  • 段表:用于将段号转换为段的基地址和长度的表格。

6. 内存保护

  • 内存访问权限:通过设置内存区域的读、写、执行权限来保护内存。
  • 内存隔离:确保每个进程只能访问属于自己的内存区域,防止恶意或错误的访问。

7. 内存共享

  • 共享内存:允许多个进程共享同一块内存区域,提高数据传输效率。
  • 共享库:允许多个进程共享同一个库文件,减少内存占用。

8. 内存碎片

  • 外部碎片:在分区分配中,空闲分区太小,无法满足新的内存请求。
  • 内部碎片:在分页中,页面的大小通常是固定的,导致每个页面的最后部分可能未被完全利用。

ca0