• redis缓存数据库


    redis

    介绍

    redis是业界主流的key-value nosql 数据库之一。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

    Redis优点

    • 异常快速 : Redis是非常快的,每秒可以执行大约110000设置操作,81000个/每秒的读取操作。

    • 支持丰富的数据类型 : Redis支持最大多数开发人员已经知道如列表,集合,可排序集合,哈希等数据类型。

      这使得在应用中很容易解决的各种问题,因为我们知道哪些问题处理使用哪种数据类型更好解决。
    • 操作都是原子的 : 所有 Redis 的操作都是原子,从而确保当两个客户同时访问 Redis 服务器得到的是更新后的值(最新值)。

    • MultiUtility工具:Redis是一个多功能实用工具,可以在很多如:缓存,消息传递队列中使用(Redis原生支持发布/订阅),在应用程序中,如:Web应用程序会话,网站页面点击数等任何短暂的数据

    redis安装

    centos下安装:

     wget http://download.redis.io/releases/redis-4.0.9.tar.gz
     tar xvf redis-4.0.9.tar.gz
     cd redis-4.0.9/
     make && make install
    

    启动:/mnt/redis-4.0.9/src/redis-server /mnt/redis-4.0.9/redis.conf &

    或修改配置文件redis.conf的选项daemonize为yes开启守护进程,这redis个版默认是no:/mnt/redis-4.0.9/src/redis-server /mnt/redis-4.0.9/redis.conf

    Redis API使用

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

    redis-py 的API的使用可以分类为:

    • 连接方式
    • 连接池
    • 操作
      • String 操作
      • Hash 操作
      • List 操作
      • Set 操作
      • Sort Set 操作
    • 管道
    • 发布订阅

    redis连接方式

    1、操作模式

    redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。

    python远程操作redis:

    import redis
    r = redis.Redis(host='192.168.10.128',port=6379)
    r.set('name','xiaogang')
    print(r.get('name'))
    

    2、连接池

    redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。

    Redis操作字符串(String)

    Redis 字符串数据类型的相关命令用于管理 redis 字符串值。

    查看语法格式命令:help

    127.0.0.1:6379> help set
    
      SET key value [EX seconds] [PX milliseconds] [NX|XX]
      summary: Set the string value of a key
      since: 1.0.0
      group: string
    

    set(key, value, ex=None, px=None, nx=False, xx=False)

    在Redis中设置值,默认,不存在则创建,存在则修改
    参数:
         ex,过期时间(秒)
         px,过期时间(毫秒)
         nx,如果设置为True,则只有key不存在时,当前set操作才执行
         xx,如果设置为True,则只有key存在时,岗前set操作才执行
    

     get key

    获取值
    

    setnx key value

    设置值,只有name不存在时,执行设置操作(添加)
    

     setex key value time

    # 设置值
    # 参数:
        # time,过期时间(数字秒 或 timedelta对象)
    

     psetex key time_ms value

    # 设置值
    # 参数:
        # time_ms,过期时间(数字毫秒 或 timedelta对象)
    

     mset *args, **kwargs

    批量设置值
    如:
    127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 k4 v4
    OK
    

     mget *args **kwargs

    #批量获取值
    127.0.0.1:6379> mget k1 k2 k3 k4
    1) "v1"
    2) "v2"
    3) "v3"
    4) "v4"
    

     getset key value

    #设置新值并返回原来的值
    127.0.0.1:6379> getset k1 v111111
    "v1"
    

     getrange key start end

    # 获取子序列(根据字节获取,非字符)
    # 参数:
        # start,起始位置(字节)
        # end,结束位置(字节)
    #字母和数字占一个字节,一个汉字三个字节
    # 如:
    127.0.0.1:6379> getrange k1 0 6
    "v111111"
    

     setrange key offset value

    # 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
    # 参数:
        # offset,字符串的索引,字节(一个汉字三个字节)
        # value,要设置的值
    
    127.0.0.1:6379> set k1 "hello world!"
    OK
    127.0.0.1:6379> setrange k1 6 "redis"
    (integer) 12
    127.0.0.1:6379> get k1
    "hello redis!"
    

     setbit key offset value

    # 对name对应值的二进制表示的位进行操作
     
    # 参数:
        # name,redis的name
        # offset,位的索引(将值变换成二进制后再进行索引)
        # value,值只能是 1 或 0
     
    # 注:如果在Redis中有一个对应: n1 = "foo",
            通过ord(),bin()得出字符串foo的二进制表示为:01100110 01101111 01101111
        所以,如果执行 setbit('n1', 7, 1),则就会将第7位设置为1,
            那么最终二进制则变成 01100111 01101111 01101111,即:"goo"
    127.0.0.1:6379> set k1 foo
    OK
    127.0.0.1:6379> 
    127.0.0.1:6379> setbit k1 7 1
    (integer) 0
    127.0.0.1:6379> get k1
    "goo"
    

     strlen key

    #返回name对应值的字节长度(一个汉字3个字节)
    127.0.0.1:6379> get k1
    "goo"
    127.0.0.1:6379> strlen k1
    (integer) 3
    

     getbit key offset

    # 获取name对应的值的二进制表示中的某位的值 (0或1)
    

     bitcount key start end

    # 获取name对应的值的二进制表示中 1 的个数
    # 参数:
        # key,Redis的name
        # start,位起始位置
        # end,位结束位置
    

     incr key

    #key的数字值自增1,当key不存在时,值为1
    127.0.0.1:6379> set k1 10
    OK
    127.0.0.1:6379> incr k1
    (integer) 11
    127.0.0.1:6379> get k1
    "11"

    127.0.0.1:6379> incr kk
    (integer) 1
    127.0.0.1:6379> get kk
    "1"

     incrby key incement

    将 key 中储存的数字加上指定的增量值。
    
    如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
    
    如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
    
    本操作的值限制在 64 位(bit)有符号数字表示之内。 
    127.0.0.1:6379> set k1 10
    OK
    127.0.0.1:6379> incrby k1 29
    (integer) 39
    127.0.0.1:6379> get k1
    "39"
    127.0.0.1:6379> incrby kt 29
    (integer) 29
    

     incrbyfloat key incement

    #将key存储的值加上指定浮点数增量值
    127.0.0.1:6379> incrbyfloat k1 29.5
    "68.5"
    

     decr key

    # 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。
     
    # 参数:
        # name,Redis的name
        # amount,自减数(整数)
    127.0.0.1:6379> set k1 10
    OK
    127.0.0.1:6379> decr k1
    (integer) 9
    

     append key

    # 在redis name对应的值后面追加内容
    # 参数:
        key, redis的name
        value, 要追加的字符串
    
    127.0.0.1:6379> append k1 "hello"
    (integer) 6
    127.0.0.1:6379> get k1
    "9hello"
    

     Redis 哈希(Hash)

    hash表现形式上有些像pyhton中的dict,可以存储一组关联性较强的数据 , redis中Hash在内存中的存储格式如下图:

    HSET key field value HMGET

    key field [field ...]

    Hset 命令用于为哈希表中的字段赋值 。
    如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
    如果字段已经存在于哈希表中,旧值将被覆盖。
    
    Hmget 命令用于返回哈希表中,一个或多个给定字段的值。
    如果指定的字段不存在于哈希表,那么返回一个 nil 值。 
    127.0.0.1:6379> hset info name lisi
    (integer) 1
    127.0.0.1:6379> hset info age 24
    (integer) 1
    127.0.0.1:6379> hset info sex male
    (integer) 1
    
    127.0.0.1:6379> hmget info name age sex
    1) "lisi"
    2) "24"
    3) "male"
    

     hexists key field

     HDEL key field [field ...]

     Hexists 命令用于查看哈希表的指定字段是否存在,如果哈希表含有给定字段,返回 1 。 如果哈希表不含有给定字段,或 key 不存在,返回 0 。
    
    Redis Hdel 命令用于删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略。 
    127.0.0.1:6379> hexists info name
    (integer) 1
    127.0.0.1:6379> hdel info name
    (integer) 1
    127.0.0.1:6379> hget info name
    (nil)
    

     hkeys key

    hgetall key

    hkeys key 获取所有哈希表的key值
    hgetall key 返回哈希表中,所有的字段和值。在返回值里,紧跟每个字段名(field name)之后是字段的值(value),所以返回值的长度是哈希表大小的两倍。 
    127.0.0.1:6379> hkeys info
    1) "age"
    2) "sex"
    127.0.0.1:6379> hgetall info
    1) "age"
    2) "24"
    3) "sex"
    4) "male"
    

     hincrby key field increment

    HINCRBYFLOAT key field increment

    Hincrby 命令用于为哈希表中的字段值加上指定增量值。
    增量也可以为负数,相当于对指定字段进行减法操作。
    如果哈希表的 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
    如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 。
    对一个储存字符串值的字段执行 HINCRBY 命令将造成一个错误。
    本操作的值被限制在 64 位(bit)有符号数字表示之内。 
    HINCRBYFLOAT命令用于为哈希表中的字段值加上指定浮点数增量值。 如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 。 127.0.0.1:6379> hincrby info age 1 (integer) 25 127.0.0.1:6379> hincrbyfloat info age 2.5 "27.5"

     hlen key

    hvalue key

    hlen key 获取key键值对个数
    hvalue key 获取哈希表中所有值
    127.0.0.1:6379> hlen info
    (integer) 3
    127.0.0.1:6379> hvals info
    1) "27.5"
    2) "male"
    3) "1"
    127.0.0.1:
    

     HMSET key field value [field value ...] 同时将多个 field-value (域-值)对设置到哈希表 key 中。

     HSCAN key cursor [MATCH pattern] [COUNT count]   迭代哈希表中的键值对。

    hmset 命令用于同时将多个 field-value (字段-值)对设置到哈希表中。
    此命令会覆盖哈希表中已存在的字段。
    如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作
    hscan 迭代哈希表中的键值对。
    127.0.0.1:6379> hmset class python alex linux laonanhai
    OK
    127.0.0.1:6379> hscan class 3
    1) "0"
    2) 1) "python"
       2) "alex"
       3) "linux"
       4) "laonanhai"
    

     redis列表(list)

    List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:

    Lpush命令将一个或多个值插入到列表头部。 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。 当 key 存在但不是列表类型时,返回一个错误。

    LPUSH key value [value ...] 将一个或多个值插入到列表头部。

    LRANGE key start stop    获取列表指定范围内的元素。

    127.0.0.1:6379> lpush names shaowei xiaogang lisi zhangsan
    (integer) 9
    127.0.0.1:6379> lrange names 0 -1
    1) "zhangsan"
    2) "lisi"
    3) "xiaogang"
    4) "shaowei"
    

       BLPOP key [key ...] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
       BRPOP key [key ...] 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

    127.0.0.1:6379> lrange number 0 -1
    1) "five"
    2) "four"
    3) "three"
    4) "two"
    5) "one"
    127.0.0.1:6379> blpop number 1
    1) "number"
    2) "five"
    127.0.0.1:6379> brpop number 1
    1) "number"
    2) "one"
    127.0.0.1:6379> lrange number 0 -1
    1) "four"
    2) "three"
    3) "two"
    

     LINDEX key index  通过索引获取列表中的元素。

    LLEN key  获取列表长度。

    127.0.0.1:6379> lindex number 0
    "four"
    127.0.0.1:6379> lindex number 1
    "three"
    127.0.0.1:6379> llen number
    (integer) 3
    

     LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素。

    LPUSHX key value  将一个值插入到已存在的列表头部。

    127.0.0.1:6379> linsert names before lisi alex
    (integer) 5
    127.0.0.1:6379> lrange names 0 -1
    1) "alex"
    2) "lisi"
    3) "xiaogang"
    4) "shaowei"
    5) "zhangsan"
    
    127.0.0.1:6379> lpushx names xiaoming
    (integer) 6
    127.0.0.1:6379> lrange names 0 -1
    1) "xiaoming"
    2) "alex"
    3) "lisi"
    4) "xiaogang"
    5) "shaowei"
    6) "zhangsan"
    

     LREM key count value  移除列表元素。

    LSET key index value  通过索引设置列表元素的值。

    127.0.0.1:6379> lrem names 0 shaowei
    (integer) 1
    127.0.0.1:6379> lrange names 0 -1
    1) "xiaoming"
    2) "alex"
    3) "lisi"
    4) "xiaogang"
    5) "zhangsan"
    127.0.0.1:6379> lset names 0 xiaohong
    OK
    127.0.0.1:6379> lrange names 0 -1
    1) "xiaohong"
    2) "xiaohong"
    3) "lisi"
    4) "xiaogang"
    5) "zhangsan"
    

     LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

    Ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

    下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

    RPOP key 移除并获取列表最后一个元素。

    127.0.0.1:6379> lpush numbers one two three four five
    (integer) 5
    127.0.0.1:6379> ltrim numbers 1 -2
    OK
    127.0.0.1:6379> lrange numbers 0 -1
    1) "four"
    2) "three"
    3) "two"
    127.0.0.1:6379> rpop numbers
    "two"
    127.0.0.1:6379> lrange numbers 0 -1
    1) "four"
    2) "three"

     RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。

    RPUSHX key value  为已存在的列表添加值。

    127.0.0.1:6379> lrange names 0 -1
    1) "xiaohong"
    2) "xiaohong"
    3) "lisi"
    4) "xiaogang"
    127.0.0.1:6379> lrange numbers 0 -1
    1) "four"
    2) "three"
    127.0.0.1:6379> rpoplpush names numbers
    "xiaogang"
    127.0.0.1:6379> lrange numbers 0 -1
    1) "xiaogang"
    2) "four"
    3) "three"
    
    127.0.0.1:6379> rpushx numbers one
    (integer) 4
    127.0.0.1:6379> lrange numbers 0 -1
    1) "xiaogang"
    2) "four"
    3) "three"
    4) "one"
    

     redis 集合(set)

    Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

    Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

    集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

    SADD key member [member ...]  向集合添加一个或多个值元素。

    scard key 获取集合元素数。

    127.0.0.1:6379> sadd name1 xiaoming lisi zhangsan
    (integer) 3
    127.0.0.1:6379> scard name1
    (integer) 3
    

     SDIFF key [key ...] 返回给定所有集合的差集。

    SDIFFSTORE destination key [key ...] 返回给定所有集合的差集并存储在 destination 中。

    127.0.0.1:6379> sdiff name1 name2
    1) "xiaoming"
    2) "zhangsan"
    127.0.0.1:6379> sdiffstore name3 name1 name2
    (integer) 2
    

    SINTER key [key ...] 返回给定所有集合的交集。

    SINTERSTORE destination key [key ...] 返回给定所有集合的交集并存储在 destination 中。

    127.0.0.1:6379> sadd num1 one two three four
    (integer) 4
    127.0.0.1:6379> sadd num2 one 2 two 32
    (integer) 4
    127.0.0.1:6379> sinter num1 num2
    1) "one"
    2) "two"
    127.0.0.1:6379> sinterstore num3 num2 num1
    (integer) 2

    SISMEMBER key member 判断 member 元素是否是集合 key 的成员

    SMEMBERS key 返回集合中的所有成员

    127.0.0.1:6379> sismember num1 'one'
    (integer) 1
    127.0.0.1:6379> smembers num1
    1) "one"
    2) "four"
    3) "three"
    4) "two"
    

    SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合

    SPOP key  移除并返回集合中的一个随机元素

    127.0.0.1:6379> smembers num2
    1) "2"
    2) "one"
    3) "32"
    4) "two"
    127.0.0.1:6379> smove num1 num2 'four'
    (integer) 1
    127.0.0.1:6379> smembers num2
    1) "2"
    2) "one"
    3) "32"
    4) "two"
    5) "four"

    127.0.0.1:6379> smembers num2
    1) "2"
    2) "one"
    3) "32"
    4) "two"
    5) "four"
    127.0.0.1:6379> spop num2
    "32"

    SREM key member [member ...] 移除集合中一个或多个元素

    SRANDMEMBER key [count]  返回集合中一个或多个随机数

    127.0.0.1:6379> srem num2 32 noe 
    (integer) 0
    127.0.0.1:6379> srandmember num2 2
    1) "one"
    2) "2"

    SUNION key [key ...] 返回所有给定集合的并集

    SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中

    127.0.0.1:6379> smembers num1
    1) "one"
    2) "three"
    3) "two"
    127.0.0.1:6379> smembers num2
    1) "one"
    2) "four"
    127.0.0.1:6379> sunion num1 num2
    1) "one"
    2) "four"
    3) "three"
    4) "two"
    127.0.0.1:6379> sunionstore num4 num2 num1
    (integer) 4
    127.0.0.1:6379> smembers num4
    1) "one"
    2) "four"
    3) "three"
    4) "two"

    Redis 有序集合(sorted set)

    Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

    不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

    有序集合的成员是唯一的,但分数(score)却可以重复。

    ZADD key score1 member1 [score2 member2]  向有序集合添加一个或多个元素,或者更新已存在成员的分数

    ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合成指定区间内的成员

    127.0.0.1:6379> zadd z1 8 jack 9 lisi 3 jerry 2 bitch
    (integer) 4
    127.0.0.1:6379> zrange z1 0 -1 withscores
    1) "bitch"
    2) "2"
    3) "jerry"
    4) "3"
    5) "jack"
    6) "8"
    7) "lisi"
    8) "9"
    

     ZCARD key 获取有序集合的成员数

    ZCOUNT key min max 计算在有序集合中指定区间分数的成员数

    127.0.0.1:6379> zcard z1
    (integer) 4
    127.0.0.1:6379> zcount z1 2 8
    (integer) 3
    

     ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment,可以通过传递一个负数值 increment ,让分数减去相应的值,比如 ZINCRBY key -5 member ,就是让 member 的 score 值减去 5 。

    当 key 不存在,或分数不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。当 key 不是有序集类型时,返回一个错误。分数值可以是整数值或双精度浮点数。

    ZINTERSTORE destination numkeys key [key ...]  计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中

    127.0.0.1:6379> zincrby z1 19 bitch
    "21"
    127.0.0.1:6379> zadd z2 89 'tom' 77 'li lianjei' 3 'jerry'
    (integer) 3
    127.0.0.1:6379> zinterstore z3 2 z1 z2
    (integer) 1
    127.0.0.1:6379> zrange z3 0 -1 withscores
    1) "jerry"
    2) "6"
    

     ZLEXCOUNT key min max 在有序集合中计算指定字典区间内成员数量

    ZRANGEBYLEX key min max [LIMIT offset count] 通过字典区间返回有序集合的成员

    127.0.0.1:6379> zlexcount z2 [jerry [tom
    (integer) 3
    127.0.0.1:6379> zrange z2 0 -1 withscores
    1) "jerry"
    2) "3"
    3) "li lianjei"
    4) "77"
    5) "tom"
    6) "89"
    127.0.0.1:6379> zrangebylex z2 [jerry [tom
    1) "jerry"
    2) "li lianjei"
    3) "tom"
    

     ZRANK key member 返回有序集合中指定成员的索引

    ZREM key member [member ...] 移除有序集合中的一个或多个成员

    127.0.0.1:6379> zrange z2 0 -1 withscores
    1) "jerry"
    2) "3"
    3) "li lianjei"
    4) "77"
    5) "tom"
    6) "89"
    127.0.0.1:6379> zrank z2 'li lianjei'
    (integer) 1
    127.0.0.1:6379> zrank z2 'jerry'
    (integer) 0
    127.0.0.1:6379> zrank z2 'tom'
    (integer) 2
    127.0.0.1:6379> zrem z2 'jerry'
    (integer) 1
    

     ZSCORE key member 返回有序集中,成员的分数值

    127.0.0.1:6379> zscore z2 tom
    "89"
    

    redis另外命令操作

     DEL key [key ...]  删除一个key

    EXISTS key [key ...]  检redis的key是否存在
    EXPIRE key seconds 为某个redis的key设置存在时间

    RENAME key newkey 为key重命名

    randomkey 随机获取一个key

    type key 获取key的对应值类型

    发布订阅

    发布者:服务器

    订阅者:Dashboad和数据处理

    Demo如下:

    import redis
    class RedisHelper:
    
        def __init__(self):
            self.__conn = redis.Redis(host='192.168.10.142')
            self.chan_sub = 'fm104.5'
            self.chan_pub = 'fm104.5'
    
        def public(self, msg):
            self.__conn.publish(self.chan_pub, msg)
            return True
    
        def subscribe(self):
            pub = self.__conn.pubsub() #打开收音机
            pub.subscribe(self.chan_sub) #调频道
            pub.parse_response() #准备接受
            return pub
    

     订阅者

    from redishelper import RedisHelper
    
    obj = RedisHelper()
    redis_sub = obj.subscribe()
    
    while True:
        msg = redis_sub.parse_response()
        print(msg)
    

     发布者

     
    from monitor.RedisHelper import RedisHelper
    obj = RedisHelper()
    obj.public('hello')
    
  • 相关阅读:
    【译文】四十二种谬误(一)
    .NET笔记(二)
    设计模式C#实现(十六)——中介者模式
    设计模式C#实现(十五)——命令模式
    《程序员修炼之道》笔记
    《学会提问》读书笔记
    设计模式C#实现(十四)——责任链模式
    设计模式C#实现(十三)——享元模式(蝇量模式)
    学以致用——读《学会提问》
    访问苹果开发者网站太慢
  • 原文地址:https://www.cnblogs.com/wenwei-blog/p/8995706.html
Copyright © 2020-2023  润新知