当前位置: 首页 > news >正文

GO实现Redis:GO实现Redis协议解析器(2)


  • 本文实现Redis的协议层,协议层负责解析指令,然后将指令交给核心database执行
  • echo database用来测试协议层的代码
  • https://github.com/csgopher/go-redis

RESP协议

RESP是客户端与服务端通信的协议,格式有五种:
正常回复:以“+”开头,以“\r\n”结尾的字符串形式
错误回复:以“-”开头,以“\r\n”结尾的字符串形式
整数:以“:”开头,以“\r\n”结尾的字符串形式
多行字符串:以“$”开头,后跟实际发送字节数,再以“\r\n”开头和结尾

$3\r\nabc\r\n

数组:以“*”开头,后跟成员个数

SET key value
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

客户端和服务器发送的命令或数据一律以 \r\n (CRLF)作为换行符。

当我们输入*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n这样一串命令,服务端接收到的是如下的命令:
*3\r\n
$3\r\n
SET\r\n
$3\r\n
key\r\n
$5\r\n
value\r\n


interface/resp/conn.go

type Connection interface {Write([]byte) errorGetDBIndex() intSelectDB(int)
}interface/resp/reply.go
type Reply interface {ToBytes() []byte
}

Connection接口:Redis客户端的一个连接
Write:给客户端回复消息
GetDBIndex:Redis有16个DB
Reply接口:响应接口


resp/reply/consts.go

type PongReply struct{}var pongBytes = []byte("+PONG\r\n")func (r *PongReply) ToBytes() []byte {return pongBytes
}var thePongReply = new(PongReply)func MakePongReply() *PongReply {return thePongReply
}type OkReply struct{}var okBytes = []byte("+OK\r\n")func (r *OkReply) ToBytes() []byte {return okBytes
}var theOkReply = new(OkReply)func MakeOkReply() *OkReply {return theOkReply
}var nullBulkBytes = []byte("$-1\r\n")type NullBulkReply struct{}func (r *NullBulkReply) ToBytes() []byte {return nullBulkBytes
}func MakeNullBulkReply() *NullBulkReply {return &NullBulkReply{}
}var emptyMultiBulkBytes = []byte("*0\r\n")type EmptyMultiBulkReply struct{}func (r *EmptyMultiBulkReply) ToBytes() []byte {return emptyMultiBulkBytes
}type NoReply struct{}var noBytes = []byte("")func (r *NoReply) ToBytes() []byte {return noBytes
}

定义五种回复:回复pong,ok,null,空数组,空

resp/reply/reply.go

type ErrorReply interface {Error() stringToBytes() []byte
}

ErrorReply:定义错误接口


resp/reply/errors.go

type UnknownErrReply struct{}var unknownErrBytes = []byte("-Err unknown\r\n")func (r *UnknownErrReply) ToBytes() []byte {return unknownErrBytes
}func (r *UnknownErrReply) Error() string {return "Err unknown"
}type ArgNumErrReply struct {Cmd string
}func (r *ArgNumErrReply) ToBytes() []byte {return []byte("-ERR wrong number of arguments for '" + r.Cmd + "' command\r\n")
}func (r *ArgNumErrReply) Error() string {return "ERR wrong number of arguments for '" + r.Cmd + "' command"
}func MakeArgNumErrReply(cmd string) *ArgNumErrReply {return &ArgNumErrReply{Cmd: cmd,}
}type SyntaxErrReply struct{}var syntaxErrBytes = []byte("-Err syntax error\r\n")
var theSyntaxErrReply = &SyntaxErrReply{}func MakeSyntaxErrReply() *SyntaxErrReply {return theSyntaxErrReply
}func (r *SyntaxErrReply) ToBytes() []byte {return syntaxErrBytes
}func (r *SyntaxErrReply) Error() string {return "Err syntax error"
}type WrongTypeErrReply struct{}var wrongTypeErrBytes = []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n")func (r *WrongTypeErrReply) ToBytes() []byte {return wrongTypeErrBytes
}func (r *WrongTypeErrReply) Error() string {return "WRONGTYPE Operation against a key holding the wrong kind of value"
}type ProtocolErrReply struct {Msg string
}func (r *ProtocolErrReply) ToBytes() []byte {return []byte("-ERR Protocol error: '" + r.Msg + "'\r\n")
}func (r *ProtocolErrReply) Error() string {return "ERR Protocol error: '" + r.Msg
}

errors定义5种错误:UnknownErrReply 未知错误,ArgNumErrReply 参数个数错误,SyntaxErrReply 语法错误,WrongTypeErrReply 数据类型错误,ProtocolErrReply 协议错误


resp/reply/reply.go

var (nullBulkReplyBytes = []byte("$-1")// 协议的结尾CRLF = "\r\n"
)type BulkReply struct {Arg []byte
}func MakeBulkReply(arg []byte) *BulkReply {return &BulkReply{Arg: arg,}
}func (r *BulkReply) ToBytes() []byte {if len(r.Arg) == 0 {return nullBulkReplyBytes}return []byte("$" + strconv.Itoa(len(r.Arg)) + CRLF + string(r.Arg) + CRLF)
}type MultiBulkReply struct {Args [][]byte
}func (r *MultiBulkReply) ToBytes() []byte {argLen := len(r.Args)var buf bytes.Bufferbuf.WriteString("*" + strconv.Itoa(argLen) + CRLF)for _, arg := range r.Args {if arg == nil {buf.WriteString("$-1" + CRLF)} else {buf.WriteString("$" + strconv.Itoa(len(arg)) + CRLF + string(arg) + CRLF)}}return buf.Bytes()
}func MakeMultiBulkReply(args [][]byte) *MultiBulkReply {return &MultiBulkReply{Args: args,}
}type StatusReply struct {Status string
}func MakeStatusReply(status string) *StatusReply {return &StatusReply{Status: status,}
}func (r *StatusReply) ToBytes() []byte {return []byte("+" + r.Status + CRLF)
}type IntReply struct {Code int64
}func MakeIntReply(code int64) *IntReply {return &IntReply{Code: code,}
}func (r *IntReply) ToBytes() []byte {return []byte(":" + strconv.FormatInt(r.Code, 10) + CRLF)
}type StandardErrReply struct {Status string
}func (r *StandardErrReply) ToBytes() []byte {return []byte("-" + r.Status + CRLF)
}func (r *StandardErrReply) Error() string {return r.Status
}func MakeErrReply(status string) *StandardErrReply {return &StandardErrReply{Status: status,}
}func IsErrorReply(reply resp.Reply) bool {return reply.ToBytes()[0] == '-'
}

BulkReply:回复一个字符串
MultiBulkReply:回复字符串数组
StatusReply:状态回复
IntReply:数字回复
StandardErrReply:标准错误回复
IsErrorReply:判断是否为错误回复
ToBytes:将字符串转成RESP协议规定的格式


resp/parser/parser.go

type Payload struct {Data resp.ReplyErr  error
}type readState struct {readingMultiLine  bool     expectedArgsCount int     msgType           byte    args              [][]byte bulkLen           int64    
}func (s *readState) finished() bool {return s.expectedArgsCount > 0 && len(s.args) == s.expectedArgsCount
}func ParseStream(reader io.Reader) <-chan *Payload {ch := make(chan *Payload)go parse0(reader, ch)return ch
}func parse0(reader io.Reader, ch chan<- *Payload) {......
}

Payload结构体:客服端给我们发的数据

  • Reply:客户端与服务端互相发的数据都称为Reply

readState结构体:

  • readingMultiLine:解析单行还是多行数据
  • expectedArgsCount:应该读取的参数个数
  • msgType:消息类型
  • args:消息内容
  • bulkLen:数据长度

finished方法:判断解析是否完成
ParseStream方法:异步解析数据后放入管道,返回管道数据

func readLine(bufReader *bufio.Reader, state *readState) ([]byte, bool, error) {var msg []bytevar err errorif state.bulkLen == 0 {msg, err = bufReader.ReadBytes('\n')if err != nil {return nil, true, err}if len(msg) == 0 || msg[len(msg)-2] != '\r' {return nil, false, errors.New("protocol error: " + string(msg))}} else {msg = make([]byte, state.bulkLen+2)_, err = io.ReadFull(bufReader, msg)if err != nil {return nil, true, err}if len(msg) == 0 || msg[len(msg)-2] != '\r' || msg[len(msg)-1] != '\n' {return nil, false, errors.New("protocol error: " + string(msg))}state.bulkLen = 0}return msg, false, nil
}

readLine:一行一行的读取。读正常的行,以\n分隔。读正文中包含\r\n字符的行时,state.bulkLen加上换行符\r\n(state.bulkLen+2)

func parseMultiBulkHeader(msg []byte, state *readState) error {var err errorvar expectedLine uint64expectedLine, err = strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)if err != nil {return errors.New("protocol error: " + string(msg))}if expectedLine == 0 {state.expectedArgsCount = 0return nil} else if expectedLine > 0 {state.msgType = msg[0]state.readingMultiLine = truestate.expectedArgsCount = int(expectedLine)state.args = make([][]byte, 0, expectedLine)return nil} else {return errors.New("protocol error: " + string(msg))}
}func parseBulkHeader(msg []byte, state *readState) error {var err errorstate.bulkLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 64)if err != nil {return errors.New("protocol error: " + string(msg))}if state.bulkLen == -1 { // null bulkreturn nil} else if state.bulkLen > 0 {state.msgType = msg[0]state.readingMultiLine = truestate.expectedArgsCount = 1state.args = make([][]byte, 0, 1)return nil} else {return errors.New("protocol error: " + string(msg))}
}

parseMultiBulkHeader:解析数组的头部,设置期望的行数和相关参数。
parseBulkHeader:解析多行字符串的头部。

func parseSingleLineReply(msg []byte) (resp.Reply, error) {str := strings.TrimSuffix(string(msg), "\r\n")var result resp.Replyswitch msg[0] {case '+': // status replyresult = reply.MakeStatusReply(str[1:])case '-': // err replyresult = reply.MakeErrReply(str[1:])case ':': // int replyval, err := strconv.ParseInt(str[1:], 10, 64)if err != nil {return nil, errors.New("protocol error: " + string(msg))}result = reply.MakeIntReply(val)}return result, nil
}func readBody(msg []byte, state *readState) error {line := msg[0 : len(msg)-2]var err errorif line[0] == '$' {// bulk replystate.bulkLen, err = strconv.ParseInt(string(line[1:]), 10, 64)if err != nil {return errors.New("protocol error: " + string(msg))}if state.bulkLen <= 0 { // null bulk in multi bulksstate.args = append(state.args, []byte{})state.bulkLen = 0}} else {state.args = append(state.args, line)}return nil
}

parseSingleLineReply:解析单行命令
readBody:读取多行的命令,如果是开头,设置bulkLen,读取下一行时根据这个+2,不是开头,设置bulkLen,读取下一行时根据这个+2,不是开头,设置bulkLen,读取下一行时根据这个+2,不是开头则直接添加到args

func parse0(reader io.Reader, ch chan<- *Payload) {defer func() {if err := recover(); err != nil {logger.Error(string(debug.Stack()))}}()bufReader := bufio.NewReader(reader)var state readStatevar err errorvar msg []bytefor {var ioErr boolmsg, ioErr, err = readLine(bufReader, &state)if err != nil {if ioErr {ch <- &Payload{Err: err,}close(ch)return}ch <- &Payload{Err: err,}state = readState{}continue}if !state.readingMultiLine {if msg[0] == '*' {// multi bulk replyerr = parseMultiBulkHeader(msg, &state)if err != nil {ch <- &Payload{Err: errors.New("protocol error: " + string(msg)),}state = readState{}continue}if state.expectedArgsCount == 0 {ch <- &Payload{Data: &reply.EmptyMultiBulkReply{},}state = readState{}continue}} else if msg[0] == '$' { // bulk replyerr = parseBulkHeader(msg, &state)if err != nil {ch <- &Payload{Err: errors.New("protocol error: " + string(msg)),}state = readState{} // reset statecontinue}if state.bulkLen == -1 { // null bulk replych <- &Payload{Data: &reply.NullBulkReply{},}state = readState{} // reset statecontinue}} else {// single line replyresult, err := parseSingleLineReply(msg)ch <- &Payload{Data: result,Err:  err,}state = readState{} // reset statecontinue}} else {// read bulk replyerr = readBody(msg, &state)if err != nil {ch <- &Payload{Err: errors.New("protocol error: " + string(msg)),}state = readState{} // reset statecontinue}// if sending finishedif state.finished() {var result resp.Replyif state.msgType == '*' {result = reply.MakeMultiBulkReply(state.args)} else if state.msgType == '$' {result = reply.MakeBulkReply(state.args[0])}ch <- &Payload{Data: result,Err:  err,}state = readState{}}}}
}

parse0:解析指令,解析完成后通过channel发出去


resp/connection/conn.go

type Connection struct {conn net.ConnwaitingReply wait.Waitmu sync.Mutex // 避免多个协程往客户端中写selectedDB int
}func NewConn(conn net.Conn) *Connection {return &Connection{conn: conn,}
}func (c *Connection) RemoteAddr() net.Addr {return c.conn.RemoteAddr()
}func (c *Connection) Close() error {c.waitingReply.WaitWithTimeout(10 * time.Second)_ = c.conn.Close()return nil
}func (c *Connection) Write(b []byte) error {if len(b) == 0 {return nil}c.mu.Lock()c.waitingReply.Add(1)defer func() {c.waitingReply.Done()c.mu.Unlock()}()_, err := c.conn.Write(b)return err
}func (c *Connection) GetDBIndex() int {return c.selectedDB
}func (c *Connection) SelectDB(dbNum int) {c.selectedDB = dbNum
}

之前写的EchoHandler是用户传过来什么,我们传回去什么。现在要写一个RespHandler来代替EchoHandler,让解析器来解析。RespHandler中要有一个管理客户端连接的结构体Connection。
Connection:客户端连接,在协议层的handler中会用到


resp/handler/handler.go

var (unknownErrReplyBytes = []byte("-ERR unknown\r\n")
)type RespHandler struct {activeConn sync.Mapdb         databaseface.Databaseclosing    atomic.Boolean
}func MakeHandler() *RespHandler {var db databaseface.Databasedb = database.NewEchoDatabase()return &RespHandler{db: db,}
}func (h *RespHandler) closeClient(client *connection.Connection) {_ = client.Close()h.db.AfterClientClose(client)h.activeConn.Delete(client)
}func (h *RespHandler) Handle(ctx context.Context, conn net.Conn) {if h.closing.Get() {// closing handler refuse new connection_ = conn.Close()}client := connection.NewConn(conn)h.activeConn.Store(client, 1)ch := parser.ParseStream(conn)for payload := range ch {if payload.Err != nil {if payload.Err == io.EOF ||payload.Err == io.ErrUnexpectedEOF ||strings.Contains(payload.Err.Error(), "use of closed network connection") {// connection closedh.closeClient(client)logger.Info("connection closed: " + client.RemoteAddr().String())return}// protocol errerrReply := reply.MakeErrReply(payload.Err.Error())err := client.Write(errReply.ToBytes())if err != nil {h.closeClient(client)logger.Info("connection closed: " + client.RemoteAddr().String())return}continue}if payload.Data == nil {logger.Error("empty payload")continue}r, ok := payload.Data.(*reply.MultiBulkReply)if !ok {logger.Error("require multi bulk reply")continue}result := h.db.Exec(client, r.Args)if result != nil {_ = client.Write(result.ToBytes())} else {_ = client.Write(unknownErrReplyBytes)}}
}func (h *RespHandler) Close() error {logger.Info("handler shutting down...")h.closing.Set(true)// TODO: concurrent waith.activeConn.Range(func(key interface{}, val interface{}) bool {client := key.(*connection.Connection)_ = client.Close()return true})h.db.Close()return nil
}

RespHandler:和之前的echo类似,加了核心层的db.exec执行解析的指令


interface/database/database.go

type CmdLine = [][]bytetype Database interface {Exec(client resp.Connection, args [][]byte) resp.ReplyAfterClientClose(c resp.Connection)Close()
}type DataEntity struct {Data interface{}
}

Exec:核心层的执行
AfterClientClose:关闭之后的善后方法
CmdLine:二维字节数组的指令别名
DataEntity:表示Redis的数据,包括string, list, set等等

database/echo_database.go

type EchoDatabase struct {
}func NewEchoDatabase() *EchoDatabase {return &EchoDatabase{}
}func (e EchoDatabase) Exec(client resp.Connection, args [][]byte) resp.Reply {return reply.MakeMultiBulkReply(args)
}func (e EchoDatabase) AfterClientClose(c resp.Connection) {logger.Info("EchoDatabase AfterClientClose")
}func (e EchoDatabase) Close() {logger.Info("EchoDatabase Close")
}

echo_database:测试协议层
Exec:指令解析后,再使用MakeMultiBulkReply包装一下返回去


main.go

err := tcp.ListenAndServeWithSignal(&tcp.Config{Address: fmt.Sprintf("%s:%d",config.Properties.Bind,config.Properties.Port),},handler.MakeHandler())
if err != nil {logger.Error(err)
}

main改成刚才写的:handler.MakeHandler()

相关文章:

GO实现Redis:GO实现Redis协议解析器(2)

本文实现Redis的协议层&#xff0c;协议层负责解析指令&#xff0c;然后将指令交给核心database执行echo database用来测试协议层的代码https://github.com/csgopher/go-redis RESP协议 RESP是客户端与服务端通信的协议&#xff0c;格式有五种&#xff1a;正常回复&#xff1…...

Geoserver 发布wmts服务,以及cesium加载发布的wmts服务

WMTS提供了一种采用预定义图块方法发布数字地图服务的标准化解决方案。WMTS弥补了WMS不能提供分块地图的不足。WMS针对提供可定制地图的服务&#xff0c;是一个动态数据或用户定制地图&#xff08;需结合SLD标准&#xff09;的理想解决办法。WMTS牺牲了提供定制地图的灵活性&am…...

【微信小程序】selectComponent(#id)失败得到是null分析

小程序中无法像网页中轻易的获取DOM元素&#xff0c;需要依靠 this.selectComponent(#id)this.selectAllComponents(#id) 本文主要针对 this.selectComponent 获取DOM元素失败的原因 下面开始正文 上图为我的业务代码&#xff0c;由图可知&#xff0c;通过for循环遍历渲染ca…...

JVM中引用计数法与可达性分析

目录 概要 如何判断对象已死&#xff1f; 引用计数算法 优点 缺点 举例说明 可达性分析 图例说明 GC Roots的对象包括以下几种 可达性分析回收过程 四大引用 回收方法区 方法区的垃圾收集主要回收两部分内容&#xff1a; 1. 废弃的常量 2. 不再使用的类型。 JVM是…...

JS-对象篇

内容 简单介绍 重点介绍三个 Array,String和JSON 后面这两个不是重点 BOM-浏览器对象模型 DOM-文档对象模式&#xff08;JS中每个HTML标签都封装成一个DOM对象&#xff09; Array 和java不同 方式一 JS中是var 变量 new Array()&#xff08;这个变量名后面没有[]这个标记&…...

【Unity】创建一个自己的AR安卓程序

目录1 环境配置2 下载官方提供的AR Starter工程3 AR Starter工程中的包以及打包设置3.1 Package Manager3.2 Player Settings4 创建一个新的AR场景5 AR场景中的物体6 在unity中运行AR场景7 在AR场景的基础上添加自己的想法7.1 修改Cube的旋转速度/方向7.2 将Cube替换为其他物体…...

游戏平台商店化的功能特点

帮助用户高效的获取游戏以及游戏相关内容是游戏平台的核心&#xff0c;基于这个需求在平台功能的设计上与其他类型产品也有着类似的思路。商店模式的特点诸如百货商店、超市、书店以及其他类型的商店&#xff0c;都会根据推荐、分类两个特点提供商品。 如果把游戏比作书籍&…...

最新前端面试知识点总结-2023(3w+字,长篇幅)

2023-前端面试知识点总结面试题总览javascript相关一、js 代码的常用优化手段二、es5 构造函数与继承三、new 一个对象的过程四、防抖与节流五、promise/A规范概述六、实现一个柯里函数封装七、事件队列八、微任务是哪些宏任务是哪些九、执行js代码时&#xff0c;同步任务、微任…...

离线安装ffmpeg

linux离线安装ffmpeg 获取安装包&#xff1a;[ffmpeg-release](Index of /releases (ffmpeg.org)) 下载最新版本&#xff0c;ffmpeg-4.4.tar.gz 然后传送到服务器上&#xff0c;解压安装 # 解压 tar -zxvf ffmpeg-4.4.tar.gz# 安装 cd ffmpeg-4.4 ./configure --enable-sha…...

位置编码Positional Encoding

位置编码Positional Encoding1.Transformers中的PE2.什么是Transformer位置编码2.1.表格型2.2.相对位置的关系-函数型3.为什么可以表示相对距离&#xff1f;4.其他参考内容全来自于网络总结。 其他参考1其他参考2 1.Transformers中的PE 摘抄自这里。 公式是初中生都看的懂, …...

Java异步注解@Async详解

一、Async注解 Async的作用就是异步处理任务。 在方法上添加Async&#xff0c;表示此方法是异步方法&#xff1b;在类上添加Async&#xff0c;表示类中的所有方法都是异步方法&#xff1b;使用此注解的类&#xff0c;必须是Spring管理的类&#xff1b;需要在启动类或配置类中…...

macOS Big Sur 11.7.5 (20G1225) 正式版 ISO、PKG、DMG、IPSW 下载

本站提供的 macOS Big Sur 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。 2023 年 3 月 27 日 &#xff08;北京时间 28 日凌晨&#xff09;&#xff0c;…...

硬件语言Verilog HDL牛客刷题day02 组合逻辑部分

1.VL11 4位数值比较器电路 1.题目&#xff1a; 某4位数值比较器的功能表如下。请用Verilog语言采用门级描述方式&#xff0c;实现此4位数值比较器。 2.解题代码&#xff1a; timescale 1ns/1nsmodule comparator_4(input [3:0] A ,input [3:0] B ,output …...

【LM401】ADC采集代码解读

本文主要实现基于LM401模组,&#xff0c;测试ADC低功耗采集&#xff0c;详细解析代码基于计算方式 对于小白理解ADC有更详细的理解 【LM401】ADC采集代码解读1. 单片机ADC与DAC简单理解2. 模组ADC通道介绍3. ADC初始化4. 采集值的计算5.测试结果硬件基于易智联的LM401的LoRa模组…...

CSDN 编程竞赛四十期题解

竞赛总览 CSDN 编程竞赛四十期&#xff1a;比赛详情 (csdn.net) 竞赛题解 题目1、小鱼的航程 有一只小鱼&#xff0c;它上午游泳150公里&#xff0c;下午游泳100公里&#xff0c;晚上和周末都休息&#xff08;实行双休日)。假设从周x&#xff08;1<x<7&#xff09;开…...

【TypeScript学习之路】泛型

【TypeScript学习之路】泛型 文章目录【TypeScript学习之路】泛型写在前面前言一、认识泛型1.1 什么是泛型1.2 泛型函数的使用二、泛型接口与泛型类2.1 泛型接口2.2 泛型类三、泛型约束写在前面 &#x1f917;这里是前端程序员小张&#xff01; &#x1f33b;人海茫茫&#xff…...

数据分析学习项目:东京奥运会跳水评论分析

“中国跳水梦之队” ————有关东京奥运会跳水评论分析 导语 第32届夏季奥林匹克运动会于2021年07月23日-2021年08月08日在日本东京举办。 四年一届的奥运会可以说是世界瞩目的盛会&#xff0c;奥运健儿们在赛场上的精神风貌不只是代表了他们自身的运动精神&#xff0c;更昭…...

Winform/Csharp中使用Linq的Where条件筛选、Select字段映射(左外连接并设置无匹配时默认值)、OrderBy(排序并自定义排序规则)

场景 Java8新特性-Stream对集合进行操作的常用API&#xff1a; Java8新特性-Stream对集合进行操作的常用API_streamapi操作集合_霸道流氓气质的博客-CSDN博客 上面讲的是在Java中使用Stream中对集合的常用操作。 在C#中Linq是有对应的类似的api。 完整和详细的用法可自行查…...

Linux-常用的Shell命令

文章目录前言常用的Shell命令文件和目录管理查看文件、目录信息查看文件内容查看文件类型查找文件查找内容查看目录大小创建文件删除文件拷贝文件移动文件创建目录删除目录拷贝目录压缩文件解压文件路径相关操作目录切换显示当前路径用户、用户组管理创建用户删除用户创建用户组…...

Go语言基础:数组定义及循环遍历

前言 大家好&#xff0c;我是沐风晓月&#xff0c;本文go语言入门-掌握go语言函数收录于《go语言学习专栏》专栏&#xff0c;此专栏带你从零开始学习go语言&#xff0c;持续更新中&#xff0c;欢迎点赞收藏。 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &#x1f9d1;个人…...

【树与二叉树】二叉树顺序结构实现以及堆的概念及结构--详解介绍

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录1. 二叉树顺序结构2.…...

天狗实战(二)SpringBoot API开发详解 --SpringMVC注解+封装结果+支持跨域+打包(下)

本文目录 前言专栏介绍一、创建SpringBoot项目1.1 添加springboot依赖1.2 创建启动类1.3 创建控制器类1.4 Run 或 Debug二、开发图书管理API2.1 web层BookAdminControllerBookVO2.2 service层BookServiceBookServiceImplBookBO2.3 dal层...

实验一 Windows系统安全实验【网络安全】

实验一 Windows系统安全实验【网络安全】前言推荐实验一 Windows系统安全实验3.1 帐户和口令的安全设置3.1.1 实验目的3.1.2 实验环境3.1.3 实验内容和步骤1. 删除不再使用的帐户并禁用guest帐户2.启用密码策略和帐户锁定策略3.查看“用户权限分配”4.查看“用户组权限分配”5.…...

蓝桥杯正确的解题姿势

在做算法题的过程中最忌讳的就是上来就一顿乱敲&#xff0c;一开始我就是这样&#xff0c;但随着不断的刷题和老师的指导&#xff0c;总结了自己的刷题方法 示例题目 三角回文数 问题描述 对于正整数 n, 如果存在正整数 k使得 n123...kk(k1)/2 , 则 n 称为三角数。例如, 66066 …...

【mysql】性能优化

目录一、硬件与操作系统二、架构设计层面的优化三、mysql程序配置优化四、mysql执行优化一、硬件与操作系统 1.使用高性能cpu&#xff0c;提高计算能力 2.增大可用内存&#xff0c;提高读取能力 3.提高硬盘的读写速度&#xff0c;使用专用的固态硬盘 4.增大网络带宽&#xff0c…...

Jupyter安装与远程使用过程记录

Jupyter安装与远程使用过程记录 文章目录Jupyter安装与远程使用过程记录Jupyter在线试用在服务器上安装Jupyter Notebook配置服务器远程连接首先保证ip地址连通性其次开启服务器访问端口然后在服务器启动服务最后测试连通性后续使用教程Jupyter在线试用 官网适用&#xff0c;感…...

Swift入门

基本数据类型 Int、UInt&#xff1a;整数型、非负整数Float、Double&#xff1a;单精度浮点数、双精度浮点数Bool&#xff1a;布偶值String、Character&#xff1a;字符串、字符 其他类型 Array, Dictionary&#xff1a;数组、字典StructClassvar&#xff1a;变量let&#x…...

【HashMap】jdk1.8中HashMap的插入扩容源码学习分析

jdk1.8中HashMap的插入扩容源码学习分析 一、成员变量 首先介绍HashMap中各个成员变量的作用&#xff0c;在HashMap中有以下成员变量 size记录了HashMap中键值对的个数 loadFactor&#xff08;加载因子&#xff09;用来决定size达到容量的百分之多少时触发扩容机制 默认是0…...

Linux编译器-gcc/g++ 使用

在介绍gcc/g的使用前我们先了解一下两者的不同 gcc时主要编译c语言&#xff0c;而g主要编译c的&#xff0c;但是两者的选项是相同的&#xff0c;因此我们以gcc和c语言为例来讲解。背景知识 gcc和g都是编译器其核心作用将文本类文件翻译成二进制可执行 那么其过程是怎样的&…...

网络安全专家最爱用的9大工具

网络安全专家&#xff0c;不是你认为的那种搞破坏的 “黑客”。网络安全专家&#xff0c;即 “ethical hackers”&#xff0c;是一群专门模拟网络安全专家攻击&#xff0c;帮助客户了解自己网络的弱点&#xff0c;并为客户提出改进建议的网络安全专家。 网络安全专家在工作中&a…...

php网站开发最低配置/南京seo网站优化

switch_to.frame() 切换frame switch_to.default_content() 切换到主页面 #从frame中切回主文档 switch_to.parent_frame() 这是switch_to中独有的方法&#xff0c;可以切换到上一层的frame&#xff0c;对于层…...

有限公司属于什么企业类型/中山口碑seo推广

上一篇中已经讲到了如何安装单击版Redis&#xff0c;这一篇我们来说下如何安装Cluster&#xff0c;关于哨兵模式这里我就不写文章安装了&#xff0c;有兴趣的同学可以自己去研究&#xff0c;哨兵模式可以在主从模式下在创建三台机器的哨兵集群监控redis主从集群即可。 本文由于…...

天津网站制作南昌/视频网站搭建

Django多进程日志文件问题 最近使用Django做一个项目。在部署的时候发现日志文件不能滚动&#xff08;我使用的是RotatingFileHandler&#xff09;&#xff0c;只有一个日志文件。 查看Log发现一个错误消息&#xff1a;PermissionError: [WinError 32] 另一个程序正在使用此文件…...

wordpress 去广告/找资源最好的是哪个软件

上代码 //查询某一个地区//参数1 地区树结构//参数2 地区名称或者地区idprivate List<DvJkglMemberCityDistributionVo> getDvJkglMemberCityDistributionVo(List<DvJkglMemberCityDistributionVo> dvJkglMemberCityDistributionVos,String areaName) {List<Dv…...

客户资源网/seo刷关键词排名工具

这个话题一般情况下&#xff0c;我不可能明说出来&#xff0c;甚至更不可能发出来。因为有以下的一些原因&#xff1a; 1. 如何包装&#xff0c;只代表个人观点&#xff0c;毕竟一个面试官一个心态和标准。没人敢打包票说这是包装圣经。 2.分层不同&#xff0c;负责筛选的hr&…...

济南建设网站公司/网站功能

闭包 闭包有点类似于匿名函数&#xff0c;写法如下 let expensive_closure |num| {println!("calculating slowly...");thread::sleep(Duration::from_secs(2));num }; expensive_closure(intensity)类型推断和标注 闭包不要求像 fn 函数那样在参数和返回值上注明…...