使用ioredis在Node.js中操作Redis数据结构的详细指南

使用ioredis在Node.js中操作Redis数据结构的详细指南

一. 使用ioredis操作Redis数据结构的详细知识点讲解

在Node.js中,ioredis是一个强大且易于使用的Redis客户端库,它提供了对Redis数据库的直接操作。本文将通过一系列代码示例,详细解释如何使用ioredis进行基本的Redis操作,适合初学者理解和学习。

1. 连接管理

首先,我们需要连接到Redis服务器。ioredis默认连接到本地的localhost:6379

javascript 复制代码
const Redis = require('ioredis');
const redis = new Redis(); // 默认连接到 localhost:6379

// 连接到Redis
redis.on('connect', () => {
  console.log('Connected to Redis');
});

// 断开与Redis的连接
redis.disconnect().then(() => {
  console.log('Disconnected from Redis');
});

2. 字符串操作

使用ioredis操作Redis中的字符串类型的详细知识点如下:

1) 基本字符串操作

设置键值对

使用set方法设置键值对:

javascript 复制代码
redis.set('key', 'value');

获取键值对

使用get方法获取键值对的值:

javascript 复制代码
redis.get('key', (err, result) => {
  if (err) throw err;
  console.log(result); // 输出: value
});

设置键值对并设置过期时间

使用setex方法设置键值对,并指定过期时间(秒):

javascript 复制代码
redis.setex('key', 10, 'value');
2) 字符串的过期和删除

删除键

使用del方法删除键:

javascript 复制代码
redis.del('key');
3). 字符串的过期时间

设置键的过期时间

使用expire方法设置键的过期时间(秒):

javascript 复制代码
redis.expire('key', 60);

查看键的剩余过期时间

使用ttl方法查看键的剩余过期时间(秒):

javascript 复制代码
redis.ttl('key');
5). 字符串的位操作

ioredis也支持对字符串执行位操作,例如:

javascript 复制代码
redis.getbit('key', 0); // 获取位
redis.setbit('key', 0, 1); // 设置位
6). 管道和事务

ioredis支持管道和事务,可以批量执行命令,提高性能:

javascript 复制代码
redis.pipeline()
  .set('foo', 'bar')
  .get('foo')
  .exec((err, results) => {
    // results === [ [null, 'OK'], [null, 'bar'] ]
  });

以上是使用ioredis操作Redis中字符串类型的一些基本和高级操作。通过这些操作,你可以有效地管理Redis中的字符串数据。

3. 哈希操作

使用ioredis操作Redis中的哈希(Hash)数据结构时,你可以执行多种操作来管理这些复杂的数据结构。以下是一些详细的知识点和操作方法:

1). 设置哈希字段的值
  • hset(key, field, value):为哈希表中的指定字段赋值。

    javascript 复制代码
    redis.hset('myhash', 'field1', 'value1');
2). 获取哈希字段的值
  • hget(key, field) :获取哈希表中指定字段的值。

    javascript 复制代码
    redis.hget('myhash', 'field1').then((value) => {
      console.log('Value:', value); // 输出 'value1'
    });
3). 批量设置哈希字段的值
  • hmset(key, field1, value1, field2, value2, ...) :同时设置哈希表中的多个字段的值。

    javascript 复制代码
    redis.hmset('myhash', 'field2', 'value2', 'field3', 'value3');
4). 批量获取哈希字段的值
  • hmget(key, field1, field2, ...) :同时获取哈希表中多个字段的值。

    javascript 复制代码
    redis.hmget('myhash', 'field1', 'field2', 'field3').then((values) => {
      console.log(values); // 输出 ['value1', 'value2', 'value3']
    });
5). 获取哈希表中的字段数量
  • hlen(key) :获取哈希表中字段的数量。

    javascript 复制代码
    redis.hlen('myhash').then((result) => {
      console.log(result); // 输出字段数量
    });
6). 删除哈希字段
  • hdel(key, field) :删除哈希表中的一个或多个字段。

    javascript 复制代码
    redis.hdel('myhash', 'field1').then((result) => {
      console.log(result); // 输出被删除的字段数量
    });
7). 获取哈希表中的所有字段名
  • hkeys(key) :获取哈希表中的所有字段名。

    javascript 复制代码
    redis.hkeys('myhash').then((fields) => {
      console.log(fields); // 输出所有字段名
    });
8). 获取哈希表中的所有值
  • hvals(key) :获取哈希表中的所有值。

    javascript 复制代码
    redis.hvals('myhash').then((values) => {
      console.log(values); // 输出所有值
    });
9). 获取哈希表中的所有字段和值
  • hgetall(key) :获取哈希表中的所有字段和值。

    javascript 复制代码
    redis.hgetall('myhash').then((hash) => {
      console.log(hash); // 输出所有字段和值
    });
10). 检查哈希字段是否存在
  • hexists(key, field) :检查哈希表中指定的字段是否存在。

    javascript 复制代码
    redis.hexists('myhash', 'field1').then((exists) => {
      console.log(exists); // 如果字段存在,输出 1,否则输出 0
    });
11). 为哈希表字段的整数值增加增量
  • hincrby(key, field, increment) :为哈希表中指定字段的整数值增加指定的增量。

    javascript 复制代码
    redis.hincrby('myhash', 'field1', 1); // 将 'field1' 的值增加 1
12). 为哈希表字段的浮点数值增加增量
  • hincrbyfloat(key, field, increment) :为哈希表中指定字段的浮点数值增加指定的增量。

    javascript 复制代码
    redis.hincrbyfloat('myhash', 'field1', 1.5); // 将 'field1' 的值增加 1.5
13). 获取哈希表中字符串字段的长度
  • hstrlen(key, field) :获取哈希表中字符串字段的长度。

    javascript 复制代码
    redis.hstrlen('myhash', 'field1').then((length) => {
      console.log(length); // 输出字段值的长度
    });

这些操作提供了对Redis哈希数据结构的全面管理能力,使得在Node.js应用中处理复杂数据变得更加简单和高效。

4. 列表操作

使用ioredis操作Redis中的列表(List)数据结构时,你可以执行多种操作来管理这些数据结构。以下是一些详细的知识点和操作方法:

1). 插入元素
  • lpush(key, ...values):在列表的左侧(头部)插入一个或多个元素。

    javascript 复制代码
    await redis.lpush('my_list', 'element1', 'element2');

    参考:[腾讯云开发者社区-使用aioredis操作列表对象:插入单个元素]

  • rpush(key, ...values):在列表的右侧(尾部)插入一个或多个元素。

    javascript 复制代码
    await redis.rpush('my_list', 'element1', 'element2');
2). 弹出元素
  • lpop(key):从列表的左侧(头部)弹出一个元素。

    javascript 复制代码
    const element = await redis.lpop('my_list');
  • rpop(key):从列表的右侧(尾部)弹出一个元素。

    javascript 复制代码
    const element = await redis.rpop('my_list');
3). 获取列表长度
  • llen(key) :获取列表的长度。

    javascript 复制代码
    const length = await redis.llen('my_list');
4). 获取列表元素
  • lrange(key, start, stop) :获取列表中指定范围内的元素。

    javascript 复制代码
    const elements = await redis.lrange('my_list', 0, -1); // 获取所有元素
5). 删除元素
  • lrem(key, count, value) :删除列表中与给定值相等的元素。

    javascript 复制代码
    // 从列表中删除第一个匹配的元素
    await redis.lrem('my_list', 1, 'element1');
6). 根据索引设置元素值
  • lset(key, index, value) :设置列表指定索引处的元素值。

    javascript 复制代码
    await redis.lset('my_list', 0, 'newElement');
7). 截取列表
  • ltrim(key, start, stop) :对一个列表进行修剪,只保留指定范围内的元素。

    javascript 复制代码
    await redis.ltrim('my_list', 0, -1); // 保留所有元素
8). 管道和事务

ioredis支持管道和事务,可以批量执行命令,提高性能:

javascript 复制代码
const pipeline = redis.pipeline();
pipeline.lpush('my_list', 'newElement');
pipeline.lrange('my_list', 0, -1);
pipeline.exec((err, results) => {
  // 处理结果
});

以上是使用ioredis操作Redis中列表数据结构的一些基本和高级操作。通过这些操作,你可以有效地管理Redis中的列表数据。

5. 集合操作

使用ioredis操作Redis中的集合(Set)数据结构时,你可以执行多种操作来管理这些数据结构。以下是一些详细的知识点和操作方法:

1). 向集合添加元素
  • sadd(key, member1, member2, ...) :向集合中添加一个或多个元素。如果元素已存在,则不会重复添加。

    javascript 复制代码
    await redis.sadd('myset', 'one', 'two', 'three');
2). 从集合中移除元素
  • srem(key, member1, member2, ...) :从集合中移除一个或多个元素。

    javascript 复制代码
    await redis.srem('myset', 'two');
3). 检查元素是否在集合中
  • sismember(key, member) :检查元素是否是集合的成员。

    javascript 复制代码
    const isMember = await redis.sismember('myset', 'one');
    console.log('Is member:', isMember); // true 或 false
4). 获取集合中的所有元素
  • smembers(key) :获取集合中的所有元素。

    javascript 复制代码
    const members = await redis.smembers('myset');
    console.log('Members:', members);
5). 获取集合的大小
  • scard(key) :获取集合的大小,即元素的数量。

    javascript 复制代码
    const size = await redis.scard('myset');
    console.log('Set size:', size);
6). 集合运算
  • sinter(key1, key2, ...):返回两个或多个集合的交集。

    javascript 复制代码
    const intersection = await redis.sinter('myset', 'anotherSet');
    console.log('Intersection:', intersection);
  • sunion(key1, key2, ...):返回两个或多个集合的并集。

    javascript 复制代码
    const union = await redis.sunion('myset', 'anotherSet');
    console.log('Union:', union);
  • sdiff(key1, key2, ...):返回两个集合的差集,即存在于第一个集合中但不在第二个集合中的元素。

    javascript 复制代码
    const difference = await redis.sdiff('myset', 'anotherSet');
    console.log('Difference:', difference);
  • smove(source, destination, member):将元素从一个集合移动到另一个集合。

    javascript 复制代码
    await redis.smove('myset', 'anotherSet', 'one');

以上是使用ioredis操作Redis中集合数据结构的一些基本操作。通过这些操作,你可以有效地管理Redis中的集合数据。

6. 有序集合操作

使用ioredis操作Redis中的有序集合(Sorted Set)涉及到一系列的命令,这些命令可以帮助你管理有序集合中的数据。以下是一些详细的知识点和操作方法:

1). 向有序集合添加元素
  • zadd(key, score, member) :向有序集合中添加一个元素,或者更新已存在元素的分数。

    javascript 复制代码
    await redis.zadd('myzset', { 'one': 1, 'two': 2, 'three': 3 });
2). 获取有序集合中的元素
  • zrange(key, start, stop, [withscores]) :获取有序集合中指定范围内的元素,可以包含分数。

    javascript 复制代码
    const elements = await redis.zrange('myzset', 0, -1, 'WITHSCORES');
    console.log(elements); // 输出 ['one', '1', 'two', '2', 'three', '3']
3). 获取有序集合中元素的逆序排列
  • zrevrange(key, start, stop, [withscores]) :获取有序集合中指定范围内的元素,按照分数从大到小排序。

    javascript 复制代码
    const elements = await redis.zrevrange('myzset', 0, -1, 'WITHSCORES');
    console.log(elements); // 输出 ['three', '3', 'two', '2', 'one', '1']
4). 获取有序集合中元素的排名
  • zrank(key, member):获取有序集合中指定元素的排名,按分数从小到大排列。

    javascript 复制代码
    const rank = await redis.zrank('myzset', 'two');
    console.log(rank); // 输出 '1'
  • zrevrank(key, member):获取有序集合中指定元素的排名,按分数从大到小排列。

    javascript 复制代码
    const rank = await redis.zrevrank('myzset', 'two');
    console.log(rank); // 输出 '2'
5). 删除有序集合中的元素
  • zrem(key, member1, [member2, ...]) :从有序集合中移除一个或多个元素。

    javascript 复制代码
    await redis.zrem('myzset', 'one');
6). 获取有序集合的大小
  • zcard(key) :获取有序集合中的成员数。

    javascript 复制代码
    const size = await redis.zcard('myzset');
    console.log(size); // 输出集合的大小
7). 计算有序集合中指定分数区间的成员数
  • zcount(key, min, max) :计算在有序集合中指定区间分数的成员数。

    javascript 复制代码
    const count = await redis.zcount('myzset', '1', '2');
    console.log(count); // 输出分数在1到2之间的成员数
8). 增加有序集合中元素的分数
  • zincrby(key, increment, member) :有序集合中对指定成员的分数加上增量。

    javascript 复制代码
    await redis.zincrby('myzset', 1, 'two'); // 将 'two' 的分数增加 1
9). 获取有序集合中元素的分数
  • zscore(key, member) :获取有序集合中指定成员的分数。

    javascript 复制代码
    const score = await redis.zscore('myzset', 'two');
    console.log(score); // 输出 'two' 的分数
10). 根据分数区间获取有序集合中的元素
  • zrangebyscore(key, min, max, [withscores], [limit]) :返回有序集合中指定分数区间的成员。

    javascript 复制代码
    const elements = await redis.zrangebyscore('myzset', '1', '3', 'WITHSCORES');
    console.log(elements); // 输出分数在1到3之间的成员及其分数
11). 根据字典区间获取有序集合中的元素
  • zrangebylex(key, min, max) :通过字典区间返回有序集合的成员。

    javascript 复制代码
    const elements = await redis.zrangebylex('myzset', '-', '+');
    console.log(elements); // 输出字典序在 '-' 到 '+' 之间的成员
12). 移除有序集合中指定分数区间的成员
  • zremrangebyscore(key, min, max) :移除有序集合中给定的分数区间的所有成员。

    javascript 复制代码
    await redis.zremrangebyscore('myzset', '1', '2');

以上是使用ioredis操作Redis中有序集合的一些基本和高级操作。通过这些操作,你可以有效地管理Redis中的有序集合数据。

7. 发布/订阅

发布/订阅模式允许我们发布消息到频道,并订阅频道以接收消息。

javascript 复制代码
// 向频道发布消息
redis.publish('myChannel', 'Hello!').then((numSubscribers) => {
  console.log('Number of subscribers:', numSubscribers);
});

// 订阅频道
const subscriber = new Redis();
subscriber.on('message', (channel, message) => {
  console.log(`Received data: ${message} from ${channel}`);
});
subscriber.subscribe('myChannel');

8. 键管理

键管理操作允许我们检查键是否存在、设置过期时间、获取剩余过期时间以及删除键。

javascript 复制代码
// 检查键是否存在
redis.exists('myKey').then((exists) => {
  console.log('Key exists:', exists);
});

// 设置键的过期时间
redis.expire('myKey', 10).then((didSetExpire) => {
  console.log('Key has an expiration time set:', didSetExpire);
});

// 获取键的剩余过期时间
redis.ttl('myKey').then((ttl) => {
  console.log('Time to live for key:', ttl);
});

// 删除键
redis.del('myKey').then((delCount) => {
  console.log('Deleted keys count:', delCount);
});

9. 事务处理

事务处理允许我们执行一系列操作,这些操作要么全部成功,要么全部失败。

javascript 复制代码
// 开始一个事务
redis.multi()
  .set('foo', 'bar')
  .get('foo')
  .exec().then((results) => {
    console.log('Transaction results:', results);
  });

10. 管道处理

管道处理允许我们批量执行多个命令,减少网络延迟。

javascript 复制代码
// 创建一个管道
redis.pipeline()
  .set('foo', 'bar')
  .get('foo')
  .exec().then((results) => {
    console.log('Pipeline results:', results);
  });

11. 脚本执行

脚本执行允许我们执行Lua脚本,这可以提高性能,尤其是在需要执行多个操作时。

javascript 复制代码
// 执行Lua脚本
redis.eval('return redis.call("set", KEYS[1], ARGV[1])', 1, 'myLuaKey', 'luaValue').then(() => {
  console.log('Lua script executed');
});

// 执行已加载的Lua脚本
const script = `
  local value = redis.call('get', KEYS[1])
  if value then
    return value
  else
    return 'nil'
  end
`;
redis.evalsha(script, 1, 'myLuaKey').then((result) => {
  console.log('Lua script result:', result);
});

以上是ioredis库中一些基本操作的入门指南。每个示例都包含了一个简单的操作和相应的代码,以帮助理解每个方法的作用。在实际开发中,你可能需要根据具体的业务逻辑来组合和扩展这些操作。希望这篇文章能帮助你更好地理解和使用ioredis

二. Promise风格与回调函数风格在Redis String类型CRUD操作中的对比

帮助理解:

ioredis库中,当你使用回调函数风格的API时,这些方法实际上返回的是 Promise对象 。这意味着即使你在方法调用的末尾提供了一个 回调函数ioredis内部仍然会返回一个 Promise对象,以便支持现代的异步处理方式。

当然,下面我将对比展示使用Promise风格的异步处理方式和使用回调函数风格的异步处理方式,以Redis的String类型为例

1. Promise风格(现代异步处理方式)

Promise风格是ES6引入的一种异步处理方式,它允许我们使用.then().catch()方法来处理异步操作的结果和错误。

  • 创建(Create)

    javascript 复制代码
    redis.set('key', 'value')
      .then(result => {
        console.log('Set result:', result); // 处理结果
      })
      .catch(err => {
        console.error('Error setting key:', err); // 处理错误
      });
  • 读取(Read)

    javascript 复制代码
    redis.get('key')
      .then(result => {
        console.log('Get result:', result); // 处理结果
      })
      .catch(err => {
        console.error('Error getting key:', err); // 处理错误
      });
  • 更新(Update)

    javascript 复制代码
    redis.set('key', 'new_value')
      .then(result => {
        console.log('Update result:', result); // 处理结果
      })
      .catch(err => {
        console.error('Error updating key:', err); // 处理错误
      });
  • 删除(Delete)

    javascript 复制代码
    redis.del('key')
      .then(result => {
        console.log('Delete result:', result); // 处理结果
      })
      .catch(err => {
        console.error('Error deleting key:', err); // 处理错误
      });

2. 回调函数风格(Node.js传统异步处理方式)

回调函数风格是在Promise之前Node.js中处理异步操作的主要方式。每个异步函数都接受一个回调函数作为最后一个参数,该回调函数在操作完成时被调用。

  • 创建(Create)

    javascript 复制代码
    redis.set('key', 'value', function(err, result) {
      if (err) {
        console.error('Error setting key:', err); // 处理错误
      } else {
        console.log('Set result:', result); // 处理结果
      }
    });
  • 读取(Read)

    javascript 复制代码
    redis.get('key', function(err, result) {
      if (err) {
        console.error('Error getting key:', err); // 处理错误
      } else {
        console.log('Get result:', result); // 处理结果
      }
    });
  • 更新(Update)

    javascript 复制代码
    redis.set('key', 'new_value', function(err, result) {
      if (err) {
        console.error('Error updating key:', err); // 处理错误
      } else {
        console.log('Update result:', result); // 处理结果
      }
    });
  • 删除(Delete)

    javascript 复制代码
    redis.del('key', function(err, result) {
      if (err) {
        console.error('Error deleting key:', err); // 处理错误
      } else {
        console.log('Delete result:', result); // 处理结果
      }
    });

3. 对比理解

  • 链式调用 vs 嵌套回调

    • Promise风格允许链式调用.then().catch(),使得代码更加线性和易于阅读,特别是在处理多个异步操作时,避免了所谓的"回调地狱"。
    • 回调函数风格需要嵌套回调,当有多个异步操作时,代码可能会变得难以阅读和维护。
  • 错误处理

    • Promise风格通过.catch()集中处理错误,使得错误处理更加集中和一致。
    • 回调函数风格在每个回调中单独处理错误,可能会导致错误处理代码的重复。
  • 异步控制

    • Promise风格提供了更多的控制力,如async/await语法,使得异步代码可以像同步代码一样编写,提高了代码的可读性和可维护性。
    • 回调函数风格在异步控制上较为有限,通常需要手动管理回调的执行顺序。

总的来说,Promise风格提供了一种更加现代和强大的异步处理方式,而回调函数风格则是Node.js中传统的处理方式。选择哪种方式取决于个人偏好和项目需求。

三. 使用Express和ioredis管理Redis中的代办任务:深入指南

这段代码是一个使用Express框架和ioredis库的Node.js应用程序,它提供了一个简单的API来管理代办任务。这些任务存储在Redis数据库中,使用哈希数据结构。下面我将结合这段代码,深入讲解Redis中的相关知识。

Redis哈希数据结构

Redis的哈希是一个字符串字段到字符串值的映射表,适合用于存储对象。在上面的代码中,每个代办任务都被表示为一个对象,并存储在Redis哈希中。

1. 代办任务的添加

javascript 复制代码
router.post('/add', (req, res) => {
    let { task } = req.body;
    let taskId = Date.now();
    const key = `task_obj:${taskId}`;
    redis.hset(key, { state: 0, code: 0, task: task }, (error, result) => {
        if (error) {
            console.log(error);
            return res.status(500).send('Error adding task');
        }
        res.send("Task added OK!");
    });
});
  • hset命令用于设置哈希表中的字段值。如果字段已存在,则会更新其值。
  • taskId是使用当前时间戳生成的唯一标识符,确保每个任务都有一个唯一的键。
  • key是任务的唯一键,格式为task_obj:<taskId>
  • 回调函数中的error参数用于处理可能发生的错误,result参数表示操作的结果。

2. 查看所有代办任务

javascript 复制代码
router.get('/tasks', async (req, res) => {
    const keys = await redis.keys("task_obj:*");
    let task_item;
    const task_arr = [];
    keys.forEach(async v => {
        task_item = await redis.hgetall(v);
        task_arr.push({ ...task_item, tid: v.substr(9) });
        if (keys.length == task_arr.length) {
            res.json({ code: 0, data: task_arr });
        }
    });
});
  • keys命令用于查找所有匹配给定模式的键。这里使用task_obj:*模式来查找所有任务对象。
  • hgetall命令用于获取哈希表中所有的字段和值。
  • 使用async/await语法来处理异步操作,使代码更易于阅读和维护。
  • task_arr数组用于存储所有任务对象,每个对象都包含一个额外的tid属性,它是任务的唯一标识符。

3. 获取单个代办任务

javascript 复制代码
router.get('/task', (req, res) => {
    let { id } = req.query;
    let key = `task_obj:${id}`;
    redis.hgetall(key, (error, task) => {
        if (error) {
            return res.status(500).send('Error retrieving task');
        }
        if (!task) {
            return res.status(404).send('Task not found');
        }
        res.json({ code: 0, task });
    });
});
  • 通过查询参数id来获取任务的键,并使用hgetall命令获取任务的详细信息。
  • 如果任务不存在,返回404状态码。

4. 更新代办任务

javascript 复制代码
router.post('/update', (req, res) => {
    let { id, task } = req.body;
    let key = `task_obj:${id}`;
    redis.hset(key, { code: 0, state: 0, task: task }, (error, result) => {
        if (error) {
            console.log(error);
            return res.status(500).send('Error updating task');
        }
        res.send('Task updated');
    });
});
  • 与添加任务类似,hset命令用于更新任务的状态和内容。
  • 如果更新失败,返回500状态码。

5. 删除代办任务

javascript 复制代码
router.get('/del', (req, res) => {
    let { id } = req.query;
    let key = `task_obj:${id}`;
    redis.del(key, (error, result) => {
        if (error) {
            console.log(error);
            return res.status(500).send('Error deleting task');
        }
        if (result === 0) {
            return res.status(404).send('Task not found');
        }
        res.send('Task deleted');
    });
});
  • del命令用于删除一个或多个键。
  • result参数表示被删除的键的数量。如果为0,则表示没有键被删除,可能是因为键不存在。

总结

这段代码展示了如何在Node.js应用程序中使用Redis来存储和管理代办任务。通过ioredis库,我们可以轻松地执行Redis命令,如hsethgetallkeysdel等,来操作哈希数据结构。此外,代码中使用了异步/等待(async/await)语法来简化异步操作的处理,使代码更加清晰和易于维护。

相关推荐
小小小CTFER1 分钟前
数据结构_实现双向链表
数据结构·链表
芳菲菲其弥章7 分钟前
数据结构经典算法总复习(上卷)
数据结构·算法
ByteMaster_24 分钟前
B树的性质和插入过程
数据结构·b树
cloud___fly1 小时前
黑马Redis数据结构学习笔记
数据结构·redis·笔记·学习
Menior_1 小时前
数据结构—图
数据结构·c++·算法
东阳马生架构2 小时前
Redis应用—9.简单应用汇总
redis
YGGP2 小时前
【LeetCode】将有序数组转换为二叉搜索树
数据结构·算法·leetcode
杜杜的man2 小时前
redis库基础知识
数据库·redis·缓存
码农丁丁2 小时前
[前端]mac安装nvm(node.js)多版本管理
前端·macos·node.js·nvm
ALISHENGYA2 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(多分支结构)
数据结构·c++·算法