R语言语法基础(说人话版)

在Rstudio中使用ctrl+回车来执行某一行的代码

在R语言中,通常不需要像C语言一样在每条语句的结尾添加分号来表示语句结束。R语言是一种脚本语言,它使用换行符来分隔语句,因此分号通常是可选的,除非你想在同一行上写多个语句。在R中,分号用于分隔多个语句,但它不是必需的,尤其是在每行只有一个语句时。因此,一般情况下,你不需要在每个语句的末尾加上分号。

R对象的属性包括

类型(class):是函数还是数据?

模式(mode):数据结构里面每个元素的类型

什么意思呢,比如我们C语言中有数组,数组中有元素,元素有类型,同样的R语言中也有向量,向量里面有元素,元素有类型,元素的类型就是就被称为模式。当然这里只是以向量来举个例子,向量只是R语言中的其中一种数据结构。根据数据结构中元素的种类可以把模式分为数值型,字符型,复数型,逻辑型,函数,表达式等。其中数值型又包括整形,单精度实型,双精度实型,逻辑型

字符型的变量在赋值的时候要用双引号引起来,这与C语言中的字符赋值是不一样的,因为在C语言中字符用单引号引起来

长度(length)

补充:%/%是整除的意思

我们可以看到上面的逻辑运算那一列中分别有两个逻辑与和两个逻辑或,那么一个x&y和x&&y有什么区别?假如有x和y两个向量,它们均包含五个元素,那么x&y会把x和y中的对应位置的每个元素都进行与操作,而x&&y只会对x和y这两个向量的第一个元素进行与操作。

一个语句写完换到下一行之后有一个>,一个语句没有写完也可以换到下一行,这时候会有一个+

求助符可以不用,因为我们可以在右下角的help中搜索需要的函数

R的数据结构

对于一般的缺失值,R并不会任由这个位置空着,而是会填充上一个NA的特数量

对于运算产生的缺失值,比如对负数求对数,其他语言会报错,而R则会产生一个NaN的特数量

针对上面两种缺失值,R有检验的手段,也就是函数is.na()和is.nan(),因为在处理数据的时候会产生缺失值,我们希望把缺失值删掉或者填充为平均值,就要用到这两个函数,搭配循环语句等手段

正无穷大:lnf

负无穷大:-lnf

R同样提供了对正负无穷大进行检验的手段,就是isfinite()和isinfinite(),分别用于检测正无穷大和负无穷大

常用数据结构

R语言中常用的数据结构包括:向量、因子、数组、矩阵、数据框、时间序列、列表

其中列表和数据框允许多种模式。其余的只允许一个模式

向量

R语言中向量的下标是从1开始的,而C语言等高级语言中下标是从0开始的

对标的是C语言中的数组,里面存放着一系列类型相同的元素

建立向量可以通过以下方式:

1.使用seq()函数

若向量具有较为简单的数学规律,可以使用seq函数来生成一个向量,实际上seq常用于生成一个简单的等差数列

seq(from, to, by = (to - from)/(length.out - 1), length.out = NULL)

其中:

  • from: 序列的起始值。
  • to: 序列的结束值。
  • by: 序列值之间的步长。默认情况下,步长被设置为 (to - from) / (length.out - 1)。
  • length.out: 期望的输出序列的长度。如果指定了 length.out,则 by 将被忽略。

seq()函数的功能是生成从起始值到结束值的等间隔序列。它可以用来生成从一个值到另一个值的数字序列。例如:

虽然这个函数有四个参数,但是在使用的时候通常不需要输入四个参数,只需要三个,比如seq(1, 10, by = 2)就没有输入length.out,这时候length.out就被默认为NULL。再比如seq(1, 10, length.out = 5),by就默认为(to - from)/(length.out - 1),输出: 1 3.25 5.5 7.75 10

2.使用c()函数生成向量:

常用于创建没有规律的向量,这也是后期用的最多的一种方式

vector

比如用c()创建一个字符型的向量

x

3.使用:运算符生成向量:

冒号运算符通常用来生成步长为1的等差数列

比如x

4.使用rep()函数生成向量:

常用于创建具有重复规律的向量,还可以与其他的操作符结合使用,比如

x=rep(1:3,3),执行之后x是123123123

逻辑型向量

逻辑型向量里面的元素只能包含TRUE FALSE 或者缺失值NA

在运算的时候TRUE会被当做1,FALSE会被当做0

什么时候会用到逻辑向量?

1.对向量里面的元素进行判断的时候,比如要统计向量中有多少个大于零的元素,有多少个小于零的元素

2.对图中的点进行赋值的时候,比如满足某条件的值赋成红色

向量运算中的循环法则

在第一个例子中两个不等长的向量进行相加,一个向量是1 2,一个向量是1 2 3 4,向量再进行加减的时候是对应位置的元素相加减,此时的第一个向量会被补齐到四个元素,补齐遵循循环法则,也就是第一个向量会被补齐为1212,然后与第二个向量1234对应的位置相加,最终得到的向量是2446,实际上Rstudio非常智能,他在显示的时候自动在两个相邻数之间加了一个空格,使我们能够更清晰的看到向量的元素是2 4 4 6

第二个例子中第一个向量是1 2 3 4,第二个向量是1 2 3 4 5 6 7,在相加的时候遵循循环法则,补齐到7个元素,变成1 2 3 4 1 2 3,与第二个向量相加之后的到结果为2 4 6 8 6 8 10,可以看到结果已经显示在了屏幕上面。但是同时下面报了一个错误,这是因为这两个向量的长度不成倍数关系。

向量子集(元素)的提取

提取元素使用的是[]

假如有一个向量x

正的下标:提取向量中对应的元素,如执行x[1]之后x变成了42,执行x[c(1,4)]也就是提取第一个和第四个元素,x变成了42 9,要提取多个元素只能在[]里面使用c(),不能直接写x[1,4],这种写法会报错

负的下标:去掉向量中对应的元素,如执行x[-2]之后x变成了42 64 9

逻辑运算:提取向量中满足条件的元素,如执行x[x>10]之后x变成了42 64,同时我们可以用sum(x>10)来统计原来的x向量中大于10的个数,因为sum函数是把TRUE 和 FALSE加起来

矩阵

这里的矩阵就是线性代数里面学过的矩阵,有行和列,矩阵的元素也必须是同一种类型也即一个矩阵只能有一种模式

矩阵的建立

矩阵的建立使用的是函数matrix

matrix的函数原型和参数意义如下

matrix(data=NA,nrow=1,ncol=1,byrow=FALSE,dimnames=NULL)

data:为一个向量,其元素用于构建矩阵

nrow:矩阵的行数

ncol:矩阵的列数

byrow:是否按行填充

dimnames:矩阵行列的名字

如图

这样就创建了一个四行两列的矩阵a

实际上也可以不写nrow和ncol,直接写matrix(x,4,2),也可以创建a矩阵

可以看到a矩阵的行和列显示的时候都有一个逗号,R语言规定逗号前面的是行,后面的是列

我们在创建矩阵的时候省略了matrix函数原型中后面的2个参数,同时观察到a矩阵是按照列来进行填充的,这是因为第三个参数byrow我们不写,默认是FLASE,顾名思义也知道不是按行来排列的,如果我们想要按行来用向量x填充矩阵a,我们就要把第三个参数byrow写成TRUE或者T,如图

这样就是按行排列的了

可以使用下面的函数查看

发现a有四行两列,模式是numeric,类型是matrix,长度是8

如何从矩阵里面提取元素?

同向量的下表类似,从矩阵里面提取元素也是用的[]操作符,这个操作非常重要,因为我们在处理数据的时候要经常需要提取某一行或者某一列的数据

对于一个矩阵a来讲

a[,j]表示取出矩阵第j列的元素

a[i,]表示取出矩阵第i行的元素

a[i,j]表示取出第i行第j列的元素

a[-i,]表示取出除第i行以外的所有元素

a[,-j]表示取出除第j列以外的所有元素

例如

我们也可以在提取的时候加上一个drop=F

这样提取出来的第一行不仅仅是三个元素,而是由这三个元素组成的一个矩阵

这是因为R的缺省规则是返回一个位数尽可能低的对象,这可以改变drop的值来改变

再来看一个比较复杂的代码

从外面来看是要提取a的某一行,再看里面,又是提取了a的第二列,也就是3和4,然后找出这两个数里面比3大的数,也就是四,因此最终里面就是4,但是a只有两行,这时候R会提取有大于三的数也就是4的这一行,也就是第二行。

数据框

数据框实际上就是一种推广了的矩阵,数据框的每一列必须是统一模式,且长度要相等,也就是说不同列可以是不同模式的数据,比如第一列可以保存字符,第二列可以保存数值

如何建立数据框?

1.通常使用函数data.frame()来建立数据框

可以使用上面的代码创建一个如图所示的数据框

这里的INDEX和VALUE并不是函数原型的参数,而是我们随便起的,表示列名,比如我还可以写成A和B,就创建了一个这样的数据框

通常情况下不需要给行号起名,这样默认是从1开始顺序递增,当然如果想,也可以对行进行命名,比如

这样就行号就从3开始递增了

row.names必须是一个可变的值,像row.names=3这样的写法会直接报错

2.把文件导入R来建立数据框

read.table 读取表格文件

read.csv() 逗号分隔的文件

read.delim() tab键分隔的文件

数据框子集的提取

数据框子集的提取与矩阵基本相同,主要的差别在于在提取数据框的某一列的时候可以使用变量的名称

格式:foo[row,column]

其中foo是数据框的名称,row是需要提取的行号,column是需要提取的列号

如图创建了一个这样的数据框

df[df[,1]>50,]这个语句首先能看到他的逗号在前面,表示提取某一行,提取的是哪一行,需要看df[,1]的结果。df[,1]表示提取第一列,然后后面还有>50,因此提取的行就是第一列中包含大于50的元素的那行。也就是第三行。

要提取数据框的某一列,还可以使用$符号直接提取

也可以使用$符号增加一列

列表

在分析复杂数据的时候,仅有向量和数据框是不够的,有时候需要生成包括不同类型的对象,R的列表就是包含任何类型的对象,列表与数据框最直观的区别就是列表是一维的,而数据框是二维的。下面是列表与数据框的区别

  1. 数据类型
    • 列表(list):列表可以包含不同类型的数据对象,例如向量、矩阵、数据框、其他列表等。因此,列表是一种混合型的数据结构。
    • 数据框(data frame):数据框中的每一列必须是相同的数据类型,可以是数值型、字符型、因子型等。数据框类似于电子表格,每一列是一个变量,每一行是一个观察值。
  1. 结构
    • 列表(list):列表是一维的,每个元素可以是任意的R对象,包括其他列表。列表的结构可以是任意的,因此可以具有不同的长度和结构。
    • 数据框(data frame):数据框是二维的,由行和列组成。每一列代表一个变量,每一行代表一个观察值。数据框具有表格状的结构,所有的列必须有相同的长度。
  1. 索引
    • 列表(list):列表的元素可以通过名称或索引来访问。每个元素都有一个名称,可以使用该名称或索引来访问该元素。
    • 数据框(data frame):数据框中的变量可以通过名称来访问,也可以通过行列索引来访问。
  1. 用途
    • 列表(list):列表通常用于存储不同类型的数据对象,并且可以具有任意的长度和结构。列表在构建复杂数据结构时非常有用。
    • 数据框(data frame):数据框通常用于存储二维数据,每一行代表一个观察值,每一列代表一个变量。数据框是进行数据分析和统计建模的主要数据结构之一。
  1. 创建方式
    • 列表(list):可以使用list()函数来创建列表,通过向list()函数传递不同的R对象来创建列表的不同元素。
    • 数据框(data frame):可以使用data.frame()函数来创建数据框,需要提供变量作为列的内容,并且可以指定变量名称。

如何创建列表?

通常使用list函数来创建列表,函数原型如下

list(..., recursive = FALSE)

其中...是任意数量任意类型的R对象,中间用逗号分隔,而且每个对象都要有自己的名字,比如

列表a里面包含了一个向量x,一个矩阵y,和一个数值z

如何提取列表子集?

因为列表里面的元素都是按照顺序排列的,因此也可以通过下标来确定唯一的元素,当然也可以这样提取了,比如

这样就提取了列表a中的第一个元素

使用$符号提取

又由于列表里面每个元素都有自己的名称,因此可以直接使用名称来唯一确定某个元素,当然也就可以提取,比如

这样就提取了名称为x的元素

也可以使用两个[]来提取元素

仔细观察发现这三种提取方式虽然目的都是提取列表的第一个元素,但是提取的结果还是有细微的差别,第一种方式运行结果有个$x,但是后两种没有。这是因为一个[]表示提取的是列表的子列表,也就是说第一个运行结果是一个子列表,你可能有疑问,第一个元素x不是一个向量吗?怎么成列表了?虽然x是一个向量,但是他是列表a的子元素或者说子集,列表的子集被称作子列表,很合理吧。

而后两种提取方式则是提取了x这个向量中元素的值,返回的是一个向量

知道了这一点之后,来看这样的代码

因为a[[1]]表示提取了第一个元素并且是个向量,因此我们可以再次提取这个向量的某个元素,这里后面的[2]就表示提取a列表第一个元素也就是x向量的第二个元素,x向量是1 2 3 4,他的第二个元素就是2

再比如

a$y表示提取了列表a中名称为y的元素,y是一个矩阵,再[2,]表示提取这个矩阵第二行,最终结果就是2 4

因子

在R语言中,因子(Factor)是一种用来表示分类变量的数据结构。它将离散的数据分为不同的水平(levels),并将数据存储为整数值,而不是直接存储为字符或字符串。比如

运行结果为

因子的应用比较少,就不过多解释了

R程序设计

1.条件语句

形式1:if().... else...

形式2:ifelse(条件,yes,no)

来看一个例子

运行结果是一个散点图

这段代码首先生成了一个长度为10的随机数向量x,这些随机数服从均值为10,标准差为2的正态分布。具体来说,rnorm(10, mean = 10, sd = 2)这部分生成了这个随机数向量。

然后,使用plot()函数对这个向量进行绘图。在plot()函数中,x是要绘制的数据,col参数用于指定点的颜色。在这里,使用了一个条件语句ifelse(x > 1,"red","black"),如果x的值大于1,则点的颜色为红色,否则为黑色。

另外,font.lab=2参数用于设置坐标轴标签的字体样式。

最后,ylab和xlab分别指定了y轴和x轴的标签。

综合起来,这段代码的作用是生成一个正态分布随机数向量,并以散点图的形式将这些数据绘制出来,点的颜色根据数据值是否大于1来区分。

循环语句

在R语言中,循环语句主要有三种形式:for、while 和 repeat。下面分别介绍它们的用法:

1. for 循环

for 循环通常用于遍历一个序列(如向量、列表等)中的元素,执行指定的操作。其基本语法为:

for (variable in sequence) { # 待执行的代码块 }

  • variable 是循环中的变量,它在每次迭代时会取序列 sequence 中的一个值。
  • sequence 是要迭代的序列,可以是向量、列表等。
  • 在每次迭代时,variable 取 sequence 中的一个元素,然后执行代码块中的操作。

例如,遍历一个向量中的元素并打印它们的值:

2. while 循环

while 循环会在指定条件为真时重复执行代码块,直到条件为假为止。其基本语法为:

while (condition) { # 待执行的代码块 }

  • condition 是一个逻辑表达式,当其为 TRUE 时,循环会继续执行;当其为 FALSE 时,循环结束。
  • 在每次迭代时,都会检查 condition 的值,如果为 TRUE,则执行代码块。

例如,计算一个数的阶乘直到结果大于100:

3. repeat 循环

repeat 循环会无限地执行代码块,直到遇到 break 语句为止。其基本语法为:

repeat { # 待执行的代码块 if (condition) { break # 在满足条件时终止循环 } }

  • repeat 循环会一直执行其中的代码块,直到遇到 break 语句。
  • 通常在循环内部使用 if 语句和条件来控制何时退出循环。

总结:

  • 当你需要对一个序列中的每个元素执行相同的操作时,选择 for 循环。
  • 当你需要在满足某个条件时重复执行某段代码时,选择 while 循环。
  • 当你需要在满足某个条件前无限循环执行某段代码时,选择 repeat 循环。

利用向量化简化循环或控制结构

在R语言中,向量化是一种利用向量操作来简化循环和控制结构的技术。通过向量化,可以避免显式地使用循环来操作向量中的每个元素,从而提高代码的效率和可读性。以下是几个利用向量化简化循环和控制结构的例子:

计算向量元素的平方

筛选满足条件的元素

计算两个向量对应元素的和

文件操作

先来看两个最常用的函数setwd()和dir(),他们分别用于设置当前工作目录和列出指定目录中的文件和子目录。

来介绍setwd()

这个函数的使用比较简单,只需要在括号里面输入我们想要设置的工作路径即可,setwd这个函数的参数不允许省略

再来介绍dir()

我的D盘里面有一先个叫做123的文件夹,里面放了这么几个文件

在我使用dir函数并把路径设置为"D:/123"并运行就输出了这些文件名

如果在dir里面不放任何参数,则会显示当前工作路径下的所有文件名称

除了setwd函数和dir函数之外,R语言中还有需要进行文件操作的函数,比如

  1. file.exists():检查文件或目录是否存在。
  2. file.info():获取文件或目录的详细信息,如大小、修改时间等。
  3. list.files():列出目录中的文件。
  4. list.dirs():列出目录中的子目录。
  5. file.create():创建文件。
  6. file.remove():删除文件。
  7. dir.create():创建目录。
  8. file.rename():重命名文件。
  9. file.copy():复制文件。
  10. file.path():构建平台无关的文件路径。
  11. read.table() / read.csv():读取表格数据文件。
  12. write.table() / write.csv():将数据写入表格数据文件。

这些函数没有setwd和dir函数这么重要,如果需要使用的时候可以再学习他们的用法

数据的读取与存储

在R中有一个自带的数据集叫做mtcars,它包含了汽车的部分信息,下面将介绍一些读取其中信息的方式以及运行结果

head(mtcars):显示前几行

tail(mtcars):显示后几行

str(mtcars) 显示数据框结构的函数,它会显示数据框的基本结构信息,包括列名、列数据类型以及数据框的前几行数据。

'data.frame': 32 obs. of 11 variables:这个输出是str()函数的结果,它提供了关于数据框结构的描述信息。让我们逐步解释这个输出:

  • 'data.frame'::这部分指示了数据的类型,即数据框(data frame)。
  • 32 obs.:这表示数据框包含了32个观测值,即行数为32。
  • of 11 variables::这表示数据框有11个变量,即列数为11。

mode()函数用于返回对象的存储模式。对于数据框(如mtcars),这通常表示数据的总体类型。数据框通常被认为是一个列表,因此对于

mtcars这样的数据框,mode(mtcars)的输出结果为list

class()函数用于返回对象的类别或类别向量。对于数据框(如mtcars),这通常表示数据的类型。数据框通常被认为是data.frame类别的对象。

names(mtcars)返回mtcars数据框中所有列的名称,以字符向量的形式。

接下来介绍针对数据类型和针对数据结构的函数

数据类型包括数值型,字符型,逻辑型,复数型等,其中数值型又分为整数型和双精度型两种

数据结构包括向量vector,矩阵matrix,数组array,数据框data.frame,因子factor,列表list

以is.data.frame() 和 as.data.frame()为例

    • is.data.frame() 用于检查一个对象是否为数据框。如果对象是数据框,则返回 TRUE;否则返回 FALSE。
    • as.data.frame() 是用于将其他类型的对象转换为数据框的函数。它可以将矩阵、列表或向量等其他数据类型转换为数据框。

# 将列表转换为数据框 my_list <- list(x = 1:5, y = letters[1:5]) df_from_list <- as.data.frame(my_list) print(df_from_list)

read.table()

read.table()是R语言中用于从文本文件中读取数据并创建数据框的函数。它通常用于读取以制表符、逗号或其他分隔符分隔的数据文件。下面是read.table()函数的基本用法和一些重要参数:

read.table(file, header = FALSE, sep = "", quote = "\"'", ...)

  • file:要读取的文件的路径或连接。
  • header:一个逻辑值,指示是否将文件的第一行作为列名。默认为FALSE。
  • sep:一个字符,指定数据中字段之间的分隔符,默认为空白字符。
  • quote:一个字符向量,指定引号字符。默认为"\"'",表示双引号和单引号都被视为引号。
  • ...:其他参数,例如colClasses用于指定每列的数据类型。

举例:

# 从CSV文件中读取数据,并将第一行作为列名 data <- read.table("data.csv", header = TRUE, sep = ",") # 从文本文件中读取数据,字段之间使用制表符分隔 data <- read.table("data.txt", header = TRUE, sep = "\t") # 如果你知道每列的数据类型,你可以使用colClasses参数来提高读取效率 data <- read.table("data.txt", header = TRUE, sep = "\t", colClasses = c("numeric", "character", "factor"))

read.table()是一个非常灵活和常用的函数,可以根据你的需要进行各种配置,以读取各种格式的数据文件。

read.csv()

read.csv()函数与read.table()函数非常相似,但是它是read.table()函数的一个特殊版本,用于读取逗号分隔的数据文件(CSV文件)。它的使用方式与read.table()几乎相同,只是省略了一些参数的设置,因为在读取CSV文件时,常见的参数已经设定为适当的默认值。下面是read.csv()函数的基本用法:

read.csv(file, header = TRUE, sep = ",", quote = "\"", ...)

  • file:要读取的文件的路径或连接。
  • header:一个逻辑值,指示是否将文件的第一行作为列名。默认为TRUE。
  • sep:一个字符,指定数据中字段之间的分隔符。在CSV文件中,通常为逗号","。
  • quote:一个字符,指定引号字符。默认为双引号"\""。
  • ...:其他参数,例如colClasses用于指定每列的数据类型。

举例:

# 从CSV文件中读取数据,并将第一行作为列名 data <- read.csv("data.csv") # 如果你知道每列的数据类型,你可以使用colClasses参数来提高读取效率 data <- read.csv("data.csv", colClasses = c("numeric", "character", "factor"))

read.csv()是一个非常方便的函数,用于读取常见的CSV文件,省去了设置参数的麻烦。

write.table()函数是用于将数据写入文件的函数,它的作用是将数据框或矩阵写入到文本文件中。与read.table()和read.csv()函数类似,write.table()函数也是R语言中常用的数据输入输出函数之一。

write.table(x, file, sep = " ", quote = TRUE, row.names = TRUE, col.names = TRUE, ...)

x:要写入文件的数据框或矩阵。

file:要写入的文件路径或连接。

sep:一个字符,指定字段之间的分隔符。默认为一个空格。

quote:一个逻辑值,指定是否在字符字段周围加上引号。默认为TRUE,即加上引号。

row.names:一个逻辑值或字符向量,指定是否在输出中包含行名。默认为TRUE,即包含行名。

col.names:一个逻辑值或字符向量,指定是否在输出中包含列名。默认为TRUE,即包含列名。

...:其他参数,例如append参数用于指定是否将数据追加到现有文件中。

举例:

# 将数据框写入到文本文件中 write.table(my_data, "output.txt", sep = "\t", quote = FALSE, row.names = FALSE) # 将矩阵写入到文本文件中,不包含行名和列名 write.table(my_matrix, "output.txt", sep = ",", row.names = FALSE, col.names = FALSE)

write.table()函数可以将数据以指定的格式写入到文本文件中,方便进行数据的存储和共享。

相关推荐

  1. R基础语法

    2024-03-16 14:46:03       47 阅读
  2. R语言基础

    2024-03-16 14:46:03       34 阅读
  3. R语言入门】开启R的会并大步向前!

    2024-03-16 14:46:03       37 阅读
  4. 翁恺-C语言程序设计-10-0.

    2024-03-16 14:46:03       18 阅读
  5. R语言基础入门教程

    2024-03-16 14:46:03       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-16 14:46:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-16 14:46:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-16 14:46:03       87 阅读
  4. Python语言-面向对象

    2024-03-16 14:46:03       96 阅读

热门阅读

  1. 备战蓝桥杯Day28 - 贪心算法

    2024-03-16 14:46:03       39 阅读
  2. Vue3 onErrorCaptured errorHandler 异常处理

    2024-03-16 14:46:03       36 阅读
  3. linux--redhat系统Yum源配置

    2024-03-16 14:46:03       34 阅读
  4. 【统计】什么事 KPSS 检验

    2024-03-16 14:46:03       48 阅读
  5. python常用框架介绍

    2024-03-16 14:46:03       48 阅读
  6. STM32的IAP讲解

    2024-03-16 14:46:03       31 阅读
  7. 力扣日记3.16-【贪心算法篇】53. 最大子数组和

    2024-03-16 14:46:03       43 阅读
  8. tmux终端复用器

    2024-03-16 14:46:03       42 阅读
  9. 前端图片预加载和懒加载

    2024-03-16 14:46:03       38 阅读