热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Redis的那些事:一文入门Redis的基础操作

Redis是什么Redis,全称是Remote Dictionary Service,翻译过来就是,远程字典服务。red

Redis是什么

Redis,全称是Remote Dictionary Service,翻译过来就是,远程字典服务。

redis属于nosql非关系型数据库。Nosql常见的数据关系,基本上是以key-value键值对形式存在的。

Key-value: 就像翻阅中文字典或者单词字典,通过指定的需要查询的字或者单词(key),可以查找到字典里面对应的详细内容和介绍(value)

Redis的一些特点:支持数据持久化、支持多种数据结构、支持数据备份、原子性操作等。

原子性:操作不能被中途打断。

Redis的由来

Redis的作者:意大利人 —— Salvatore Sanfilippo,现在至少四十几岁了,还在写代码。

Redis灵感来源:Alessia Merz,意大利一个舞者女郎。Merz在意大利有愚蠢的含义(俚语)。于是,Redis的默认服务端口号6379的来源灵感,来自于Mers这个词,详情可看九宫格输入法:

 1793e54dbfefd6cc9d60a3c10ef03126.jpeg

Redis的安装

Mac系统:brew install redis

Linux系列系统:apt-get install redis  或 yum install redis 或apt-get install redis-server 或 yum install redis-server

 9cda39102a3398a34a8cb4cbeebf7c75.png

Windows系统:下载地址:https://github.com/tporadowski/redis/releases

 5acdb23a32d2c4b84b198b688941aaf8.png

Redis启动方式

Mac/Linux: 启动服务端redis-server  启动客户端:redis-cli   

如果需要后台启动,需要修改 redis.conf  文件,设置daemonize 为 yes

然后使用 redis-server /xxx/redis.conf 指定配置文件进行启动。xxx是指定的路径。

以下下载的姿势可能不对,版本有点低,所以后面暂且用windows环境下的进行测试。

 5961dce6172b4a919ff5ac3b5c8bffaa.png

Windows启动方式:双击redis-server.exe文件即可运行

 1e04f953eeeba5cfefcda04cc99f09f7.png

可以点击cli.exe文件,启动客户端。

输入ping,返回pong,代表服务是通的。

通过使用:set key value

可以用来设置一个键值对。

通过:get key

可以获取到key对应的value值。

 e70d5bcdb0191bb00e491d9d113d481f.png

通过命令创建redis密码:config set requirepass 密码

 97885f972c1408e831bdfde3ebeddd4b.png

通过使用 命令:auth 密码

进行redis权限验证。

使用:keys * 可以显示所有当前存在的key

 017843dce22bc23a8665d129dc9ef78e.png

使用命令:select index

可以选择指定的Redis的Index(数据库)。Redis默认有最多16个数据库(或者叫Index),默认从0开始。例如上面设置的默认是0库,选择1库进行查询,就会没有东西。

 155e2de1bec638a3fef5c9cc92f35079.png

Redis的数据结构

Redis的数据结构,体现在key-value的value上。Key默认都是字符串,value的基本数据结构包括字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(zset)

字符串数据结构

字符串在redis里面是可变的。用一个图来简单说明一下。

 a371ec28d0d87a9cc564cc28bf8d7638.png

字符串存储规则:Redis存储字符串期间,额外的空间分配规则是,当数据小于1MB的时候,每次扩容的空间是成倍增长的。大于1MB的时候,每次扩容的空间是1MB。

上面这段话如果不太理解,可以看Redis源码,源码内容如下。

 cdbacb237dd295a2a643d949724d268d.png

 并且String单个数据最大的长度为 2^32-1=512M

关于字符串的其他操作:

使用命令:exists key

可以查看是否存在该key;

使用命令:del key

可以直接删除指定的key以及对应的value。

 c284aeba688ad4475f4f08ac5a8de0d4.png

如果已有key,使用:set key xxx

会直接把key原有的值设置为xxx

 77e9592a3aafb7bf1da3c34f92dd64cd.png

使用命令: expire key second

可以给指定的key设置过期时间。例如我设置了10s过期,十秒以后,就会被自动销毁了。

 8119ac45891724df8fd28df89b9acaf9.png

 使用命令:setex  key  second  value

可以设置key-value键值对,并且可以同时设置过期时间的秒数。

 04c520969cda6cdd663599c53c9ef852.png

 使用命令: ttl key

可以查看指定的key的剩余的过期时间。

 5e18868f7cd8ced1a16e6043642fc33e.png

 对于数值型的字符串,可以使用命令:incr key 和 decr key 进行自增1和自减1

使用命令:incrby key number 和 decrby key number 进行自增或自减指定的数值。

 47637ee2cac19f2bdd5a3280a722b375.png

 可以通过批量设置命令:mset key value key value…… 

进行批量设置。

通过:mget key key key……

进行批量读取。不过mget批量读取的结果集是个列表(因为带有序号)

 8573c46e2ab275fe21d193a2fe34215c.png

 列表数据结构

Redis的列表,最大可以存储40亿+个元素(2^32-1)。

列表的插入速度很快,时间复杂度是O,但是当数据量特别庞大的时候,使用索引进行查询操作会变得很慢,因为通过索引定位查询的时间复杂度为O(n)。

设置列表的key和value命令:lpush key value value value ……

可以设置一个key,带有多个元素的列表。l:left,代表的是左边,相当于每个元素是从左边被写入的(倒序插入)。

使用命令:lpop  key

可以取出最左边的元素的值,同时会把该值舍弃掉。 

 dc1aa5f13e9fdcd7f445037f540cfcc6.png

 rpush是依次从右边插入(正序插入),rpop是取出最右边的元素的值,然后舍弃掉。r: right,代表右边

 7175477226147444f0e943d02166f21a.png

 可以使用命令:linsert key before|after 指定的value 要插入的value

进行插入元素。before 会把要插入的元素插入到指定的value的前面;after会把要插入的元素插入到指定的value的后面。

 0f96c134bdfd899a11ed26f4cfa63093.png

 通过使用命令:lset key index 新的value

可以把列表指定的索引对应的值给替换掉。

 4874f279ff659b50dce9f6faef1a7e1b.png

 可以通过命令:lindex key index

获取指定的索引的值,并且不会被舍弃。操作索引期间,需要注意时间复杂度,元素多的情况下慎用。

 4d785b8c1dfc35c51907748c2fc8261a.png

 可以使用命令:lrange key 起始索引 结束索引

获取在索引区间的所有元素。元素包含起始索引和结束索引的值。

 f2a1c2da5f66188c314e2fad38784442.png

 列表还可以用来当作消息队列使用,因为列表存取的一些方式,可以用来先进先出、先进后出等堆栈操作,先进先出与消息队列机制雷同。

 哈希数据结构

哈希数据结构,可以当作是字典(key-value)里面嵌套了个字典(value数据类型又是一个 key-value的结构)。类似Json,或者类似俄罗斯套娃,例如:

person:{

 “Name”:”wesky”,

  “age”:18

}

使用命令:hset  key field value

可以设置哈希数据结构的key,以及一个属性和属性对应的值。

使用命令:hget key field

可以获取指定哈希数据的key对应的属性的值。

使用命令:hmset key field value field value ……

可以批量设置哈希数据指定key的多个属性和值。

使用命令:hmget key field1 field2 ……

可以批量获取指定的key下指定的多个属性的值。

使用命令:hgetall key

可以获取指定的哈希数据的key下的所有属性和值的列表。如下图所示。

 fc504d228ce77f25ea67eb4fea15744b.png

使用命令:hkeys key

可以获取哈希数据指定key的所有属性名称的列表;

使用命令:hvals key

可以获取哈希数据指定key的所有属性的值的列表;

使用命令:hlen key

可以获取到哈希数据里面指定key的属性个数。

 f6a5e9e820b8b645cefcff0fc8edb598.png

集合数据结构

集合结构也可以看成是一个没有属性值的哈希数据结构,并且属性不能重复且无序的。

类似于:

Person{

 “name”,

 “age”

}

使用命令:sadd key field1 field2……

可以设置集合的key和元素集。由于集合是不可重复的,所以重复新增的元素会被自动剔除。

 c04b8d8c5b1199b55900ca830b657745.png

使用命令:smembers key

可以返回指定集合的所有元素;

使用命令:scard key

可以查询集合元素的个数;

使用命令:srandmember key (number:可选)

可以随机返回指定集合的一个或多个元素。不指定个数,即返回一个。

 1d6039e4791ec670f829757408bdec16.png

集合数据,可以进行一些集合运算操作。

命令:sdiff  key1  key2

可以比较集合key1和集合key2的差集,差集结果为写在前面的集合元素减去后面集合的元素;

命令:sinter key1 key2

可以获取到集合key1和集合key2的交集。

 a0bd7147bf221dfdeb1fd58d6e7178e6.png

命令:sunion key1 key2

可以获取集合key1和集合key2的并集。

 44d8beb4815279f69dec8a16a3316200.png

有序集合

有序集合比较常见的一个场景,是用来做排行榜。

命令:zadd  key  score1(分数,用于排行的值)  member1(集合的元素)  score2(分数,用于排行的值)  member2(集合的元素) ……

可以用来新增有序集合。其中,分数代表权重,值越低,排越前。

命令:zrange key 起始索引 结束索引

可以查询指定集合索引区间的所有元素的属性。

命令:zincrby key 增加权重值 menber

可以对有序集合指定的元素进行增加权重(对分数进行增加指定的值)

 0011222c7970287f2428415fdd8c78e4.png

 命令:zcard  key

可以获取有序集合的个数;

命令:zcount  key  最小分数  最大分数

可以获取到有序集合在指定的分数区间的所有元素;

命令:zcount key member

可以获取有序集合里面指定的元素当前的分数;

命令:zrange key  起始索引 结束索引 withscores

可以获取到有序索引里面指定的索引区间内所有的元素以及元素对应的分数。

 a3d3ec11afdc8a561deabf5595db2246.png

备注:以上五种数据结构,都属于容器型,它们的特点是,当没有元素的时候,会被自动释放掉。

Redis分布式锁

Redis的操作是原子性的,如果存在多客户端同时操作的情况下,会发生一些干扰问题。原子性指的是,redis在进行读写期间是不会被打断的,会一直进行到底。下面用一个图片进行说明。

 475a5df6672854449fde2f0a63c55241.png

如上图所示,A和B同时都要操作Redis数据库里面的Key1。假设此刻Key1存储的是银行的存款,然后在A的地方消费掉了,此刻A触发了扣减余额的操作。这个时候,修改redis的值是通过先读取值出来到内存里面,然后进行扣减的;读取出来的时候,还没扣减完成,这个时候B(比如说是信用卡自动还款扣钱)也要扣减Key1的余额,也要进行先读取出来,然后才进行扣减。由于Redis是原子性操作,所以A的步骤不会被打断,B也不会被打断。这个时候,A扣减完成了,例如原本余额是100元,扣减了10元,A更改完毕以后,值变成了90元。此刻,B也要扣减,例如扣减20元,但是读取的是A改完之前的值,所以改完以后是80元。以上就产生了冲突,于是就有了Redis的分布式锁用来避免这个问题。

 通过命令:set  key  value  ex  second  nx

可以设置一个锁,key代表锁的名称,value是值;second是锁的超时时间。

如下图所示,我开了两个客户端,并且标注了我的操作顺序号。

076a62cc17c97667736ee4ccf5de25e5.png

锁如果没有过期或删除,其他客户端创建锁会失败;但是其他客户端也可以删除锁,所以具有一定风险。建议可以对锁设置不同客户端所需要的不同的值用来区分。然后把需要操作的地方,放到锁里面操作,来避免产生的同时操作产生的问题。

例如伪代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

If(exists(lock))

{

  return false; // 存在锁,修改失败

}

Else
{

  Set lock true ex 5 nx;

  Set key1 100;

  del lock;

  Return true; // 修改成功

}

Redis位图

    位图的最小单位是bit,每个bit的值只能是0和1,位图的应用场景一般用于一些签到记录,例如打卡等。

场景举例: 例如某APP要存储用户的打卡记录,如果按照正常的思路来做,可能是用户每天是否打卡的记录都单独设置一个key-value键值对来存储,这样的话,每个用户每天都需要耗费一个键值对空间。而如果是位图,就可以很方便地通过位图来进行记录,例如如下图:

 359452f7ae4374b294a044a7782b39a3.png

位图不算基础数据结构或者特殊数据结构,其本质上还是字符串。由于每个bit代表一个数据,所以还可以当作是bit数组来看待。

可以通过命令:

setbit key 偏移量(索引位)  value(0/1,默认是0)

进行设置对应位置的位图数据。

通过命令:

 getbit key 偏移量

可以获取到对应的位图索引数据。

也可以通过: 

get key 

来获取位图对应的字符串信息。

 73777f7762b2031d80e634db6a97024f.png

例如hello字符串的ascii码对应的二进制,分别是:

h: 01101000

e: 01100101

l: 01101100

l: 01101100

o: 01101111

以下设置字符串hello的位图操作,如图所示,字符串对应二进制数拼接起来的二进制,值为1所在的bit索引位(offset),使用:

setbit key offset 1 

进行设置1即可。

 46660bf20c443d713e3693f2d09bdfc7.png

setbit/gitbit 和 set/get 实际上是可以互相转换的,只是一种是操作bit位,一种是操作直接的值。同时可以互相交叉操作使用,例如setbit存储,get读取;set 存储,getbit读取等等。

可以通过命令: bitcount key 起始字符索引 结束字符索引

对指定key里面的数据,指定的字符索引区间内,获取到对应的位图数据是1的个数。如果不指定,则会获取到全部字符串对应位图的1的个数。如下图所示,结合以上二进制数据可知,h字符有3个1,o字符有6个1。

 29d15327c5480e451307a94e997bc819.png

以上指令操作可以适用于在类似打卡天数统计上使用,可以快速统计出区间内为1的数据个数。

通过命令:bitops  key  bit值(0/1)  起始字符索引 结束字符索引

可以获取到指定的区间内,第一次出现指定的bit值(0或1)所在的位图索引。如果不指定区间,默认代表字符串全部区间。如下图所示,hello里面,第一次出现1是在位图的第一个索引位置;第一次出现0是在第0个位图索引位;字符索引位为1代表第二个字符,第一次出现的值为1的位图索引位置为9。

注意: 字符串的索引,0到N,0代表第一个字符,例如’h’。位图的索引,也是0到N,0代表位图上面第一个bit位,值为0或者1,例如h的位图索引位置是0的值是0  (01101000)

 2dc111a9fb99db298ecbf0fb4d1d53f2.png

可以通过命令:

bitfield  key get 类型 位图索引

来获取指定类型数据的ascii码。

例如,以下截图中,命令:

bitfield hello get u8 0 

其中,u8代表类型,u开头代表无符号数据,8代表获取8个bit位。如果是有符号的数据,是以i开头的。最后面的0,代表要获取的起始位图下标索引,此处是第0个索引。

hello五个字符,对应的ascii码分别为:104,101,108,108,111

如果以上命令的类型 u8 换成 u4 ,则获取到的值是0110,对应的值是6;以此类推。

也可以并列get获取,例如:

bitfield  key  get  type1 offset1  type2 offset2 ……

其他玩法,大佬们可以自己尝试。我这边有关操作可以参考如下截图所示内容。

 2174119391fbd5607600f129be96029b.png

通过命令:

bitfield key set type 位图索引 ascii码

可以把对应的ascii码根据类型写入到指定的索引中,并且会返回原来索引被替换的ascii码值。

例如下图所示操作,位图索引从0开始,代表第一个字符h所在位置。97代表a的ascii码,执行以后,返回104(h的ascii码),并且通过get命令可以查看到字符串已经被替换了。

 a2ed22c24990d88dba09cbf2a63ef3ef.png

可以使用命令:

bitfield  key  incrby  type  索引  自增值

对指定类型和索引区间的值进行累加 ,如下图所示。h通过 u8 类型自增1,即h+1=i

注意:对于累加的数据不能超出指定类型的最大值,例如 u4 最大值是15,累加到15以后会自动折返为0。

 f258049db1aaa30fd23fc1cd91954006.png

 针对以上会出现折返的情况,可以使用溢出报错或者保持最大或最小值的方式来避免折返的情况。

使用命令:

Bitfield  key  overflow  fail  incrby  type  offset  value

可以实现溢出的时候,会返回nil;

使用命令:

Bitfield  key  overflow  sat  incrby  type  offset  value

可以实现当要溢出的时候,还是会返回当前的最大值或最小值。如下图所示。

 40888cd1810b17db983f0b64f898d6cc.png

HyperLogLog

HyperLogLog是一种可以快速去重的数据结构。但是有一定的误差率,大概在0.81%左右。应用场景一般是在需要针对一些大数据量的情况下进行去重计算大概的统计值使用,例如网站的PV量等等。

使用命令:

pfadd   key  value1  value2 ……

可以添加对应的多个数据集到指定的key里面去。

如果添加已经存在的数据,会被自动去重。

使用命令:pfcount key

可以统计数据集的个数。

使用命令:pfmerge 目标key  源key1   源key2  ……

可以对多个不同的key进行数据合并,并且数据集重复的会自动排重。

使用HyperLogLog的用途,是在针对大数据量的情况下,在允许一定的容错率的情况下,用它可以节约资源并且快速地进行排重。例如使用set来设置数据,资源损耗肯定是巨大的;但是使用hyperloglog来处理,资源损耗是固定的12kb,可以处理的数据量大约是2^64个数据。

 9df6b514816d6c2bfec6e1271dec3cfc.png

冷门科普:命令是pf开头,是为了纪念HyperLogLog的作者——Philippe Flajolet 

布隆过滤器

布隆过滤器,最常见的场景是商品推荐业务。例如购物时候浏览的信息被记录以后,可以进行推荐其他同类型的其他商品。推荐的其他商品不会和浏览过的商品重复(去重),但是也存在一定的误差。

布隆过滤器源码地址链接:

https://github.com/RedisBloom/RedisBloom

先进行下载,下载方式可以按照自己喜欢的方式下载。例如此处我下载到d目录下的wesky/bloom文件夹下。

 5c885dd2326480e05645f592a2b2dfa8.png

然后进入到文件夹内,使用make命令进行编译。编译成功的话,会产生一个 redisbloom.so的文件。如下,我也很尴尬,没成功,就暂且到这里吧。

 060ebd65dd36cb032beb448f8325afce.png

假如上面配置成功的话,启动redis服务的时候,可以把.so文件配置到redis.conf配置文件下,例如我上面所在的位置,新增的样式如下:

loadmodule D:/Wesky/Bloom/RedisBloom/redisbloom.so

或者使用命令启动的时候,使用命令进行指定:

redis-server --loadmodule D:/Wesky/Bloom/RedisBloom/redisbloom.so

由于当前我本机无法编译布隆过滤器源码,所以就暂且到这吧,请见谅。

布隆过滤器下,会有一些命令,供参考,大家可以根据自己情况,进行自己尝试,当作是留个悬念了。

命令:

bf.add  key  xxx

bf.madd  key  数据1   数据2 ……

bf.exists key 数据

bf.mexists key 数据1 数据2 ……

……


推荐阅读
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
author-avatar
那一世我给不了你的温柔
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有