1:算是正式开始把。。。。。
还是看了一下redis的视频,今天主要学习的是redis的客户端,在下面总结,然后具体学习了一下算法的时间复杂度分析。。
2:JAVA
Redis客户端的学习
Redis客户端到底是个啥呢?简单来说,它就像是一个"接口",只不过这个接口的具体实现是由各种编程语言提供的。我们主要关注的是Java里的两个常用工具:`Jedis`和`Spring-Redis`。通过它们,Java可以提供一些类和方法,用来创建连接、操作Redis数据库。大体流程是:先建立连接,然后通过Redis对象来操作数据。 接下来,我具体讲一下`Jedis`和`Spring-Redis`的区别,以及我是怎么学习它们的。
Jedis
首先看`Jedis`,它的使用其实很简单。比如,我们可以这样写代码来连接Redis:
csharp
private Jedis jedis;
@BeforeEach
void setUp() {
// 设置Redis的地址和端口号
jedis = new Jedis("192.168.100.128", 6379);
// 设置密码
jedis.auth("123");
// 选择数据库(Redis支持多个库,默认是0号库)
jedis.select(0);
}
这里的Jedis
连接配置,其实就是告诉程序:你要连哪个Redis服务器(远程还是本地)、用什么端口、密码是多少,以及选哪个数据库。如果是本地连接,区别就在于主机地址(host)不同。
Jedis操作Redis的例子
接下来看几个实际操作Redis的例子。 **1. 操作字符串** 比如,我们可以用`set`和`get`方法来存取数据:
csharp
void testString() {
String result = jedis.set("name", "zzh"); // 存入键值对
System.out.println("result=" + result); // 打印结果
String name = jedis.get("name"); // 获取键对应的值
System.out.println("name=" + name); // 打印值
}
这里,jedis.set
就相当于在Redis命令行里执行SET name zzh
,而jedis.get
就是GET name
。本质上,Jedis
就是把命令行的操作封装成了代码。 2. 操作哈希表 再比如,我们可以用hset
和hgetall
来操作哈希表:
javascript
void testHash() {
// 插入哈希表数据
jedis.hset("user:1", "name", "张智昊");
jedis.hset("user:1", "age", "18");
// 获取整个哈希表
Map<String, String> hgetAll = jedis.hgetAll("user:1");
System.out.println(hgetAll); // 打印结果
}
运行后,你会看到类似这样的输出:
ini
{name=张智昊, age=18}
Jedis的局限性
虽然`Jedis`用起来挺方便,但它也有点麻烦的地方。比如,它的方法参数要求很严格,必须是固定的格式。我们来看一下源码:
kotlin
public String set(String key, String value) {
this.checkIsInMultiOrPipeline();
this.client.set(key, value);
return this.client.getStatusCodeReply();
}
public String set(String key, String value, SetParams params) {
this.checkIsInMultiOrPipeline();
this.client.set(key, value, params);
return this.client.getStatusCodeReply();
}
从源码可以看出,set
方法只能接受key
和value
都是字符串类型的参数。如果你想插入类似user:{"name":"zzh"}
这种结构化的数据,Jedis
是不支持的,得自己想办法绕过去。 同理,get
方法也要求传入的key
必须是字符串类型:
kotlin
public String get(String key) {
this.checkIsInMultiOrPipeline();
this.client.get(key);
return this.client.getBulkReply();
}
总结一下,Jedis
的用法其实跟直接在Redis命令行里敲命令差不多,但因为它的参数限制比较死板,所以有时候会显得有点麻烦。
总结
总的来说,`Jedis`就是一个能让我们用Java代码操作Redis的工具。它的好处是简单直接,缺点是灵活性不够,尤其是处理复杂数据结构时会有点别扭。至于`Spring-Redis`,它在`Jedis`的基础上做了更多封装,后面我会继续学习并分享心得。
spring-redis:
我们先来看一下 `spring-redis` 是如何使用的。以下是一个简单的代码示例,展示了如何通过 `RedisTemplate` 操作 Redis 中的字符串类型数据:
csharp
void TestString() {
// 使用 opsForValue() 方法设置键值对
redisTemplate.opsForValue().set("name:", "张智昊");
// 通过键获取对应的值
Object name = redisTemplate.opsForValue().get("name:");
// 输出获取到的值
System.out.println("name=" + name);
}
从这段代码中可以看出,Spring 框架内置了一个名为 StringRedisTemplate
的工具类,其中提供了 opsForValue()
方法。通过这个方法,我们可以选择操作 Redis 中的数据类型。例如,上面的代码演示了如何对字符串类型的数据进行存储和获取(键值对形式)。
默认序列化机制的问题
然而,默认情况下,`RedisTemplate` 使用的是 Spring 自带的序列化机制(通常是基于 Java 的序列化方式)。这种序列化方式会将数据以二进制的形式存储到 Redis 中。例如,当我们插入键值对 `"name:" -> "张智昊"` 时,实际存储到 Redis 中的数据可能如下所示:
\xac\xed\x00\x05t\x00\x09\xe5\xbc\xa0\xe6\x99\xba\xe6\x98\x8a
可以看到,这段数据是经过序列化后的二进制内容,虽然它能够被程序正确解析,但对于人类来说几乎是不可读的。这种默认的序列化机制可能会带来以下几个问题:
- 可读性差:由于数据是以二进制形式存储的,直接查看 Redis 数据库的内容时无法直观理解其含义。
- 兼容性问题:如果其他语言或框架需要与 Redis 进行交互,它们可能无法解析这种特定的序列化格式。
- 调试困难:在开发和调试过程中,查看 Redis 数据变得复杂,增加了排查问题的难度。
解决方案:自定义序列化机制
为了解决上述问题,我们可以通过自定义 `RedisTemplate` 的序列化器来改进数据存储的方式。例如,可以使用 `StringRedisSerializer` 或 `Jackson2JsonRedisSerializer` 等更友好的序列化方式,使存储的数据更加清晰易读。 以下是配置 `RedisTemplate` 使用 `StringRedisSerializer` 的示例代码:
arduino
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 key 和 value 的序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
// 设置连接工厂
template.setConnectionFactory(redisConnectionFactory);
return template;
}
通过这种方式,存储到 Redis 中的数据将变为更直观的字符串形式。例如,插入 "name:" -> "张智昊"
后,Redis 中的实际存储内容将直接显示为:
arduino
"张智昊"
这不仅提高了数据的可读性,还增强了与其他系统的兼容性,同时也便于开发人员进行调试和维护。
3:数据结构于算法
时间复杂度:
主要是学习了一下时间复杂度分析,这算算法的基础吧,毕竟算法就是如何用更短的时间更小的内存去实现同样的事情
我感觉学到的主要就是你去关注那个带n的式子常数级别的运算,对整个程序影响不大,一旦牵扯到n就提高一个层次:
大 O 复杂度表示法:
**1. 只关注循环执行次数最多的一段代码**
我刚才说了,大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。所以, 我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了 。这段核心代码执行次数的 n 的量级,就是整段要分析代码的时间复杂度。
2. 加法法则:总复杂度等于量级最大的那段代码的复杂度
总的时间复杂度 * 就 *等于量级最大的那段代码的时间复杂度。那我们将这个规律抽象成公式就是:
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).
3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
我刚讲了一个复杂度分析中的加法法则,这儿还有一个 乘法法则 。类比一下,你应该能"猜到"公式是什么样子的吧?
如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)T2(n)=O(f(n)) O(g(n))=O(f(n)*g(n)).
也就是说,假设 T1(n) = O(n),T2(n) = O(n2),则 T1(n) * T2(n) = O(n3)。
空间复杂度分析
前面,咱们花了很长时间讲大 O 表示法和时间复杂度分析,理解了前面讲的内容,空间复杂度分析方法学起来就非常简单了。
前面我讲过,时间复杂度的全称是 渐进时间复杂度 , 表示算法的执行时间与数据规模之间的增长关系 。类比一下,空间复杂度全称就是 渐进空间复杂度 (asymptotic space complexity), 表示算法的存储空间与数据规模之间的增长关系 。
我还是拿具体的例子来给你说明。(这段代码有点"傻",一般没人会这么写,我这么写只是为了方便给你解释。)
css
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i <n; ++i) {
a[i] = i * i;
}
for (i = n-1; i >= 0; --i) {
print out a[i]
}
}
复制代码
跟时间复杂度分析一样,我们可以看到,第 2 行代码中,我们申请了一个空间存储变量 i,但是它是常量阶的,跟数据规模 n 没有关系,所以我们可以忽略。第 3 行申请了一个大小为 n 的 int 类型数组,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O(n)。
我们常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。所以,对于空间复杂度,掌握刚我说的这些内容已经足够了。