开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

「Rust 重写 sqlite」REPL

发表于 2021-11-13

「这是我参与11月更文挑战的第 7 天,活动详情查看:2021最后一次更文挑战」


REPL

既然我们是以SQLite为模型,那么我们不妨试着照搬SQLite的样子,以方便用户使用。当你运行sqlite3 时,SQLite启动一个 读-执行-打印 的循环,又称:REPL。

1
2
3
4
5
6
7
8
9
10
11
shell复制代码❯ sqlite3

SQLite version 3.32.3 2020-06-18 14:16:19
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .databases
main:
sqlite> .dadada
Error: unknown command or invalid arguments: "dadada". Enter ".help" for help
sqlite> .exit

首先为了实现上述结果,这里要做出一些设计选择。由于这个项目的重点是研究和建立一个数据库,大部分开发精力集中于此,这意味着我不想花大部分时间重新发明轮子和编写CLI解释器或REPL逻辑。因此,对于这些,我决定利用已经开发的、有点成熟的第三方库来完成。不过也许在未来,如果有一些空闲时间,并且使用这些第三方库确实影响了应用程序的整体性能,我可以随时回来替换。

REPL的逻辑是非常直接:

一个无限循环中,打印一个提示,获得一个输入行,验证然后处理该行。

我决定使用 rustyline crate,它已经相当成熟,内存效率高,而且已经解决了很多我们必须处理的问题,甚至从用户体验方面来说,例如,实时提供提示和自动完成,这是一个非常好的功能。

因此,在正式编写代码前,你可以在Github上找到它。下面我将使用一个demo snippet,快速演示 rustyline 是如何通过一个简单的例子工作的:

首先,需要在你的 cargo.toml 中添加该依赖关系:

1
2
toml复制代码[dependencies]  
rustyline = "7.1.0"

main.rs 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
rust复制代码use rustyline::error::ReadlineError;
use rustyline::Editor;

fn main() {
// 创建一个具有默认配置选项的编辑器
let mut repl = Editor::<()>::new();
// 加载了一个带有历史命令的文件。如果该文件不存在,它会创建一个
if repl.load_history(".repl_history").is_err() {
println!("No previous history.");
}
// 无限循环,一直卡在这里,直到用户终止程序
loop {
// 要求用户输入一个命令。你可以在这里添加任何你想要的东西作为前缀
let readline = repl.readline(">> ");

// readline返回一个结果。然后用匹配语句来过滤这个结果
match readline {
Ok(line) => {
repl.add_history_entry(line.as_str());
println!("Line: {}", line);
},
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break
},
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break
},
Err(err) => {
println!("Error: {:?}", err);
break
}
}
}
// 把命令保存到文件中。到现在为止,它们都储存在内存中
repl.save_history(".repl_history").unwrap();
}

就这样,有了上述代码,你就有了一个基本的REPL程序,开始开始运行。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis(十)redis使用list解决高并发问题,如商品

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

redis真的是一个很好的技术,它可以很好的在一定程度上解决网站一瞬间的并发量,例如商品抢购秒杀等活动。

redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力。

为什么redis的地位越来越高,我们为何不选择memcache,这是因为memcache只能存储字符串,而redis存储类型很丰富(例如有字符串、LIST、SET等),memcache每个值最大只能存储1M,存储资源非常有限,十分消耗内存资源,而redis可以存储1G,最重要的是memcache它不如redis安全,当服务器发生故障或者意外关机等情况时,redsi会把内存中的数据备份到硬盘中,而memcache所存储的东西全部丢失;这也说明了memcache不适合做数据库来用,可以用来做缓存。

下面用redis解决瞬间秒杀活动来说明:

下面这个程序模拟了20人一瞬间涌入这个页面进行秒杀,能够秒杀成功的只有10人,我们把先进来的用户放入redis队列中,当队列中的用户达到10时,后来用户就转到秒杀结束页面。这里用随机数来表示不同的用户。

程序如下:这里只是单纯的模拟redis,具体应用还需要自行设计模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
php复制代码<?php
$redis = new Redis();
$redis->connect("127.0.0.1",6379);
$redis_name = "miaoshaceshi";
$num = 10;
// 获取redis列表长度
$len = $redis->llen($redis_name);
if($len < $num){
$uuid = mt_rand(100000,999999);
$redis->rpush($redis_name,$uuid);
echo "秒杀成功!";
}else{
echo "对不起,来晚了!";
}

// $r = $redis->lpop("miaoshaceshi");
// var_dump($r);

?>

我这里使用postman来模拟并发测试上边的代码。结果如下图所示:

111.png

有兴趣你可以自己试一下。

当然,使用postman来模拟并发请求这个是不对的,postman是顺序请求,当然效果大概是一样的,并发请求当然也还有其他的处理方式,比如,你可以在前端进行限制,抢购按钮只允许点击一次,接口没有返回数据之前不允许再次点击等等,这个需要看你自己项目的具体逻辑,但是redis仍然是处理抢购增加服务器性能的一个很好地工具

以上就是redis处理高并发的大概原理,当然,和实践还是有区别的。

有好的建议,请在下方输入你的评论。

欢迎访问个人博客
guanchao.site

欢迎访问小程序:

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Flask 入门系列之 request 对象!

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战。

一个完整的 HTTP 请求,包括客户端向服务端发送的 Request 请求和服务器端发送的 Response 响应。为了能方便的访问获取请求及响应报文信息,Flask 框架提供了一些内建对象,下面就来说一下 Flask 针对请求提供的内建对象request。

request请求对象

request请求对象封装了从客户端发来的请求报文信息,我们能从request对象上获取请求报文中的所有数据。
其大部分功能是由依赖包Werkzeug完成的,Flask 做了一些特定功能的封装,形成了request请求对象。

request请求对象的使用

导入flask包中的request对象,就可以直接在请求函数中使用该对象了。

1
2
3
4
5
6
7
8
9
10
11
python复制代码from flask import request

@app.route('/user', methods=['GET', 'POST'])
def user():
if request.method == 'POST':
user_name = request.form['user_name']
return 'user :{} add success!'.format(user_name)

else:
user_id = request.args.get('user_id', 0)
return 'Hello user:{}!'.format(user_id)

上述代码中,request对象中的method变量可以获取当前请求的方法,即GET、POST、PUT、DELETE等;form变量获取POST请求form-data表单中的数据,本质是一个字典,如果提交的表单中没有user_name,则会返回 400 Bad Request 的错误,当然也可以使用异常捕获机制处理。request.args.get()方法获取的是GET请求的url中的参数(问号?之后的部分),第一个参数指定获取的 url 参数的 key,第二个参数指定默认值,当 key 不存在时,则返回默认值。

如下:

  • GET请求
    image.png
  • POST请求
    image.png

除此之外,请求报文中的其他信息都可以通过 request 对象提供的属性和方法获取,常用的部分如下:

  • url:请求的 url
  • args:Werkzeug 的 ImmutableMultiDict 对象,存储解析后的查询字符串,可通过字典方式获取键值
  • blueprint:当前蓝本的名称
  • cookies:一个包含所有随请求提交的 cookies 的字典
  • data:包含字符串形式的请求数据
  • endpoint:于当前请求相匹配的端点值
  • files:Werkzeug 的 MultiDict 对象,包含所有上传文件
  • form:Werkzeug 的 ImmutableMultiDict 对象,包含解析后的表单数据
  • values:Werkzeug 的 CombinedMultiDict 对象,结合了 args 和 form 属性的值
  • get_data(cache=True,as_text=False,parse_from_data=False):获取请求中的数据,默认读取为字节字符串(bytestring),as_text为True则返回解码后的unicode字符串
  • get_json(self,force=False,silent=False,cache=True):作为 json 解析并返回数据,如果 MIME 类型不是 json,返回 None(除非 force 设为 True);解析出错则抛出Werkzeug提供的 BadRequest 异常(如果未开启调试模式,则返回400错误响应),如果 silent 设为 True 则返回 None;cache 设置是否缓存解析后的 json 数据
  • headers:Werkzeug 的 EnvironHeaders 对象,包含请求的头部字段
  • json:包含解析后的 json 数据,内部调用 get_json(),可通过字典的方式获取键值
  • method:请求的 HTTP 方法
  • referrer:请求发起的源 url,即 referer
  • scheme:请求的URL模式(http 或 https)
  • user_agent:用户代理(User Agent),包含了用户的客户端类型,操作系统类型等信息

原创不易,如果小伙伴们觉得有帮助,麻烦点个赞再走呗~

最后,感谢女朋友在工作和生活中的包容、理解与支持 !

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot + thymeleaf foreach

发表于 2021-11-13

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

话说自从前后端分离之后,前后端放在一起的场景就很少了,最近写个简单的后台,突然踩坑了,使用themeleaf模板渲染时,发现th:each来遍历生成表单数据,一直抛异常,提示Property or field 'xxx' cannot be found on null

接下来看一下这个问题到底是个什么情况

I. 项目搭建

1. 项目依赖

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

开一个web服务用于测试

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<dependencies>
<!-- 邮件发送的核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

配置文件application.yml

1
2
3
4
5
6
7
8
9
10
yaml复制代码server:
port: 8080

spring:
thymeleaf:
mode: HTML
encoding: UTF-8
servlet:
content-type: text/html
cache: false

II. 问题复现与处理

1. 场景复现

一个最基础的demo,来演示一下问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码@Controller
public class IndexController {
public Map<String, Object> newMap(String key, Object val, Object... kv) {
Map<String, Object> map = new HashMap<>();
map.put(key, val);
for (int i = 0; i < kv.length; i += 2) {
map.put(String.valueOf(kv[i]), kv[i + 1]);
}
return map;
}

@GetMapping(path = "list")
public String list(Model model) {
List<Map> list = new ArrayList<>();
list.add(newMap("user", "yh", "name", "一灰"));
list.add(newMap("user", "2h", "name", "2灰"));
list.add(newMap("user", "3h", "name", "3灰"));
model.addAttribute("list", list);
return "list";
}
}

对应的html文件如下(注意,放在资源目录 templates 下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
html复制代码<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>

<div>
<div th:each="item: ${list}">
<span th:text="${item.user}"></span>
&nbsp;&nbsp;
<span th:text="${item.name}"></span>
</div>

<hr/>

<p th:each="item: ${list}">
<p th:text="${item.user}"></p>
&nbsp;&nbsp;
<p th:text="${item.name}"></p>
</p>
</div>
</body>
</html>

注意上面的模板,有两个each遍历,出现问题的是第二个

2. 原因说明

上面提示user没有,那么是否是语法问题呢?将html改成下面这个时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
html复制代码<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
</head>
<body>

<div>
<div th:each="item: ${list}">
<span th:text="${item.user}"></span>
&nbsp;&nbsp;
<span th:text="${item.name}"></span>
</div>
</div>
</body>
</html>

相同的写法,上面这个就可以,经过多方尝试,发现出现问题的原因居然是<p>这个标签

简单来讲,就是<p>标签不能使用th:each,测试一下其他的标签之后发现<img>,<input>标签也不能用

那么问题来了,为啥这几个标签不能使用each呢?

这个原因可能就需要去瞅一下实现逻辑了,有知道的小伙伴可以科普一下

III. 不能错过的源码和相关知识点

0. 项目

  • 工程:github.com/liuyueyi/sp…
  • 源码:github.com/liuyueyi/sp…

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

  • 一灰灰Blog个人博客 blog.hhui.top
  • 一灰灰Blog-Spring专题博客 spring.hhui.top

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python matplotlib 绘制柱状图 1 柱状图

发表于 2021-11-13

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

复习回顾

Python 为数据展示提供了大量优秀的功能包,其中 matplotlib 模块可以方便绘制制作折线图、柱状图、散点图等高质量的数据包。

关于 matplotlib 模块,我们前期已经对matplotlib进行基本框架、以及常用方法的学习

  • matplotlib 模块概述:对matplotlib模块进行初步认识,对常用的方法进行学习
  • matplotlib 模块底层原理:matplotlib 模块包含脚本层、美工层及后端层三层细节了解
  • matplotlib 模块折线图绘制:总结折线图相关属性和方法

在 matplotlib 模块提供的图表中,除了折线图使用最多外,柱状图也是我们日常数据分析的图表。

image.png

本期,我们开始学习绘制柱状图相关属性和方法,let’s go~

  1. 柱状图概述

  • 什么是柱状图

+ 柱状图又称为条形图,是一种以长方形的长度为变量数据进行统计的图表
+ 柱状图用来比较两个或以上类型
+ 柱状图只有一个以长方形的长度为变量
+ 柱状图可以横向排列或者多维方式展示
  • 柱状图使用场景

+ 柱状图适用在较小数据集的分析
+ 适用二维数据集,只比较一个维度数据差异项
+ 直观展示各个体之间数据的差异
+ 表现离散型的时间序列
  • 柱状图绘制步骤

1. 导入matplotlib.pyplot模块
2. 准备数据,可以使用numpy/pandas整理数据
3. 调用pyplot.bar()绘制柱状图
  • 案例展示

本次,我们分析过去5年内的产品年销量展示

+ 案例所用到的数据如下:



1
2
3
4
python复制代码import random

x_data = ["20{}年".format(i) for i in range(16,21)]
y_data = [random.randint(100,300) for i in range(6)]
+ 绘制柱状图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
scss复制代码
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"]=['SimHei']
plt.rcParams["axes.unicode_minus"]=False

for i in range(len(x_data)):
plt.bar(x_data[i],y_data[i])

plt.title("销量分析")
plt.xlabel("年份")
plt.ylabel("销量")

plt.show()
![image.png](https://gitee.com/songjianzaina/juejin_p13/raw/master/img/bac5f5fdacf392a0937ac4f960bcc6419490939c31ea329189433ed245ccef14)
  1. 柱状图属性

  • 柱状体颜色填充

+ facecolor(fc)关键字
+ color 关键字
+ 颜色简称:
属性值 说明 属性值 说明
“b”/“bule” 蓝色 “m”/“magenta” 品红
“g” /“green” 绿色 “y”/“yellow” 黄色
“r”/“red” 红色 “k”/“black” 黑色
“c”/“cyan” 青色 “w”/“white” 白色
+ rgb:


    - 格式形式:(r,g,b)
    - 取值范围:0~1
  • 柱状描边设置

+ 柱状体边框颜色


    - edgecolor 或者 ec
+ 柱状体边框样式


    - linestyle 或者 ls
    - 线条样式:
    | 属性值 | 说明 |
    | -------------- | ------ |
    | "-" 、"solid" | 默认实线显示 |
    | "--"、"dashed" | 虚线 |
    | "-." "dashdot" | 点划线 |
    | ":"、"dotted" | 虚线 |
    | "None" """" | 空
+ 柱状体边框宽度


    - linewidth 或者 lw
  • 柱状图填充样式

+ hatch: 设置填充样式
+ 属性取值:{'/', '', '|', '-', '+', 'x', 'o', 'O', '.', '\*'} |
  • 柱状图刻度标签

+ tickle label:默认使用数字标签
  • 我们对 第一节柱状图添加边框样式为”–”,添加指定rgb颜色,填充圆圈
1
2
python复制代码for i in range(len(x_data)):
plt.bar(x_data[i],y_data[i],color=(0.2*i,0.2*i,0.2*i),linestyle="--",hatch="o")

image.png

  1. 堆叠柱状图

在柱状图中,我们会在同时对比两组数据在同一类中的表现形式,因此需要绘制堆叠柱状图

  • bottom : 条形底座的y坐标,默认值为0
  • 在第一节案例中,添加一组y轴数据所有数据如下:
1
2
3
python复制代码 x_data = ["20{}年".format(i) for i in range(16,21)]
y_data = list(random.randint(100,300) for i in range(5))
y2_data = list(random.randint(100,300) for i in range(5))
  • 再添加一次pyplot.bar方法,添加bottom属性
1
2
python复制代码 plt.bar(x_data,y_data,lw=0.5,fc="r",label="Phone")
plt.bar(x_data,y2_data,lw=0.5,fc="b",label="Android",bottom=y_data)

image.png

  1. 并列柱状图

在绘制并列的柱状图中,要控制好每个柱状体的位置和大小可以使用width属性

  • width: 设置每组柱状体的宽度
  • x轴:x轴的宽度每组直接也要设置好
  • 例如继续改造上面案例,我们为bar1和bar2添加了width属性后,单独设置x轴并排的宽度为0.3
1
2
3
4
5
6
7
8
python复制代码x_width = range(0,len(x_data))
x2_width = [i+0.3 for i in x_width]


plt.bar(x_width,y_data,lw=0.5,fc="r",width=0.3,label="Phone")
plt.bar(x2_width,y2_data,lw=0.5,fc="b",width=0.3,label="Android")

plt.xticks(range(0,5),x_data)

image.png

  1. 水平柱状图

柱状图中,有时候需要让柱状图水平放置,比较差异,我们这时候需要使用到barh方法

  • pyplot.barh(y,width):绘制水平柱状图
  • 结合上述案例,改用barh方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Python复制代码  x_data = ["20{}年".format(i) for i in range(16,21)]
y_data = list(random.randint(100,300) for i in range(5))
y2_data = list(random.randint(100,300) for i in range(5))

x_width = range(0,len(x_data))
x2_width = [i+0.3 for i in x_width]

plt.barh(x_width,y_data,lw=0.5,fc="r",height=0.3,label="Phone")
plt.barh(x2_width,y2_data,lw=0.5,fc="b",height=0.3,label="Android")
plt.yticks(range(0,5),x_data)

plt.legend()

plt.title("销量分析")
plt.ylabel("年份")
plt.xlabel("销量")

plt.show()

image.png

  1. 添加折线柱状图

我们在查看柱状图时,有时候会需要辅助折线来查看

  • 使用pyplot.plot()方法汇总折线图
  • 同时使用pyplot.text()显示坐标值
  • 当堆叠图时,需要计算好折线相对位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码plt.plot(x_data,y_data,color="pink",linestyle="--")

plt.plot(x_data, y2_data+200, color="skyblue", linestyle="-.")

# 柱状图
plt.bar(x_data,y_data,lw=0.5,fc="r",width=0.3,label="Phone",alpha=0.5)
plt.bar(x_data,y2_data, lw=0.5, fc="b", width=0.3, label="Android",alpha=0.5,bottom=y_data)

for i,j in zip(x_data,y_data):

plt.text(i,j+0.05,"%d"%j,ha="center",va="bottom")

for i2,j2 in zip(x_data,y2_data):

plt.text(i2,j2+180,"%d"%j2,ha="center",va="bottom")

image.png

  1. 正负柱状图

我们需要使用Axes对象来设置坐标轴的位置

  • 首先使用pyplot.gca()方法创建axes对象
  • 然后使用matplotlib.spines模块调用set_position设置坐标轴位置
  • set_position 设置轴位置点
  • spines[]选项有”left”|”bottom”|”width”|”height”
  • set_position 值格式为(位置类型,数量);位置类型;”outward”|”axes”|”data”|;数量:中心->(“轴”,0.5),零->(“数据”,0.0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码y_data = np.random.randint(100, 300,5)
y2_data = np.random.randint(100, 300,5)

ax = plt.gca()
ax.spines["bottom"].set_position(('data', 0))

plt.bar(x_data,+y_data,lw=0.5,fc="r",width=0.3,label="Phone")
plt.bar(x_data,-y2_data, lw=0.5, fc="b", width=0.3, label="Android")

for i,j in zip(x_data,y_data):

plt.text(i,j,"%d"%j,ha="center",va="top")

for i2,j2 in zip(x_data,y2_data):

plt.text(i2,-j2,"%d"%j2,ha="center",va="bottom")

image.png

总结

本期,我们对matplotlib模块中详细学习绘制各种柱状图标相关属性和方法,在遇到需要直观展示离散数据点的差异时,我们可以使用bar()或者barh()绘制美观的图表。

以上是本期内容,欢迎大佬们点赞评论,下期见~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

直接内存(内存溢出、释放原理) 内存溢出 释放原理

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」

内存溢出

虽然直接内存(Direct Memory)不受jvm管理,但是它还是会有内存溢出的情况。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
js复制代码    static int ONE_HUNDRED_GB = 1024 * 1024 * 1024;

public static void main(String[] args){
List<ByteBuffer> byteBufferList = new ArrayList<>();
Integer num = 0;
try{
while (true){
// 每次分配的直接内存大小
ByteBuffer bBuf = ByteBuffer.allocateDirect(ONE_HUNDRED_GB);
byteBufferList.add(bBuf);
num++;
// 由于速度太快这里每次让线程休眠一下
Thread.sleep(2000l);
}
}catch (Exception e){
e.printStackTrace();
} finally{
System.out.println(num);
}
}

运行上面这段代码会报出:java.lang.OutOfMemoryError: Direct buffer memory 错误,提示直接内存溢出。

这里运行了三次就报直接内存溢出
image.png

运行前本机内存

image.png

运行时本机内存

image.png

总结

虽然直接内存并不由我们的jvm进行分配管理,但是还是会出现内存溢出的情况。

通过运行前内存和运行时内存可以看出,直接内存并不会占满我们计算机系统的所有内存,当达到一定大小时就会抛出内存溢出信息。

释放原理

直接内存的分配和回收释放主要是用到了一个叫做Unsafe对象。

分配是使用该对象中提供的本地方法 allocateMemory() 方法,在Java程序中最终是通过调用 Unsafe 对象中allocateMemory() 进行直接内存的分配。

回收释放是使用该对象中提供 freeMemory() 的本地方法进行直接内存的回收释放。

主要的体现在 ByteBuffer.allocateDirect 源码中,先点进该源码中。

image.png

再进入 new DirectByteBuffer(capacity) 方法中查看该源码。

image.png

在这里就能看到是通过 unsafe.allocateMemory(size) 方法进行分配的直接内存,然后们再看具体实现?进入会发现这个方法是 native 的,该方法的实现由其它的语言进行实现的没法查看,但是我们知道直接内存最终是通过该方法分配的就行。

我们继续找直接内存的释放,这段代码不能直接看到释放的方法,我们进入 new Deallocator(base, size, cap) 这里面,然后会发现下面有个run() 方法,该方法中调用了直接内存释放的方法 unsafe.freeMemory(address);

image.png

那么到底是怎么执行该 run() 方法的,看前面那张图主要是通过 cleaner(虚引用类型)它的特点是:当它所关联的这个对象被jvm回收时就会触发 Cleaner.clean 方法。

这里它关联的是ByteBuffer 对象,该对象是java对象,当该对象被垃圾回收掉时它就会执行 Cleaner 对象中 clean 方法。

查看 Cleaner 对象中clean方法

image.png

在这里会调用到刚才的 run() 方法进行直接内存的回收释放。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Redis入门】常见数据类型和命令 👉写在前边 命令大全

发表于 2021-11-13

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

👉写在前边

  • 上篇我们了解了 redis的历史和运行,了解了如何运行之后,我们这篇来总结一下redis中的常见数据类型和相关的常用命令

命令大全:

​www.redis.cn/commands.ht…

Redis-Key

EXPIRE key10

10s就过期

ttl key

查询过期剩余时间

type key

查看类型

move name 1(1表示当前数据库)

移除当前的key

大杂烩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
nginx复制代码127.0.0.1:6379> keys * # 查看所有的key
(empty list or set)
127.0.0.1:6379> set name kuangshen # set key
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> EXISTS name # 判断当前的key是否存在
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 # 移除当前的key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name qinjiang
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> clear
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"qinjiang"
127.0.0.1:6379> EXPIRE name 10 # 设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name # 查看当前key的剩余时间
(integer) 4
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name # 查看当前key的一个类型!
string
127.0.0.1:6379> type age
string

String

appen和strlen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
nginx复制代码127.0.0.1:6379> set key1 v1 # 设置值
OK
127.0.0.1:6379> get key1 # 获得值
"v1"
127.0.0.1:6379> keys * # 获得所有的key
1) "key1"
127.0.0.1:6379> EXISTS key1 # 判断某一个key是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 "hello" # 追加字符串,如果当前key不存在,就相当于setkey
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1 # 获取字符串的长度!
(integer) 7
127.0.0.1:6379> APPEND key1 ",kaungshen"
(integer) 17
127.0.0.1:6379> STRLEN key1
(integer) 17
127.0.0.1:6379> get key1
"v1hello,kaungshen"

incr和decr 自增和自减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
nginx复制代码# i++
# 步长 i+=
127.0.0.1:6379> set views 0 # 初始浏览量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增1 浏览量变为1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减1 浏览量-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # 可以设置步长,指定增量!
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
127.0.0.1:6379> DECRBY views 5

getRange字符串范围 setRange替换指定位置开始的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nginx复制代码# 字符串范围 range
127.0.0.1:6379> set key1 "hello,kuangshen" # 设置 key1 的值
OK
127.0.0.1:6379> get key1
"hello,kuangshen"
127.0.0.1:6379> GETRANGE key1 0 3 # 截取字符串 [0,3]
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 # 获取全部的字符串 和 get key是一样的
"hello,kuangshen"
# 替换!
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换指定位置开始的字符串!
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"

设置过期时间setex setnx(不存在才设置,存在时会失败)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
nginx复制代码# setex (set with expire) # 设置过期时间
# setnx (set if not exist) # 不存在时再设置(在分布式锁中会常常使用!)
127.0.0.1:6379> setex key3 30 "hello" # 设置key3的值为 hello,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis" # 如果mykey 不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
2) "mykey"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,创建失败!
(integer) 0
127.0.0.1:6379> get mykey

mset/get 同时设置/获取多个值

msetnx 是一个原子性的操作,要么一起成功,要么一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nginx复制代码mset
mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx 是一个原子性的操作,要么一起成功,要么一起失败!
(integer) 0
127.0.0.1:6379> get k4
(nil)

对象 set user:1:name xxx user:1:age xxx

其实相当于key=user:1:name这一串很长的,然后:后边可以更换属性

1
2
3
4
5
6
7
8
nginx复制代码# 对象
set user:1 {name:zhangsan,age:3} # 设置一个user:1 对象 值为 json字符来保存一个对象!
# 这里的key是一个巧妙的设计: user:{id}:{filed} , 如此设计在Redis中是完全OK了!
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

get同时set

1
2
3
4
5
6
7
8
9
nginx复制代码getset # 先get然后在set
127.0.0.1:6379> getset db redis # 如果不存在值,则返回 nil
(nil)
127.0.0.1:6379> get db
"redis
127.0.0.1:6379> getset db mongodb # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"

使用场景

String类似的使用场景:value除了是我们的字符串还可以是我们的数字!

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储!

Hash(跟String类似的其实)

Map集合,key-map! 时候这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的 key-vlaue!

hash存储经常变更的数据 user name age,尤其是是用户信息之类的,经常变动的信息! hash 更适合于对象的存储,String更加适合字符串存储!

hset/get

hdel(类似String的del)

hkeys/hvals(只获得所有key/val)

同样可以指定增量,可以hsetnx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
nginx复制代码127.0.0.1:6379> hset myhash field1 kuangshen # set一个具体 key-vlaue
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"kuangshen"
127.0.0.1:6379> hmset myhash field1 hello field2 world # set多个 key-vlaue
OK
127.0.0.1:6379> hmget myhash field1 field2 # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash # 获取全部的数据,
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定key字段!对应的value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
##########################################################################
hlen
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量!
(integer) 2
##########################################################################
127.0.0.1:6379> HEXISTS myhash field1 # 判断hash中指定字段是否存在!
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
##########################################################################
# 只获得所有field
# 只获得所有value
127.0.0.1:6379> hkeys myhash # 只获得所有field
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash # 只获得所有value
1) "world"
2) "hello"
##########################################################################
incr decr
127.0.0.1:6379> hset myhash field3 5 #指定增量!
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello # 如果不存在则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world # 如果存在则不能设置
(integer) 0

List

可以玩成双向队列,栈等

  • 底层是一个双向链表,对于两段的插入删除效率比较高,而对于中间索引值的插入和更改就相对慢了,因为查询慢

Lpush和Rpush LRange 查看区间值(0 -1就会查所有)

只在左边进的话就类似栈,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
nginx复制代码127.0.0.1:6379> LPUSH list one # 将一个值或者多个值,插入到列表头部 (左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 # 获取list中值!
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 # 通过区间获取具体的值!
1) "three"
2) "two"
127.0.0.1:6379> Rpush list righr # 将一个值或者多个值,插入到列表尾部 (右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"

Lpop和Rpop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nginx复制代码LPOP
RPOP
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
127.0.0.1:6379> Lpop list # 移除list的第一个元素
"three"
127.0.0.1:6379> Rpop list # 移除list的最后一个元素
"righr"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"

Lindex(通过下标获得 list 中的某一个值)

1
2
3
4
5
6
7
8
nginx复制代码Lindex
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1 # 通过下标获得 list 中的某一个值!
"one"
127.0.0.1:6379> lindex list 0
"two"

Llen 获取长度

1
2
3
4
5
6
7
8
9
nginx复制代码Llen
127.0.0.1:6379> Lpush list one
(integer) 1
127.0.0.1:6379> Lpush list two
bilibili:狂神说Java(integer) 2
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> Llen list # 返回列表的长度
(integer) 3

Lrem 移除count个指定value的值

注意是从左边开始删掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
nginx复制代码移除指定的值!
取关 uid
Lrem
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"

trim 截取

通过下标截取指定的长度,可以理解为只保留截取区间的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nginx复制代码trim 修剪。; list 截断!
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 通过下标截取指定的长度,这个list已经被改变了,截断了,只剩下截取的元素!
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"

rpoplpush 移除指定的值,移动到新的list中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nginx复制代码rpoplpush # 移除列表的最后一个元素,将他移动到新的列表中!
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除列表的最后一个元素,将他移动到新的列表中!
"hello2"
127.0.0.1:6379> lrange mylist 0 -1 # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1 # 查看目标列表中,确实存在改值!
1) "hello2"

Lset 修改指定下标的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nginx复制代码lset #将列表中指定下标的值替换为另外一个值,更新操作
127.0.0.1:6379> EXISTS list # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other # 如果不存在,则会报错!
(error) ERR index out of range

Insert 在指定value前边或者后边插入值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nginx复制代码linsert # 将某个具体的value插入到列把你中某个元素的前面或者后面!
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "world"
(integer) 2
127.0.0.1:6379> LINSERT mylist before "world" "other"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist after world new
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

Set

底层是一个哈希表,查询是O(1)

成员相关

Sadd 添加成员

Smember 查看所有成员

SIsMember 查看是否是成员之一

Scard 获取元素个数

srem 删除指定value

SrandMember 随机获取成员(不会pop)

Spop 随机pop出来

大杂烩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
nginx复制代码127.0.0.1:6379> sadd myset "hello" # set集合中添加匀速
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "lovekuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset # 查看指定set的所有值
1) "hello"
2) "lovekuangshen"
3) "kuangshen"
127.0.0.1:6379> SISMEMBER myset hello # 判断某一个值是不是在set集合中!
(integer) 1
127.0.0.1:6379> SISMEMBER myset world
(integer) 0
##########################################################################
127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数!
(integer) 4
##########################################################################
rem
127.0.0.1:6379> srem myset hello # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "lovekuangshen2"
2) "lovekuangshen"
3) "kuangshen"
##########################################################################
set 无序不重复集合。抽随机!
127.0.0.1:6379> SMEMBERS myset
1) "lovekuangshen2"
2) "lovekuangshen"
3) "kuangshen"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
1) "lovekuangshen"
2) "lovekuangshen2"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "lovekuangshen"
2) "lovekuangshen2"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"lovekuangshen2"
##########################################################################
删除定的key,随机删除key!
127.0.0.1:6379> SMEMBERS myset
1) "lovekuangshen2"
2) "lovekuangshen"
3) "kuangshen"
127.0.0.1:6379> spop myset # 随机删除一些set集合中的元素!
"lovekuangshen2"
127.0.0.1:6379> spop myset
"lovekuangshen"
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
##########################################################################
将一个指定的值,移动到另外一个set集合!
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "kuangshen" # 将一个指定的值,移动到另外一个set集
合!
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
2) "set2"
##########################################################################
微博,B站,共同关注!(并集)
数字集合类:
- 差集 SDIFF
- 交集
- 并集
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "b"
2) "a"
127.0.0.1:6379> SINTER key1 key2 # 交集 共同好友就可以这样实现
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "b"
2) "c"
3) "e"
4) "a"
5) "d"

ZSet(有序集合)

  • 在set的基础上,增加了一个值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
nginx复制代码127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
##########################################################################
排序如何实现
127.0.0.1:6379> zadd salary 2500 xiaohong # 添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 kaungshen
(integer) 1
# ZRANGEBYSCORE key min max
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部的用户 从小到大!
1) "kaungshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 从大到进行排序!
1) "zhangsan"
2) "kaungshen"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 显示全部的用户并且附带成
绩 1)
"kaungshen"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 显示工资小于2500员工的升
序排序!
1) "kaungshen"
2) "500"
3) "xiaohong"
4) "2500"
##########################################################################
# 移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 获取有序集合中的个数
(integer) 2
##########################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量!
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

💠下篇预告

  • 下篇我们将解析一下redis的配置文件,之后还会有Springboot整合以及自定义RedisTemplate和工具类等相关知识。

参考

  • 尚硅谷Redis6视频
  • 狂神说Redis视频

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Java入门100例】08素数和——break和cont

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

🌲本文收录于专栏《Java入门练习100例》——试用于学完「Java基础语法」后的巩固提高及「LeetCode刷题」前的小试牛刀。

点赞再看,养成习惯。微信搜索【一条coding】关注这个在互联网摸爬滚打的程序员。

本文收录于技术专家修炼,里面有我的学习路线、系列文章、面试题库、自学资料、电子书等。欢迎star⭐️

题目描述

难度:简单

计算500以内的素数和。

知识点

  • 素数的定义
  • break和continue
  • 开方运算

解题思路

1.素数的定义

大于1的自然数中,除了1和它本身以外不再有其他因数就叫做素数。

比如2=1×2;5=1×5;所以2、5就是素数。但6=1×6=2×3,即6除了1和自身6外还有其他因数2和3,不是素数。

2.break和continue

break:终止所有循环,直接跳出。

continue:终止本次循环,继续执行下一次循环。

3.开方运算

Math.sqrt()

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码/**
* 求500以内的素数和
*/
public class question_08 {
public static void main(String[] args) {
int sum=0,i,j;
for(j=2;j<=500;j++) {
for( i=2;i<=j/2;i++) {
if(j%i==0)
break; //说明除去1和本身有其他除数,不是素数,也就没有继续循环的必要
}
if(i>j/2) { //i>j/2说明,break没有被执行到,即除去1和本身无其他除数,是素数
sum+=j;
}
}
System.out.println(sum);
}
}

输出结果

扩展总结

思考:如果是计算500w以内的素数和,如何提高效率呢?

回答:将j/2改为Math.sqrt()。

最后

独脚难行,孤掌难鸣,一个人的力量终究是有限的,一个人的旅途也注定是孤独的。当你定好计划,怀着满腔热血准备出发的时候,一定要找个伙伴,和唐僧西天取经一样,师徒四人团结一心才能通过九九八十一难。
所以,

如果你想学好Java

想进大厂

想拿高薪

想有一群志同道合的伙伴

请加入技术交流

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MYSQL collate 带来的坑 定义 区别 坑 解决方

发表于 2021-11-13

定义

collate 是一种排序规则,用于在比较text、varchar等文本字段时,确定顺序的方案

如下所示,可以位于字段(name、nick)、表(employee)的定义上

1
2
3
4
5
6
sql复制代码CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(256) COLLATE utf8mb4_bin COMMENT '名字',
`nick` varchar(256) COLLATE utf8mb4_bin COMMENT '绰号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

也可以用于库上

create database test default character set utf8mb4 collate utf8mb4_general_ci;

或者在查询语句中直接使用

select id from employee order by name collate utf8mb4_general_ci

各处的collate按照以下顺序生效:

  1. SQL语句
  2. 列级
  3. 表级
  4. 库级
  5. 实例级

utf8与utfmb4关系

  • utf8 最长3字节,utfmb4 最长4字节
  • utfmb4 完全兼容utf8,所以尽量使用utfmb4
  • 有些表情时用4字节表示的,只能用utfmb4 表示

区别

最关键的点在于尾缀 _ci,case sensitive 大小写是否敏感

  • utf8mb4_bin:大小写敏感,二进制数据存储
  • utf8mb4_general_ci:不区分大小写,在对特殊字符排序时可能会不一致,速度快
  • utf8mb4_unicode_ci:不区分大小写,精准比较,速度较慢

坑

由于数据库默认的排序规则是 utf8mb4_general_ci (不区分大小写),所以如果不特别指定的话,在用字符串作为主键的情况下,就会出现不一致的key(大小不一致)却产生冲突的现象,如下所示

1
2
3
4
5
6
7
8
9
10
11
sql复制代码MySQL [test]> CREATE TABLE `business` (
`business_id` varchar(32) COMMENT '业务唯一标识符',
PRIMARY KEY (`business_id`)
) ENGINE=InnoDB;
Query OK, 0 rows affected (0.02 sec)

MySQL [test]> insert into business values("key");
Query OK, 1 row affected (0.00 sec)

MySQL [test]> insert into business values("KEY");
ERROR 1062 (23000): Duplicate entry 'KEY' for key 'PRIMARY'

解决方案

可以通过修改字段的排序方式,可以避免该问题

1
2
3
4
5
sql复制代码MySQL [test]> alter table business modify column business_id varchar(32) collate utf8mb4_bin;GINE=InnoDB;
Query OK, 0 rows affected (0.02 sec)

MySQL [test]> insert into business values("KEY");
Query OK, 1 row affected (0.00 sec)

可见在修改字段的排序方式后,就能插入进去了

在出现该问题后,只能从字段上修改排序方式了,实例、库、表的修改都不能影响该字段排序了;因为在创建该字段时,已经隐形继承了上一级的排序方式,当上级(实例、库、表)修改时,不会影响该字段的排序方式了,只能显示指定该字段的排序方式

1
2
3
4
5
sql复制代码MySQL [test]> ALTER TABLE `business` CHARACTER SET utf8mb4 collate utf8mb4_bin;
Query OK, 0 rows affected (0.02 sec)

MySQL [test]> insert into business values("KEY");
ERROR 1062 (23000): Duplicate entry 'KEY' for key 'PRIMARY'

如上所示,即使我们修改了表的排序方式,大小不一致的key仍有碰撞,即排序方式没变

其实我们通过表的创建语句也可以看出,当我们更新表的排序方式时,字段的不一致排序方式会显示展示出来:

1
2
3
4
5
6
7
8
9
sql复制代码MySQL [tms_test]> show create table business;
+----------+---------------+
| Table | Create Table
+----------+---------------+
| business | CREATE TABLE `business` (
`business_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '业务唯一标识符',
PRIMARY KEY (`business_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
+----------+---------------+

参考

Mysql的utf8与utf8mb4区别,utf8mb4_bin、utf8mb4_general_ci、utf8mb4_unicode_ci区别

MYSQL中的COLLATE是什么?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

C语言详解:数组

发表于 2021-11-13

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

数组

一维数组

创建
定义

数组是一组相同类型的元素的集合。那数组的语法形式:

1
2
3
C复制代码type_t arr_name [const_n]
//如:
int arr[10];

type_t 指的是数组元素的类型。

const_n 指的是一个常量表达式,用来指定数组的大小。

此时运行程序的话,系统会报一个警告:未初始化变量。打开调试就会发现系统默认填入一些无意义的数据。

系统默认未初始化值

当然全局数组的话,系统默认初始化为0;

1
2
3
4
C复制代码int arr[10];// 0 0 ... 0
int main(){
return 0;
}
创建实例
1
2
3
4
5
6
7
8
C复制代码//1.	
int arr[10];
//2.
int count = 10;
int arr2[count];//这样的创建数组可不可以呢?
//3.
float arr3[20];//浮点型数组
char ch[10];

数组的创建必须要[]使用常量,不能使用变量。(ps:虽然C99支持变长数组,但一般用常数创建就已经够用了)同样,我虽然用const_n表示常量,但可千万不要误会为const修饰的变量哦。

为什么呢?

因为数组控制不好容易越界访问非法内存,用变量的话风险太大,所以一直以来都是用常量创建数组的。

初始化

初始化,顾名思义,在创建数组的同时给予一些合理的初识值。如:

1
C复制代码int arr[10] = { 1,2,3 };//不完全初始化

这种是不完全初始化,剩余的元素默认是0

1
C复制代码int arr2[] = { 1,2,3,4 };//利用初始化内容,指定数组大小

这种是省略数组的const_n常量表达式

由初始化内容指定数组的大小

那下面这三个有什么不同呢?

/字符串数组初始化示

第一种是用字符串初始化数组,字符串有\0作为结束标志,虽不算字符串内容,但是可以说是字符串与生俱来的,所以它也被初始化作为数组内容。a b c \0

第二种和第三种是一样的,因为数组元素类型是字符型,且字符'b'的ACSII码值是98,自动将98解析为字符。a b c

使用

数组的访问是通过下标来访问的,默认下标是从0开始。通过下标引用操作符[]我们可以访问到数组元素。

1
2
3
4
5
6
7
8
C复制代码int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//数组的下标是从0开始的0~9
int sz = sizeof(arr) / sizeof(arr[0]);//10
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
//1 2 3 4 5 6 7 8 9 10

对于sizeof操作符,sizeof(arr),即sizeof+数组名,指的是计算整个数组的大小,算出来是40,然后sizeof(arr[0])是计算数组首元素的大小为4,这样一除就是元素个数啦。

使用变量sz,可以灵活的改变数组的大小,就不用再更改循环条件了。

总结:
  1. 数组是通过下标访问的,下标从0开始
  2. 数组的大小可以通过计算得到
内存存储

在这里插入图片描述

通过printf("&arr[%d]=%p\n", i,&arr[i]);这样的语句我们可以看到该数组在内存中的存储情况。

很明显的是,数组在内存中是连续存放的。

在这里插入图片描述

右边是十六进制的内存编号,可以看见每一个元素之间都相差4个字节,而一个整型元素正好占4个字节。

所以数组在内存中是连续存放的,随着数组下标的增长,地址也在增长,这也正是为什么指针变量+1,可以访问到下一个数组元素。

所以数组的本质是什么?

一组内存中连续存放的相同类型的元素。

二维数组

创建
1
2
3
4
5
c复制代码type_t arr_name[const_n][const_n]
//
int arr[3][5];//3行5列
char ch[4][7];//4行5列
double arr2[2][4]//2行4列

如上述代码所示:二维数组的语法结构就是,类型+数组名+[行][列]。

二维数组描述示意图

如图所示,二维数组在理解上就是这样的3行5列类似于表格的东西。就像线性代数里的矩阵,矩阵的定义就是一组数组成数表。

初始化
1
2
3
4
5
c复制代码//1.
int arr1[3][5] = { 1,2,3,4,5,6,7,8,9,10,11 };

//2.
int arr2[3][5] = { {1,2},{3,4},{5,6,7} };
  1. 第一种初始化,先一行一行填入,第一行是1 2 3 4 5,第二行是6 7 8 9 10,第三行不够就补零11 0 0 0 0 。
  2. 第二种的话,把每一行看成一个一维数组,不够的话还是补零,即第一行1 2 0 0 0 ,第二行3 4 0 0 0 ,第三行5 6 7 0 0 。

二维数组初始化示意

1
2
3
c复制代码char ch1[2][4] = { 'a','b' };
char ch2[2][4] = { {'a'},{'b'} };
char ch3[3][4] = { "abc","def","gh" };

当然用字符串去初始化二维数组的话,也是需要注意\0的问题。

第一行:a b c \0 ;第二行:d e f \0;第三行:g h \0 0

省略
1
c复制代码int arr2[][5] = { {1,2},{3,4},{5,6,7} };

像这样省略行可以,但是不能省略列。

行数可以根据初始化内容来规定,但如果列省略了就会造成歧义。

当然,省略必须在已经初始化的前提之下,不然行和列一概不知,怎么分配空间呢?

使用

当然二维数组同样是用下标访问数组内容的,也是从0开始。如:

二维数组下标示意

我们要去访问这个二维数组的话,我们当然是用两次循环遍历这个数组。

循环遍历二维数组示例

内存存储

当然我们也可以用同样的办法打印出每个元素的地址,如:

二维数组内存存储地址示例

  • 我们还是能发现每一个元素都是在内存中连续存放的。

这样的话,二维数组在内存中的存储形式便是大家想象中的二维的形式,把每一行理解为一个一维数组,这样的话二维数组在内存中的存储形式还是一维的。如下图的对比:

二维数组存储形式对比示意图

  • 从这里我们也可以理解到,二维数组的初始化里,为什么可以省略行不能省略列。

把行省略了,但是我们知道列,一个一个填满就是了,能填到多少行就有多少行。

理解方式
  • 对于二维数组,我们可以理解为每一行为一个元素的一维数组,该一维数组的每一个元素又是一个一维数组。

如数组arr[3][5] ,是有3个元素的一维数组,每个元素是一个有5个元素的一维数组。

指向二维数组的指针+1,指向的是下一行。

对于二维数组在内存存储形式的理解还是很重要的,有了这样的思想,我们就可以通过指针遍历得到数组元素,如:

1
2
3
4
5
6
c复制代码int arr[3][5] = { {1,2,3},{4,5,6},{7,8} };
int* p = &arr[0][0];
for (int i = 0; i < 15; i++)
{
printf("%d ", *p++);//1 2 3 0 0 4 5 6 0 0 7 8 0 0 0
}

数组越界问题

定义

数组通过下标访问,那么下标也就可以控制数组的访问范围。在数组前后进行访问的话,就是非法访问内存,即数组越界。

1
2
3
4
5
6
c复制代码//1 2 3 4 5 -858993460
int arr[5] = { 1,2,3,4,5 };
for (int i = 0; i <= 5; i++)//越界访问到第6个
{
printf("%d ", arr[i]);
}

数组越界访问到最后一个元素之后的一块内存,这就属于越界访问,-858993460是vs2019自动生成的随机值。

一般编译器是不会去检查数组越界访问的情况(vs2019太先进),所以我们就要有意识的主动检查。如果编译器提示这样的错误信息,那么一般就是数组越界了:

数组越界访问报错示例

数组作函数参数

在写代码时,我们经常会将数组作为参数,比如接下来的两个应用实例,那么我们这里以冒泡排序的实现作为案例。
在写代码时,我们经常会将数组作为参数,比如接下来的两个应用实例,那么我们这里以冒泡排序的实现作为案例。

排序算法一般有四种:冒泡排序、选择排序、插入排序和快速排序。

冒泡排序的核心思想:两两相邻的元素进行比较。

  • 一趟冒泡排序搞定一个数字,让其来到最终的位置上。
  • nnn 个元素,则总共需要 n−1n-1n−1 趟冒泡排序,每一趟排序需要进行 n−1−in-1-in−1−i 次判断大小。如分析图所示:
    冒泡排序示意图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
c复制代码void Print(int* arr, int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", *arr++);
}
}
void Sort(int arr[],int sz)//int* arr
//数组作形参本质是指针
{
//int sz = sizeof(arr) / sizeof(arr[0]);//err//用指针的sizeof值除以另一个值 = 4 / 4 = 1
for (int i = 0; i < sz - 1; i++)//n-1趟
{
for (int j = 0; j < sz - 1 - i; j++)//n-1-i次
{
if (arr[j] > arr[j + 1])//目标升序
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 1,4,6,3,7,9,3,2,8,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
//数组名单独放在sizeof中,表示整个数组
//排序
Sort(arr,sz);
//打印
Print(arr,sz);
return 0;
}
  1. 定义数组作形参时,本质上是指针。

void Sort(int *arr,int sz)本质上就是void Sort(int arr[],int sz)

所以Sort()函数内,sizeof(arr)也算的就是指针arr的大小,所以只能传参进去。

  1. 数组名arr何时代表整个数组何时代表数组首元素地址呢?
* 代表整个数组的情况:



> 单独放在`sizeof`操作符内部时,如`sizeof(arr);`  。
> 
> 
> 写出`&arr`时,代表的是整个数组,但表面仍为首元素地址。
* 代表首元素地址的情况:



> 除上面两以外其他都是代表首元素的地址。

应用实例

笔者实在没时间写两个应用实例的博客,所以在此将思维导图奉上,一般照着思维导图写就没问题了。感谢李姐和支持~

数组的应用实例1:三子棋

三子棋脑图

数组的应用实例2:扫雷游戏

扫雷脑图

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…353354355…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%