赞
踩
Java语言实现模拟页面置换算法
摘要:在存储管理当中,页面置换算法通常可分为两种:一种基于进程驻留集大小不变;另一种基于驻留集大小可变。在驻留集大小不变的页面置换算法当中,常用的算法主要有:FIFO、OPT 、LRU。本文讨论的对象是FIFO、OPT 、LRU算法。
关键词:页面置换算法,LRU算法,OPT算法,FIFO算法
在进程中运行过程中,若所要访问的页面不在内存中,需要调入页面时,选择内存中的那个物理页面将其调出。通常只能再局部性原理指导下,把未来不再使用的页面调出。如何选择调度策略即页面置换算法至关重要,置换算法的好坏,直接影响着系统的性能,因此我们有必要将常见的FIFO、LRU、OPT三种算法进行比较,分析性能。
一、 页面置换算法原理
1) 最佳置换算法(Optimal),从主存中移出永远不再需要的页面,如无这样的页面存在,则应选择最长时间不需要访问的页面。这是一种理想的情况,是实际执行中无法预知的,因而实现很困难。
2) 先进先出算法(First-in, First-out),总选择作业中在主存驻留时间最长的一页淘汰。选择建立最早的页面被置换。可以通过链表来表示各页的建立时间先后。性能较差。较早调入的页往往是经常被访问的页,这些页在FIFO算法下被反复调入和调出。只有在线性顺序访问地址空间时才是理想的,否则效率不高。在未给进程或作业分配足够的页面数时,缺页次数反而增加,从而产生Belady现象,
3) 最近最久不用的页面置换算法(LeastRecently Used Replacement),当需要置换一页面时,选择在最近一段时间内最久不用的页面予以淘汰。此算法通过周期性的对“引用位”进行检测,并利用它来记录一个页面自上次访问以来所经历的时间T;淘汰时,选择T为最大的页。选择内存中最久未使用的页面被置换。这是局部性原理的合理近似,性能接近最佳算法。但由于需要记录页面使用时间的先后关系,硬件开销太大。
二、 页面置换算法流程图
1) OPT算法
A. 原理简述
1. 在分配内存页面数m小于进程页面数n时,当然是先运行的m个页面放入内存mym[m];
2. 有需要处理的新页面,则将原来在内存mym[m]中的m个页面中将来不再使用的调出,然后将新页面放入。
B. 算法流程图
n为页面长度
m为内存的物理块
as记录缺页数
OPT流程图
2) FIFO算法
A. 原理简述:
a) 在分配内存页面数m小于进程页面数n时,当然是先运行的m个页面放入内存mym[m];
b) 这时有需要处理的新页面,则将原来在内存mym[m]中的m个页面中最先进入的调出,然后将新页面放入。
c) 以后若再有新页面需要调入,则按b)的规则进行。
根据算法的特点:所使用的内存页面构成一个队列。
B. 算法流程图
n为队列长度
as记录缺页数
FIFO流程图
3) LRU算法
A. 原理简述
1. 在分配内存页面数m小于进程页面数n时,当然是先运行的m个页面放入内存mym[m];
2. 当需要调页面进入内存,而当前分配的内存页面全部不空闲是,选择将其中最长时间没有使用的那个页面调出,以空出内存来放置新调入的页面。其中用一个一维数组myb[m]记录没有被访问的时间。
B. 算法流程图
n为页面长度
m为内存的物理块
as记录缺页数
LRU流程图
三、 页面置换算法具体实现
1. OPT算法
public void Opt() {
int m_absent;// 缺页数
double m_absentf;// 缺页率
int m_change; // 置换次数
double m_changef;// 页面置换率
int mym[];// 存放物理块中现有的页面号
int myb[];
int as = 0;// 置换页面数
myb = new int[n];
int m1 = 0, a;
boolean x; // 页面是否需要置换
String str2, str1;
String m_list = "Optimal置换算法";
mym = new int[m];
for (int i = 0; i < n; i++) {
str1 = ""; str2 = "";
x = true;
for (int k = 0; k < m; k++) {
if (myt[i] == mym[k])// 判断物理块中的页面是否与当前页面相等
{
x = false;// 判断物理块中是不是有跟当前须替换的页面
m1++;
break;
}
}
if (x) {
int c = 0, d;
if ((i - m1) < m) {
a = i - m1;
} // 当前物理页面未填满时直接装入
else {
for (int k = 0; k < m; k++) {
for (int h = i; h < length; h++) {
if (mym[k] == myt[h])// 判断物理块中的页面是否与未来页面相等
{
myb[k] = h;
break;
}
if (h == length - 1)
myb[k] = length;
}
}
d = myb[0];
for (int h = 0; h < m; h++) {
if (d < myb[h]) {
d = myb[h];
c = h;
}
}
a = c; // 找出物理块中最久未使用的页面号
}
mym[a] = myt[i];// 将其替换
myb[a] = 0;
for (int k = 0; k < m; k++) {
if (k != a)
myb[k] = myb[k] + 1;
// 使物理块中的每个未改变页面的时间增一
}
myb[a] = 0;
as++;
}
for (int j = 0; j < m; j++) {
int b;
b = mym[j];
str2 = String.valueOf(b);
str1 = str1 + " " + str2;
}
m_list = m_list + "/n" + str1;
}
m_absent = as;
m_absentf = (double) as / n;
if ((as - m) <= 0) {
m_change = 0;
m_changef = 0;
} else {
m_change = as - m;
m_changef = (double) (as - m) / n;
}
}
2. LRU算法
public void LRU() {
int m_absent;// 缺页数
double m_absentf;// 缺页率
int m_change; // 置换次数
double m_changef;// 页面置换率
int mym[];// 存放物理块中现有的页面号
int myb[];
int as = 0;// 置换页面数
myb = new int[n];
int m1 = 0, a;
boolean x; // 页面是否需要置换
String str2, str1;
String m_list = "LUR置换算法";
mym = new int[m];
for (int i = 0; i < n; i++) {
str1 = "";
str2 = "";
x = true;
for (int k = 0; k < m; k++) {
if (myt[i] == mym[k])// 判断物理块中的页面是否与当前页面相等
{
myb[k] = myb[0];
for (int j = 0; j < m; j++) {
if (j != k)
myb[j] = myb[j] + 1;// 使物理块中的每个未使用页面的时间增一
}
x = false;// 判断物理块中是不是有跟当前须替换的页面
m1++;
break;
}
}
if (x) {
int c = 0, d;
if ((i - m1) < m) {
a = i - m1;
} // 当前物理页面未填满时直接装入
else {
d = myb[0];
for (int h = 0; h < m; h++) {
if (d < myb[h]) {
d = myb[h];
c = h;
}
}
a = c; // 找出物理块中最久未使用的页面号
}
mym[a] = myt[i];// 将其替换
myb[a] = 0;
for (int k = 0; k < m; k++) {
if (k != a)
myb[k] = myb[k] + 1;// 使物理块中的每个未改变页面的时间增一
}
myb[a] = 0;
as++;
}
for (int j = 0; j < m; j++) {
int b;
b = mym[j];
str2 = String.valueOf(b);
str1 = str1 + " " + str2;
}
m_list = m_list + "/n" + str1;
}
m_absent = as;
m_absentf = (double) as / n;
if ((as - m) <= 0) {
m_change = 0;
m_changef = 0;
} else {
m_change = as - m;
m_changef = (double) (as - m) / n;
}
}
3. FIFO算法
public void FIFO() {
int m_absent;// 缺页数
double m_absentf;// 缺页率
int m_change; // 置换次数
double m_changef;// 页面置换率
int mym[];// 存放物理块中现有的页面号
int m1 = 0, r;
int as = 0;// 置换页面数
boolean x; // 页面是否需要置换
String str1, str2;
String m_list = "FIFO置换算法";
mym = new int[m];
for (int i = 0; i < n; i++) {
str1 = "";
str2 = "";
x = true;
for (int k = 0; k < m; k++) {
if (myt[i] == mym[k]) {
for (r = k; r < m - 1; r++)
mym[r] = mym[r + 1];
mym[r] = myt[i];
x = false;// 判断物理块中是不是有跟当前须替换的页面
break;
}
}
if (x) {
mym[0] = myt[i];
for (r = 0; r < m - 1; r++)
mym[r] = mym[r + 1];
mym[r] = myt[i];
as++;
}
int b;
for (int j = 0; j < m; j++) {
b = mym[j];
str2 = String.valueOf(b);
str1 = str1 + " " + str2;
}
m_list = m_list + '/n' + str1;
}
m_absent = as;
m_absentf = (double) as / n;
if ((as - m) <= 0) {
m_change = 0;
m_changef = 0;
} else {
m_change = as - m;
m_changef = (double) (as - m) / n;
}
}
四、 页面置换实例分析(程序模拟算法)
实验结果:
页面号引用串 | 8 10 5 6 4 2 7 1 3 10 4 5 3 10 5 6 4 8 2 10 5 6 4 8…9 4 4 | |||
| 缺页数 | 缺页率 | 置换次数 | 置换率 |
FIFO | 20 | 0.67 | 15 | 0.5 |
LUR | 18 | 0.6 | 13 | 0.43 |
Optimal | 15 | 0.5 | 10 | 0.33 |
实验结果表明:Optimal算法效果最好,其次LUR算法,再次FIFO算法
五、 LRU算法与FIFO算法、OPT算法
FIFO算法总是选择在内存驻留时间最长的一页将其淘汰。这种算法基于CPU按线性顺序访问地址空间的这个假设上,许多时候,CPU不是按线性顺序访问地址空间的。所以,那些在内存中停留时间最长的页往往经常被访问到。这就造成FIFO算法的缺页率不太理想。并且它还有—个缺点:Belady奇异现象。实现FIFO算法无需硬件提供新的帮助,只采用循环数组管理驻留集即可。OPT 算法被誉为“最优算法”,因为它淘汰下次访问距当前最远的那些页中序号最小的一页。一所以,OPT算法得出的缺页率是最小的。但是,OPT 算法在使用前必须预先得知整个访问串,这很难付诸实现。因此,OPT 算法只是一种概念中的算法,目前无法实现。LRU算法的实现耗费较高,并且需要硬件的支持,但是效果较好。就缺页率而言,OPT 算法最佳,LRU算法次之,FIFO最差。
六、 心得体会
1. 通过本次课程设计,加深了对操作系统的认识,了解了操作系统中各种资源分配算法的实现,特别是对虚拟存储,页面置换有了深入的了解,并能够用高级语言进行模拟演示。通过查阅相关资料对eclipse编程工具有了一定的了解,基本掌握了JAVA应用程序编写的基本方法。使用Java编程极大的减少了代码量,直观的界面设计,不仅便于实现而且简化了编码,便于集中精力到主算法的编写上。
2. 三种页面置换算法FIFO、LRU和Optimal理解起来相当容易,但在实际编程实现的时候需要注意各种细节,需要耐心细致,实际编程中遇到一些细节上的小问题确实需要仔细考虑才行。
3. 因为需要用户输入后才能知道实际内存块和最大页面数的大小,所以用到了动态数组函数,在LRU和Optimal算法中时间权值数组myb[]是关键,必须与页面变化同步。
4. 为了简化编程的困难,采用随即数的思想,使得程序易于实现。通过三种页面置换算法的比较,对页面置换有更深一层的了解。使我对操作系统特别是页面置换这一部分的认识有了很大的加深。
5. 使自己对Java语言的基本框架有新的了解,加深了对可视化程序的认识。在使用Java语言来实现功能时,不像以往用的其他语言,它比较简练,更容易理解,实用性很强。
6. 采用了线性表实现FIFO、LRU、OPT三种算法,若采用链表的方法可以减少算法的不稳定性和时间复杂度。
七、 参考文献
[1] 计算机操作系统(修订版) 汤子瀛 哲凤屏 汤小丹 西安电子科技大学出版社 西安 1996
[2] 计算机操作系统实验教程 黄祥喜 中山大学出版社 广州 1994
[3] 计算机操作系统原理及其习题解答 苏开根 何炎祥 海洋出版社 北京 1993
[4] 计算机操作系统原理教程 孟静 清华大学出版社 北京 2000
八、 附件
页面置换模拟程序源代码
//mian.java
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
public class main {
public static void main(String[] args) {
JFrame myframe = new JFrame();
pageChange pc = new pageChange();
JTabbedPane tab = new JTabbedPane();
JMenuBar mb = new JMenuBar();
JMenu mi = new JMenu("Help");
mi.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
JOptionPane.showMessageDialog(new JOptionPane(), "版权:/n 兰州交大数理学院,连乾云", "提示!", JOptionPane.OK_OPTION);
}
});
mb.add(mi);
tab.add("页面置换算法", pc);
myframe.setLayout(new BorderLayout());
myframe.add(BorderLayout.NORTH, mb);
myframe.add(BorderLayout.CENTER, tab);
myframe.setTitle("Computer Operating Systems Page-Replacement Algorthms");
myframe.setSize(950, 650);
myframe.show(true);
}
}
//pageChange.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class pageChange extends JPanel implements ActionListener {
JPanel pan1, pan2, pan3, pan4;
JButton button1, button2, button3, clearButton, randomButton;
JTextField text1, text2, text3, text4;
String m_random = "";// 页面序列
int m, n, length;
int myt[];
randomNumber RN;
displayView FIFOTA, LRUTA, OptTA;
public pageChange() {
pan1 = new JPanel();
pan2 = new JPanel();
pan3 = new JPanel();
text1 = new JTextField(4);
text2 = new JTextField(5);
text3 = new JTextField();
text4 = new JTextField();
text4.setEditable(false);
pan1.setLayout(new GridLayout(4, 2));
pan1.add(new JLabel(" 物理块数 "));
pan1.add(text1);
pan1.add(new JLabel(" 置换页面数 "));
pan1.add(text2);
pan1.add(new JLabel(" 页表长度 "));
pan1.add(text3);
pan1.add(new JLabel(" 页面号引用串 "));
pan1.add(text4);
FIFOTA = new displayView();
LRUTA = new displayView();
OptTA = new displayView();
pan2.setLayout(new GridLayout(1, 3));
pan2.add(FIFOTA);
pan2.add(LRUTA);
pan2.add(OptTA);
button1 = new JButton("FIFO");
button2 = new JButton("LRU");
button3 = new JButton("Optimal");
randomButton = new JButton("Random");
clearButton = new JButton("Clear");
button1.addActionListener(this);
button2.addActionListener(this);
button3.addActionListener(this);
clearButton.addActionListener(this);
randomButton.addActionListener(this);
pan3.add(randomButton);
pan3.add(button1);
pan3.add(button2);
pan3.add(button3);
pan3.add(clearButton);
setLayout(new BorderLayout());
add(BorderLayout.NORTH, pan1);
add(BorderLayout.CENTER, pan2);
add(BorderLayout.SOUTH, pan3);
}
public void misInPut() {
try {
String str1, str2;
str1 = "";
str2 = "";
m = nteger.parseInt(text1.getText());
n = Integer.parseInt(text2.getText());
length = Integer.parseInt(text3.getText());
if (m == 0 || n == 0 || length == 0) {
JOptionPane.showMessageDialog(this, "请输入数字字符", "提示!",
JOptionPane.ERROR_MESSAGE);
}
if (m > n && length != 0) {
JOptionPane.showMessageDialog(this, "输入的物理块数大于页面数或页表长度为0",
"警告!", JOptionPane.WARNING_MESSAGE);
text1.setText("0");
text2.setText("0");
text3.setText("0");
text4.setText("");
} else {
myt = new int[n];
RN = new randomNumber();
myt = RN.rand(n, length);
for (int i = 0; i < n; i++) {
str2 = String.valueOf(myt[i]);
str1 = str1 + " " + str2;
}
m_random = str1;
// 产生随即页面序列
text4.setText("" + m_random);
}
} catch (NumberFormatException ee) {
JOptionPane.showMessageDialog(this, "输入数字字符!", "警告!",
JOptionPane.WARNING_MESSAGE);
text1.setText("0");
text2.setText("0");
text3.setText("0");
text4.setText("");
FIFOTA.text1.setText("0");
FIFOTA.text2.setText("0");
FIFOTA.text3.setText("0");
FIFOTA.text4.setText("0");
FIFOTA.textarea.setText(null);
LRUTA.text1.setText("0");
LRUTA.text2.setText("0");
LRUTA.text3.setText("0");
LRUTA.text4.setText("");
LRUTA.textarea.setText(null);
OptTA.text1.setText("0");
OptTA.text2.setText("0");
OptTA.text3.setText("0");
OptTA.text4.setText("");
OptTA.textarea.setText(null);
}
}
public void FIFO() {
int m_absent;// 缺页数
double m_absentf;// 缺页率
int m_change; // 置换次数
double m_changef;// 页面置换率
int mym[];// 存放物理块中现有的页面号
int m1 = 0, r;
int as = 0;// 置换页面数
boolean x; // 页面是否需要置换
String str1, str2;
String m_list = "FIFO置换算法";
mym = new int[m];
for (int i = 0; i < n; i++) {
str1 = "";
str2 = "";
x = true;
for (int k = 0; k < m; k++) {
if (myt[i] == mym[k]) {
for (r = k; r < m - 1; r++)
mym[r] = mym[r + 1];
mym[r] = myt[i];
x = false;// 判断物理块中是不是有跟当前须替换的页面
break;
}
}
if (x) {
mym[0] = myt[i];
for (r = 0; r < m - 1; r++)
mym[r] = mym[r + 1];
mym[r] = myt[i];
as++;
}
int b;
for (int j = 0; j < m; j++) {
b = mym[j];
str2 = String.valueOf(b);
str1 = str1 + " " + str2;
}
m_list = m_list + '/n' + str1;
}
m_absent = as;
m_absentf = (double) as / n;
if ((as - m) <= 0) {
m_change = 0;
m_changef = 0;
} else {
m_change = as - m;
m_changef = (double) (as - m) / n;
}
FIFOTA.text1.setText("" + m_absent);
FIFOTA.text2.setText("" + m_absentf);
FIFOTA.text3.setText("" + m_change);
FIFOTA.text4.setText("" + m_changef);
FIFOTA.textarea.setText("" + m_list);
}
public void LRU() {
int m_absent;// 缺页数
double m_absentf;// 缺页率
int m_change; // 置换次数
double m_changef;// 页面置换率
int mym[];// 存放物理块中现有的页面号
int myb[];
int as = 0;// 置换页面数
myb = new int[n];
int m1 = 0, a;
boolean x; // 页面是否需要置换
String str2, str1;
String m_list = "LUR置换算法";
mym = new int[m];
for (int i = 0; i < n; i++) {
str1 = "";
str2 = "";
x = true;
for (int k = 0; k < m; k++) {
if (myt[i] == mym[k])// 判断物理块中的页面是否与当前页面相等
{
myb[k] = myb[0];
for (int j = 0; j < m; j++) {
if (j != k)
myb[j] = myb[j] + 1;// 使物理块中的每个未使用页面的时间增一
}
x = false;// 判断物理块中是不是有跟当前须替换的页面
m1++;
break;
}
}
if (x) {
int c = 0, d;
if ((i - m1) < m) {
a = i - m1;
} // 当前物理页面未填满时直接装入
else {
d = myb[0];
for (int h = 0; h < m; h++) {
if (d < myb[h]) {
d = myb[h];
c = h;
}
}
a = c; // 找出物理块中最久未使用的页面号
}
mym[a] = myt[i];// 将其替换
myb[a] = 0;
for (int k = 0; k < m; k++) {
if (k != a)
myb[k] = myb[k] + 1;// 使物理块中的每个未改变页面的时间增一
}
myb[a] = 0;
as++;
}
for (int j = 0; j < m; j++) {
int b;
b = mym[j];
str2 = String.valueOf(b);
str1 = str1 + " " + str2;
}
m_list = m_list + "/n" + str1;
}
m_absent = as;
m_absentf = (double) as / n;
if ((as - m) <= 0) {
m_change = 0;
m_changef = 0;
} else {
m_change = as - m;
m_changef = (double) (as - m) / n;
}
LRUTA.text1.setText("" + m_absent);
LRUTA.text2.setText("" + m_absentf);
LRUTA.text3.setText("" + m_change);
LRUTA.text4.setText("" + m_changef);
LRUTA.textarea.setText("" + m_list);
}
public void Opt() {
int m_absent;// 缺页数
double m_absentf;// 缺页率
int m_change; // 置换次数
double m_changef;// 页面置换率
int mym[];// 存放物理块中现有的页面号
int myb[];
int as = 0;// 置换页面数
myb = new int[n];
int m1 = 0, a;
boolean x; // 页面是否需要置换
String str2, str1;
String m_list = "Optimal置换算法";
mym = new int[m];
for (int i = 0; i < n; i++) {
str1 = "";
str2 = "";
x = true;
for (int k = 0; k < m; k++) {
if (myt[i] == mym[k])// 判断物理块中的页面是否与当前页面相等
{
x = false;// 判断物理块中是不是有跟当前须替换的页面
m1++;
break;
}
}
if (x) {
int c = 0, d;
if ((i - m1) < m) {
a = i - m1;
} // 当前物理页面未填满时直接装入
else {
for (int k = 0; k < m; k++) {
for (int h = i; h < length; h++) {
if (mym[k] == myt[h])// 判断物理块中的页面是否与未来页面相等
{
myb[k] = h;
break;
}
if (h == length - 1)
myb[k] = length;
}
}
d = myb[0];
for (int h = 0; h < m; h++) {
if (d < myb[h]) {
d = myb[h];
c = h;
}
}
a = c; // 找出物理块中最久未使用的页面号
}
mym[a] = myt[i];// 将其替换
myb[a] = 0;
for (int k = 0; k < m; k++) {
if (k != a)
myb[k] = myb[k] + 1;// 使物理块中的每个未改变页面的时间增一
}
myb[a] = 0;
as++;
}
for (int j = 0; j < m; j++) {
int b;
b = mym[j];
str2 = String.valueOf(b);
str1 = str1 + " " + str2;
}
m_list = m_list + "/n" + str1;
}
m_absent = as;
m_absentf = (double) as / n;
if ((as - m) <= 0) {
m_change = 0;
m_changef = 0;
} else {
m_change = as - m;
m_changef = (double) (as - m) / n;
}
OptTA.text1.setText("" + m_absent);
OptTA.text2.setText("" + m_absentf);
OptTA.text3.setText("" + m_change);
OptTA.text4.setText("" + m_changef);
OptTA.textarea.setText("" + m_list);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == randomButton) {
misInPut();
}
if (e.getSource() == button1) {
FIFO();
}
if (e.getSource() == button2) {
LRU();
}
if (e.getSource() == button3) {
Opt();
}
if (e.getSource() == clearButton) {
text1.setText("0");
text2.setText("0");
text3.setText("0");
text4.setText("");
FIFOTA.text1.setText("0");
FIFOTA.text2.setText("0");
FIFOTA.text3.setText("0");
FIFOTA.text4.setText("0");
FIFOTA.textarea.setText(null);
LRUTA.text1.setText("0");
LRUTA.text2.setText("0");
LRUTA.text3.setText("0");
LRUTA.text4.setText("0");
LRUTA.textarea.setText(null);
OptTA.text1.setText("0");
OptTA.text2.setText("0");
OptTA.text3.setText("0");
OptTA.text4.setText("0");
OptTA.textarea.setText(null);
myt=new int[0];
}
}
}
//randomNumber.java
public class randomNumber {
int[] rand(int n, int length) {
int[] rand = new int[n];
for (int i = 0; i < n; i++) {
rand[i] = (int) (Math.random() * 1000 % length) + 1;
}
return rand;
}
}
//displayView.java
import java.awt.*;
import javax.swing.*;
public class displayView extends JPanel {
JTextField text1, text2, text3, text4;
JPanel pan;
JTextArea textarea;
public displayView() {
text1 = new JTextField(3);
text2 = new JTextField(3);
text3 = new JTextField(3);
text4 = new JTextField(3);
text1.setEditable(false);
text2.setEditable(false);
text3.setEditable(false);
text4.setEditable(false);
pan = new JPanel();
pan.setLayout(new GridLayout(4, 2));
pan.add(new JLabel(" 缺页数 "));
pan.add(text1);
pan.add(new JLabel(" 缺页率 "));
pan.add(text2);
pan.add(new JLabel(" 置换次数 "));
pan.add(text3);
pan.add(new JLabel(" 置换率 "));
pan.add(text4);
textarea = new JTextArea();
textarea.setEditable(false);
this.setLayout(new BorderLayout());
this.add(BorderLayout.CENTER, textarea);
this.add(BorderLayout.SOUTH, pan);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。