头像

星火燎原_6




离线:2天前


最近来访(31)
用户头像
强强_1
用户头像
jupyter
用户头像
Zh0uKal1
用户头像
是WxZz呀
用户头像
clearmann
用户头像
用户头像
微雨双飞燕
用户头像
Accepter
用户头像
yxc的小迷妹
用户头像
我要出去乱说
用户头像
cbatea
用户头像
AAAL
用户头像
Life_0
用户头像
dhxdl6666
用户头像
晨钟
用户头像
多多_0
用户头像
tuffynibbles
用户头像
雨耀
用户头像
抽风少年
用户头像
向前看丶


一、基础语法

1、Hello World

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World!")
}

2、变量

package main

import (
    "fmt"
    "math"
)

func main() {
    // Go语言里面声明变量有两种方式:
    // 方式一:var 变量名 (数据类型)可以自动推断数据类型
    var a = "initial"

    var b, c int = 1, 2

    var d = true

    var e float64

    // 方式二:用冒等号
    f := float32(e)

    g := a + "foo" //Go语言里面字符串是内置类型,所以直接用加号连接
    fmt.Println(a, b, c, d, e, f)
    fmt.Println(g)

    // 声明常量的话就是把var改成const即可
    // 值得一提的是,golang里面的常量没有确定的类型,它是根据上下文来自动确定类型
    const s string = "constant"
    const h = 50000000
    const i = 3e20 / h
    fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

3、if - else

package main

import "fmt"

func main() {
    /*
        1、if后不用括号,就算加了括号,编辑器也会自动给它去掉
        2、if后的大括号要紧跟其后
    */
    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }
}

4、循环

package main

import "fmt"

func main() {
    // golang里面的循环只有for循环,没有while和do while
    i := 1
    // 死循环
    for {
        fmt.Println("loop")
        break
    }

    for j := 1; j <= 9; j++ {
        fmt.Println(j)
    }

    for i <= 3 {
        fmt.Println(i)
        i = i + 1
    }
}

5、switch

package main

import (
    "fmt"
    "time"
)

func main() {
    a := 2
    /*
        switch 后面的变量不用加括号
        golang里面的switch不用加break,执行完某一个case就自动跳出
        相比于C/C++,golang里面的switch更加强大,可以使用任意变量类型,比如:字符串、结构体等。甚至可以用来取代任意的if-else语句
    */
    switch a {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 3:
        fmt.Println("three")
    case 4, 5:
        fmt.Println("fout or five")
    default:
        fmt.Println("other")
    }

    t := time.Now()
    /*
        可以不用书写变量,直接在case里面书写条件分支,这样的代码,会比多个if-else语句嵌套更加清晰易懂
    */
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }
}

6、数组

package main

import "fmt"

func main() {
    // 实际场景中很少用数组,因为它的长度是固定的
    var a [5]int
    a[4] = 100
    fmt.Println(a[4], len(a))

    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(b)

    var twoD [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}



活动打卡代码 LeetCode 400. 第N个数字

func findNthDigit(n int) int {
    i, num, base := 1, 9, 1      // i 表示位数,num表示i这个位数的数一共有多少个,base表示i位数的第一个数是什么 

    for n - num > 0 {   // 判断n是否当前剩余的值大于i这个位数的数,循环过后得到的n表示i位数拆分后的第几个数字
        n -= num
        i ++
        base *= 10
        num = i * base * 9
    }

    var mod = 0 // mod表示取模后的余数,如果是余数是0的话,mod就用i来表示
    var tmp = 0 // tmp表示base后面的第几个数,即完整的i位数的第几个数

    // 向上取整 + 求余数
    if n % i == 0 {
        tmp = n / i
        mod = i
    } else {
        tmp = n / i + 1
        mod = n % i
    }

    number := base - 1 + tmp     // 找到对应的是哪个完整的数

    for j := 0; j < i - mod; j++ {  // i - mod 表示要删除多少次,才能找到余数对应位置的数字
        number /= 10 
    }       // 找到对应的是这个完整的数的哪一位数字

    return number % 10 // 转成整型
}



截屏2022-12-26 18.44.34.png

// 题目中n是可以为0的,n=0的时候,表示第0位,对应的数字为0
// 可以不考虑0,这样让一位数、两位数、三位数……的数量分别是9、90、900、……看起来整齐一些,并且可以让n=1对应第1位,对应数字为1,n=2的时候对应第2位,对应数字为2……
// 如果考虑0的话,那么数量分别是10、90、900、……

class Solution {
public:
    int digitAtIndex(int n) {
        long long i = 1, num = 9, base = 1;     // i枚举的是位数,s表示这一位一共有多少个数,base表示这一位的第一个数是什么
                                                // 一位数的base就是1,两位数的base就是10,三位数的base就是100,四位数的base就是1000……
                                                // 为什么用long long呢?因为num每次乘10可能比较大,可能会爆int
        while (n > i * num) {   // n当前剩余的位数大于i位数字所有数字加起来的总位数
            n -= i * num;
            i ++;
            num *= 10;
            base *= 10;
        }

        long long tmp = 0;
        if(n%i == 0) {
            tmp = n/i;
        } else {
            tmp = n/i + 1;
        }

        //int number = base + (n + i -1) / i - 1;    // number表示第n位是属于哪个数,(n+i-1)/i是用向下取整代替向上取整,最后减1,是因为三位数里面的第n个数是从99开始数起的
        int number = base + tmp - 1; 
        // r是表示n是代表number这个数的第几位
        int r = n % i ? n % i : i;  // 用一个问号表达式,如果不是0的话,就是n%i,如果是0的话,就说明是这个数的第i位,刚好整除完,
        for (int j = 0; j < i - r; j ++ ) number /= 10;     // 比如17782要求它的第2位,就可以采用将后面三个数字删除,然后模10的方法,得到7
        return number % 10;
    }
};

Go版本

func findNthDigit(n int) int {
    i, num, base := 1, 9, 1      // i 表示位数,num表示i这个位数的数一共有多少个,base表示i位数的第一个数是什么 

    for n - num > 0 {   // 判断n是否当前剩余的值大于i这个位数的数,循环过后得到的n表示i位数拆分后的第几个数字
        n -= num
        i ++
        base *= 10
        num = i * base * 9
    }

    tmp := base + (n-1) / i     // 找到对应的是哪个完整的数
    res := strconv.Itoa(tmp)[(n - 1) % i] - '0'     // 找到对应的是这个完整的数的哪一位数字

    return int(res) // 转成整型
}

func findNthDigit(n int) int {
    i, num, base := 1, 9, 1      // i 表示位数,num表示i这个位数的数一共有多少个,base表示i位数的第一个数是什么 

    for n - num > 0 {   // 判断n是否当前剩余的值大于i这个位数的数,循环过后得到的n表示i位数拆分后的第几个数字
        n -= num
        i ++
        base *= 10
        num = i * base * 9
    }

    var mod = 0 // mod表示取模后的余数,如果是余数是0的话,mod就用i来表示
    var tmp = 0 // tmp表示base后面的第几个数,即完整的i位数的第几个数

    // 向上取整 + 求余数
    if n % i == 0 {
        tmp = n / i
        mod = i
    } else {
        tmp = n / i + 1
        mod = n % i
    }

    number := base - 1 + tmp     // 找到对应的是哪个完整的数

    for j := 0; j < i - mod; j++ {  // i - mod 表示要删除多少次,才能找到余数对应位置的数字
        number /= 10 
    }       // 找到对应的是这个完整的数的哪一位数字

    return number % 10 // 转成整型
}


活动打卡代码 AcWing 53. 最小的k个数

/
大根堆
堆顶元素是所有数里面的最小值,堆里面就放k个元素
遍历所有元素的时候,如果发现有一个元素比堆顶元素还要小,那么就将其替换出来
/

class Solution {
public:
    vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
      priority_queue<int> heap;
        for (auto x : input)
        {
            if (heap.size() < k || heap.top() > x) heap.push(x);
            if (heap.size() > k) heap.pop();
        }
        vector<int> res;
        while (heap.size()) res.push_back(heap.top()), heap.pop();
        reverse(res.begin(), res.end());
        return res;
    }
};



12月17日

Go语言学习

1、【一文Go起来】快速上手篇
2、 IDE:GoLand的安装,破解版
3、 golang安装
4、运行第一个go程序
5、 彻底搞懂golang的GOROOT和GOPATH
6、 Go语言中 Print,Println 和 Printf 的区别
7、 Go 语言切片(Slice)
8、 Golang 内置函数:make()
9、 golang make的使用
10、 Go语言的格式化输出中%d%T%v%b等的含义
11、 Go中的格式化打印:“%+v”和“%v”的区别
12、 %v %+v %#v的区别
13、 Golang之nil的妙用
14、Golang中的nil,没有人比我更懂nil!
15、 go语言,ok和_,ok模式
16、 关于逗号ok模式
17、 go语言中fallthrough用法
18、 编译型语言、解释型语言、静态类型语言、动态类型语言概念与区别

笔记

Go是一种静态、强类型、编译型、并发型的编程语言,它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性,是开发者平常常用的语言之一,也是云原生开发的通用语言。

感悟

其实,还真不是学习语法就好了呢,其中包含了很多 Go 的设计理念。
正所调好记性不如敲烂键盘,学过的东西,还是要沉淀沉淀,也可以分享出来一起探讨,更有助于成长,于是我就简单记录了一下我的 Go 语吉学习笔记。

12月18日

asyncflow项目

1、开会,听项目的报告分析

笔记:

整个项目是为了解决什么问题?
考虑到在多场景的情况下会有很多相同的操作,我们可以把“ 任务的调度、异常的处理、数据的存储、流量控制”等这部分共性的东西抽象出来,做成通用化组件化的框架,也就是做一个通用的任务异步处理框架,节省开发时间,提高开发效率。

12月19日

asyncflow项目

1、 姜睿的方案设计
2、 生产者消费者模型
3、 并发、并行、异步、同步(可以看看评论区)
4、 并发和异步从概念上理解有什么区别?
5、 多线程和并发
6、 Kafka 百度百科
7、 小朋友也可以懂的Kafka入门教程
8、 认识gin

笔记:

1、什么是broker?broker是指一个独立的Kafka服务器。
2、客户端发送出来任务可以看成是生产者生成出来任务,框架就相当于消费者,去处理这些任务。生产者和消费者通过内存缓冲区去通讯。
3、go语言设计的时候没有一定的设计标准,没有规定一定是面向对象或者一定是面向过程,它有自己的一套规则,代码既可以写成面向对象的,也可以写成面向过程的,甚至可以是go风格的代码,所以比较灵活,因此就诞生了众多不同类型的框架,各具特色,满足不同开发人员的喜好。
4、go的框架其实是可以理解为库,并不是说用了某一个框架就不能用别的框架,go允许可以选择性的使用各个库中的优秀组件,进行组合。因为每个框架都不可能是十全十美的,都会有各自的优点特色,而go语言拥有的这种集成的特性对于我们解决问题是非常有帮助的。
5、broker是由服务(用gin得到的web服务)+ 存储组成。存储做成通用接口性的,而要想访问这个接口,就需要gin起一个web服务来作为入口,对外是透明的。
6、Worker的作用是拿任务来做,也就是去拿取待执行的任务,然后去处理。

12月20日

Go语言学习

1、 Golang的设计思想就是用通信代替共享内存
2、 channel有缓冲与无缓冲同步问题
3、 面试高频问题:有缓冲和无缓冲通道的区别?
4、 侵入式和非侵入式的区别
5、 线程和协程的区别的通俗说明

asyncflow项目

1、 简单理解消息队列

笔记

1、需要有一个任务治理的服务,在例如线程挂掉了,长期得不到更新的时候,可以派上用场。这个服务可以写在broker的服务部分,共享一个服务,因为broker本身做的事情不多,当然也可以抽出来,抽出来的话就会更加解耦的。
2、更新任务的结果到Backend,最后client去获取。
3、存储的话,一般是全部放在broker里面,因为它要做一个任务的管理,放到broker里面,可以实时查到任务的状态。
4、状态机没有考虑到。任务到底有几种状态,这其实是很重要的。
5、有三个worker同时从broker取一个东西,那么如何处理竞争关系呢?(多机竞争)可以用分布式锁,因为任务的调度需要不少时间,所以通常来说是状态更新之后,就可以释放锁了,不用专门等到任务完成、任务超时、任务异常。比如一开始worker都是待执行状态的,拿到任务之后是执行中状态,更新成功之后就马上释放锁,这样锁的力度也不会太大,锁的力度能小就小,不然会很慢,会成为一个瓶颈的,减少锁占用的时间也是一种优化方式。分布式锁是一个最常规的,很多时候也是最行之有效的解决方案。

课外扩展

1、B站的直播架构演进之路分享

笔记

1、微服务可以理解为是一种设计思想,就是把系统里面的业务全部拆开,变成一个个系统,需要用到的时候我们再去访问它,比如现在有一个很热门的主播,大家疯狂给它刷弹幕,打挂了弹幕系统,但是只要礼物系统还没有挂,就丝毫不会影响给主播刷礼物。而且业务拆分之后,单服务的扩展也十分的方便了,大家继续给他刷礼物,快刷爆礼物系统了,我们可以临时去服务器的厂商那边租一些服务器,把礼物系统的代码部署上去,然后重新上线,去分摊一下礼物系统的流量,等主播下播之后,流量下去了,我们再把服务器还回去,流量的压力得到了缓解,钱一分是没感知的。

12月21日

asyncflow项目

1、 什么是任务调度?什么是分布式任务调度?
2、 【分布式存储】与【传统存储】的区别是什么?
3、 一分钟让你轻松了解清楚什么是分布式存储
4、 能不能通俗的讲解下什么是状态机?

笔记

1、统一异常处理属于微服务治理的重要环节,不管公司规模大小,并发量多大,都要做。比如eBay这种大厂,它的交易量非常大,但它的框架层和业务层错误处理做得非常细致,反倒是一些中小公司常常忽略异常处理。异常处理发生时的确有一定的性能损耗,但是这个是必须的,而且生产级代码是建议尽可能细致全面的做错误处理,目前没有证据显示,细粒度的错误处理会引发性能问题。另外,很多大厂的服务框架都有统一异常处理逻辑,统一异常处理有利于规范和后续的运维。
2、某大牛的回答(关于微服务异常设计实践的问题)
我之前设计框架的时候,一般只设计两类异常,一类是框架异常(或者你说的系统异常),另一类是业务异常(业务开发自己定制扩展)。

异常种类不需要太多,但是错误码(error code)和错误消息(error message)可以按需定义。你提到的回滚操作可以根据错误码判断。
3、状态机: 多个状态和状态之间的转换组成状态机

12月22日

asyncflow项目

1、 什么是负载均衡?
2、 什么是分库分表?何时进行分库分表?

12月23日

感悟

1、主线进度优先级最高,然后是项目=算法练习>workshop

12月24日

12月25日

Go语言学习

1、 Go语言函数声明(函数定义)
2、 GO语言中的空白标识符

12月26日

算法题

1、 C++中向上取整的三种方法
2、 golang 实现 while 和 do……while 循环
3、 关于go语言的自增自减问题
4、 Go语言的三元表达式
5、 Go string函数与strconv.Itoa函数的区别
6、 C++中减去‘0’的作用( -‘0’ )

12月27日

算法题

1、 Leetcode415:字符串相加
2、 NC330:36进制加法
3、 字节高频题补充——36进制加法(推文讲解)
4、 Go语言strconv包:字符串和数值类型的相互转换

12月28日

asyncflow项目

1、快速理解:利用一致性Hash解决MySQL分库扩容难题
2、 分库分表扩容迁移方案
3、 什么是CRUD? CRUD的操作
4、 黑马程序员MySQL:主从复制概述

笔记

1、master指主库,slave指从库。master翻译为主人,slave翻译为奴隶。

12月29日

Go语言学习

1、 【Go】Panic函数
2、 slice切片的四种定义方式
3、 slice切片的len、cap,切片的追加和截取

笔记

1、panic翻译为恐慌

12月30日

Go语言学习

1、 数据和动态数组的区别
2、 map的三种定义方式

笔记

1、数组的声明方式:
① var a [10] int
② b := [3] float
动态数组(slice)的声明方式:
① slice1 = [ ] int{1, 2, 3}
② slice2 := make([ ]int, 4) 初始值全为0
2、数组的长度是固定的,动态数组可以扩容
3、数组作为形参是值拷贝,动态数组作为形参是引用传递。
4、字节切片:var c byte = [ ]byte{‘a’, ‘b’, ‘c’, ‘d’}
fmt.Println(“c = “, c)
fmt.Printf(“%T, %v”, c, c) %T表示输出类型
5、map的三种定义方式:
① var a map[string]string 这个时候没有开辟内存空间,为nil空
需要a = make(map[string]string, 10) 开辟内存空间才可以使用
② b = make(map[int]string) 后面直接赋值就可以使用了,会自动添加空间
③ c = map[string]string {
“one”: “golang”,
“two”, “C++”,
“three”, “java”,
} 记得最后也要个逗号

算法题

1、 字节切片和字符串相互转换
2、 Go语言字符类型(byte和rune)
3、 golang 没有 char类型,str[0] 类型讲解
4、 Go语言没有内置的reverse函数,需要自己实现一个 Go 语言的字符串切片反转函数

其它

1、 OBS直播出现杂音或者电流声、没声音或者调大音量等操作

1月1日

Go语言学习

1、 map的使用方式
2、 线性安全和非线性安全
3、 goroutine基本模型和调度设计策略
4、 创建goroutine
5、 自旋锁和互斥锁的区别
6、 对比介绍:互斥锁 vs 自旋锁
7、 并发控制:互斥 (自旋锁、互斥锁和 futex) [南京大学2022操作系统-P5]
8、 golang字符串拼接,字符串数组转字符串,字符串数组声明
9、 如何让slice线程安全
10、 为什么 Go map 和 slice 是非线程安全的?
11、 Golang协程详解和应用
12、 channel的基本定义与使用
13、 Golang 之协程详解
14、 Golang 入门 : 等待 goroutine 完成任务
15、 【Go面试】Go slice为什么不是线程安全的?
16、 【Go语言101】channel
17、 【Go面试】Go map和sync.Map谁的性能好,为什么?
18、 Golang实现栈和队列

笔记

1、在main函数中,如果其中有协程,如果不使用sleep,那么其中那个协程刚启动,还没来得及执行,随着main函数(主线程)结束它们就消亡了,所以要加上sleep,等待其中的协程执行完毕。
2、 slice里面是一个三基础类型组成的结构体 其中指向底层的类型用的是指针,所以在多协程的时候,指针可能指向同一个地方,往同一个地方进行读写操作,所以可能写到同一个地方出现覆盖的情况,故是非线性安全的。
3、channel中<-称为数据发送操作符。
4、synchronous 同步的

其他

1、如何使用Dev C++进行程序调试

1月2日

Go语言学习

1、 进程、线程、协程 10 张图讲明白了!理解为什么要有协程
2、 Go语言进阶:1分钟秒懂Context包!
3、 Go语言进阶知识:1分钟带你秒懂Context包核心API
4、 公开课:Go Context的极简理解(1):上下文和猪
5、 golang公开课Context快速理解(2):cancel取消方法的使用
6、 channel与select
7、 Golang中select的实现原理
8、 golang select 用法

笔记

1、select具备多路channel的监控状态功能

Redis

1、 Mac安装Redis、启动、关闭
2、 如何查看redis版本号
3、 vi编辑后不保存退出No write since last change解决方法
4、 Mac设置redis开机自动启动
5、 黑马程序员 redis安装

笔记

1、redis-server是前台启动,需要开着这个页面,这个页面会一直保持不动
要想使用redis,你需要再打开另一个界面,执行redis-cli语句后才能够开始使用
这样会显得过于麻烦,要想在一个页面就可以完成,就需要终端后台配置一下
当然,要想更方便也可以设置一下开机自启
2、开启redis客户端用redis-cli语句

1月3日

Go语言学习

1、 GO语言:channel通道
2、 Go Channel 详解

Redis

1、 Redis用sds的话,就不再以\0作为判断标准,二进制安全
2、 Redis基础数据结构String:你知道String长度限制多少吗
3、 redis的5种数据结构及其底层实现原理
4、 Redis为什么会选择44作为两种编码的分界点?在3.2版本之前为什么是39?这两个值是怎么得出来的呢?
5、 Redis的embstr与raw编码方式不再以39字节为界了!
6、 为什么redis小等于39字节的字符串是embstr编码,大于39是raw编码?
7、 redis中embstr与raw编码方式之间的界限
8、 阿里二面:Redis 为什么把简单的字符串设计成 SDS?
9、 sizeof(void*)的大小到底由何决定?
10、 Redis的字符串的底层实现SDS
11、 Redis Linsert 命令
12、 list先进先出_Redis使用初探—list
13、 【黑马程序员】Redis List类型
14、 RedisDesktopManager图形化显示软件 mac版本

算法题

1、 下列哪些排序算法不是稳定的?
2、 堆排序
3、 五分钟图解堆排序

1月4日

Redis

1、 【黑马程序员】Redis Set
2、 Redis 集合(Set)
3、 什么是负载因子
4、 Map (三) HashMap 如何利用 hash 计算存储位置
5、 Redis 哈希表中负载因子问题
6、 Redis面试题系列:讲一讲 rehash 的过程
7、 Redis详解(七)------ AOF 持久化
8、 什么是加载因子/负载因子/装载因子
9、 Skiplist跳表通俗理解
10、 Redis string embstr 编码底层原理
11、 跳表实现原理
12、 红黑树快速入门

其它
13、 top命令详解(详细)

笔记

1、扩容不是在原来的基础上扩容,内存是需要重新申请的,不是想在原来基础上申请就能得,内存是一块一块的,你一开始申请了,后面不够就要重新申请另一块。不然分开了不连续就不好搞了。

1月5日

Redis

1、 为什么用跳表而不用平衡树
2、 数据存储和NoSql概述
3、 NoSql的分类
4、 跳表:Skiplist原理介绍和优缺点
5、 红黑树的红黑有什么意义_硬核图解–字节面试必问的红黑树
6、 redis基本数据类型——⑤有序集合 zset(跳跃表+hash)
7、 为什么有序集合需要同时使用跳跃表和字典来实现?
8、

其它

1、 全网最全的一篇数据库MVCC详解,不全我负责
2、信任始于握手 — TLS 1.2 连接过程详解

笔记

1、什么是MVCC 全称Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发性能.
2、传输层安全性协议(英语:Transport Layer Security,缩写作TLS),及其前身安全套接层(Secure Sockets Layer,缩写作SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。
该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。
3、 同一行数据平时发生读写请求时,会上锁阻塞住。但mvcc用更好的方式去处理读—写请求,做到在发生读—写请求冲突时不用加锁。
4、mvcc用来解决读—写冲突的无锁并发控制。

1月6日

Redis

1、 时间戳是什么
2、 Redis精通系列——Pub/Sub(发布订阅)
3、 Redis Streams 介绍
4、 GROUP group consumer
5、

笔记:

1、因为Streams是只附加数据结构,基本的写命令,叫XADD,向指定的Stream追加一个新的条目。一个Stream条目不是简单的字符串,而是由一个或多个键值对组成的。这样一来,Stream的每一个条目就已经是结构化的,就像以CSV格式写的只附加文件一样,每一行由多个逗号割开的字段组成。
2、


> XADD mystream * sensor-id 1234 temperature 19.8
1518951480106-0


上面的例子中,调用了XADD命令往名为mystream的Stream中添加了一个条目sensor-id: 123, temperature: 19.8,使用了自动生成的条目ID,也就是命令返回的值,具体在这里是1518951480106-0。
3、提供监听到达Stream的新消息的能力的命令称为XREAD。比XRANGE要更复杂一点,
4、三种访问模式:
① 我们希望Streams可以扇形分发消息到多个客户端。
② 我们还可以使用一种完全不同的方式来看待一个Stream:不是作为一个消息传递系统,而是作为一个时间序列存储。
③最后,如果我们从消费者的角度来观察一个Stream,我们也许想要以另外一种方式来访问它,那就是,作为一个可以分区到多个处理此类消息的多个消费者的消息流,以便消费者组只能看到到达单个流的消息的子集。
5、可以使用XRANGE查询一个时间范围
6、消费者组就像一个伪消费者,从流中获取数据,实际上为多个消费者提供服务,提供某些保证。
7、XREAD的客户端实际上也是一个消费者组
客户端就是调用者,你redis-cli之后,就是一个客户端
8、一个非常重要的细节,在强制选项STREAMS之后,键mystream请求的ID是特殊的ID >。这个特殊的ID只在消费者组的上下文中有效,其意思是:消息到目前为止从未传递给其他消费者。

1月7日

Go语言学习

1、 「Golang」for range 使用方法及避坑指南
2、 Go if _,ok:=range map; ok判断key是否在map中
3、 golang 判断map的键key是否存在

算法题

1、 剑指 Offer 03. 数组中重复的数字

1月8日

Go语言学习

1、 互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

1月9日

1月10日

1、 浅析操作系统同步原语
2、 同步原语
3、 信号量
4、 什么是调度?

笔记

1、同步原语其实就是字面意思,是计算机底层用到实现同步的原始语句。比如send和receive
2、以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
3、什么是调度?所谓调度就是选出待分派的作业或进程。处理机调度的主要目的就是为了分配处理机。

1月12日

Go语言学习

1、 web的本质

笔记

1、web是万维网的简称,是基于HTTP协议进行交互的应用网络
2、web就是通过浏览器/APP访问的各种资源
3、Web Page指网站内的网页.
4、WWW(World Wide Web 万维网)
5、Internet(互联网)则是一个更大的概念, Internet上不只有Web, 还有FTP, P2P,Email, 或者App等其他多种不同的互联网应用方式. Web只是其中最广泛的一种. Internet的概念要大于Web.

1月13日

字节青训营

1、 计数器限流方式
2、 滑动时间窗口限流
3、 漏桶限流算法
4、 令牌桶限流算法

1月14日

Redis

1、 lpush和rpush的区别_redis数据类型之list-lpush,rpush讲解
2、 Redis LRANGE命令
3、 Redis数据结构(5):quickList(快速列表)

青训营

1、 给想转Go或者Go进阶同学的一些建议

笔记

1、 DDD的核心思想就是避免业务逻辑的复杂性和技术实现的复杂性耦合在一起。
2、DDD最大的价值就是梳理业务需求,抽象出一个个“领域”,并形成各个领域之间的接口交互,方便团队协作,推进项目前进。
3、DDD领域驱动设计,还有一个有意思的观点:“不以用户为中心”。因为DDD认为,“以用户为中心”其实是表层需求,真正的需求应该是基于领域的,领域之所以有意义,一定是和人有关的。
所以做领域驱动设计时,应该做到“客观设计”,就是无论是谁使用,如何使用,这个领域都是这样的,挖掘深层次的需求。

1月15日

Go语言学习

1、 Go语言的%d,%p,%v等占位符的使用
2、 fallthrough如何使用
3、 【编程】语法糖、语法盐、语法糖精、语法海(hexie)洛(hexie)因(hexie)
4、 Go语言for range遍历数组
5、 Go sync.Once
6、 GO语言回调函数
7、 uint和int的区别

笔记

1、%q:双引号围绕的字符串,由Go语法安全地转义。
Printf(“%q”, “Go语言”)
“Go语言”
2、fallthrough只能够再向下执行一个case,不能把后面的case强制执行完
3、一切皆变量,go语言里什么都可以当作变量来使用,当然函数也不例外,函数可以作为函数变量。
4、所谓闭包,就是匿名函数,go语言支持匿名函数调用。

1月16日

Go语言学习

1、 你知道 Go 结构体和结构体指针调用有什么区别吗?

1月18日

botcamp飞书机器人

1、 三分钟了解 Serverless 是什么
2、 手把手教你用飞书 Webhook 打造一个消息推送 Bot

笔记

1、程序core是指应用程序无法保持正常running状态而发生的崩溃行为。
2、当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
3、同一个文件夹下的文件只能有一个包名,否则编译报错。
4、文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。比如:hello目录下,一开始的这个目录就都是用package main,main作为包名,如果有其它的比如math文件夹目录下,那我们可以不是非得写package math,可以写成其它名字,只要在此文件夹目录下的包名统一即可。
5、Serverless,又叫无服务器。Serverless 强调的是一种架构思想和服务模型,让开发者无需关心基础设施(服务器等),而是专注到应用程序业务逻辑上。Serverless 也是下一代计算引擎。
6、JSON(JavaScript Object Notation,即 JavaScript 对象表示法),是目前在 Web 领域里被广泛用于数据传递与交换的文件格式之一。JSON 通常由键值对构成。而我们的消息也是需要以这种格式来发送,当中包含的是关于该消息的类型、内容、其他参数等信息。
7、Webhook 顾名思义即网络钩子,也称为用户自定义 HTTP 回调函数(user-defined HTTP callbacks),通常用于监听某些行为事件,当事件触发时会向用户指定的目标地址发送信息。
8、飞书的自定义机器人本质上也就是提供了这样 「监听-通知」 的行为逻辑,让用户能够将消息转发到飞书上。
9、创建自定义机器人仅表明我们搭起了一个中转的「驿站」,但我们还需要能够向这个驿站发送相关的消息才能让其帮我们实现转发。

飞书目前支持的消息类型主要有如文本、富文本、交互式卡片等,不同的消息类型对应着不同格式的 JSON 数据。
10、富文本:如果你需要给消息内容增添一些样式,如链接、图片等,那么你可以选择富文本消息类型。
11、消息类型里最为复杂的是交互式消息卡片。消息卡片主要由两部分构成:消息头和消息主体内容

其他

1、 iTerm2和Terminal有什么区别?

笔记

1、iTerm2和Terminal有什么区别?基本上,这就是劳斯莱斯和本田之间的区别。两者都可以带您前往您想去的地方,但是其中之一是具有许多不错功能的更好乘车路线。

1月19日

botcamp飞书机器人

1、 Token
2、 goland Add Configurations 配置
3、 报错:../../go/pkg/mod/golang.org/x/net@v0.4.0/publicsuffix/table.go:5:8: package embed is not in GOROOT (/usr/local/go/src/embed)
4、 go 编译报错 package embed is not in GOROOT (/usr/local/go/src/embed)
5、 快速升级Go版本,仅需3分钟~~
6、 GitHub:Feishu 群消息机器人,定时提醒同学喝水~
7、 b站视频:用GO写一个飞书机器人定时提醒你喝水吧~

笔记

1、Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

1月20日

笔记

1、蛇形命名法就是这种user_info,还有驼峰命名法,比如userInfo(小驼峰)
2、对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。




/*
    在最坏的情况下,那个出现次数超过数组一半的数,就算其余全部的数都来抵消它,也没有关系,它也还是会有剩余
    因为它已经超过数组的一半了,再怎么抵消都会有剩余的
    所以最后的那个数,就是出现次数最多的那个数

    总体思路就是:取一个数val,cnt=1,然后判断下一个数是否与其相同,相同就cnt加1,然后继续下去,如果一直相同就一直加,
    当然如果不小心碰到了不同的,那么就减1,减到为0的时候,val就要重新换新的值,并把cnt重新置为1
*/
class Solution {
public:
    unordered_map<int, int> m;
    int moreThanHalfNum_Solution(vector<int>& nums) {
        int cnt = 0, val = -1;
        for(auto x : nums) {
            if(!cnt)    val = x, cnt = 1;   // 如果减到为0,val就重新取一个数,并把cnt重置为1
            else {
                if(x == val)    cnt++;
                else cnt --;
            }
        }
        return val;
    }
};

截屏2022-12-05 11.47.32.png



活动打卡代码 LeetCode 14. 最长公共前缀

/*
    第一重循环就是枚举一下当前有多少个字符,第二重循环就是枚举一下有多少个字符串,看一下所以字符串的这个字符是不是一样的
    前缀的子串一定要连续
    不连续的话就成了一个dp问题了,非常复杂
*/
class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        string res;
        if(strs.empty())    return res;

        for(int i = 0; ; i++) {
            if(i >= strs[0].size()) return res;
            char c = strs[0][i];    //获取第一个字符串的第i个字符
            for(auto& str: strs)  // 枚举一下后面的字符串
                if(str[i] != c || str.size() <= i )  // 判断后面i和字符串大小的关系,判断每个字符串第i个字符和第一个字符串第i个字符是否不等,如果不等就直接返回
                    return res;
            res += c;   // 否则的话就表示满足条件,将此前缀元素添加到字符串中
        }
        return res;
    }
};


活动打卡代码 AcWing 51. 数字排列

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<int> nums;
    vector<bool> st;

    vector<vector<int>> permutation(vector<int>& _nums) {
        nums = _nums;
        st = vector<bool> (nums.size(), false);     
        sort(nums.begin(),nums.end());  //由于要去重,故得先排序
        dfs(0);
        return res;
    }

    void dfs(int u)
    {
        if(u == nums.size())
        {
            res.push_back(path);
            return;
        }

        for(int i=0; i< nums.size(); ++i)
        {
            if(!st[i])
            {
                if(i>0 && nums[i] == nums[i-1] && !st[i-1])     // 去重
                    continue;
                st[i]=true;
                path.push_back(nums[i]);
                dfs(u+1);
                path.pop_back();
                st[i]=false;
            }
        }
    }
};

WechatIMG2720.jpeg
截屏2022-11-28 15.34.35.png
WechatIMG2841.jpeg



活动打卡代码 AcWing 50. 序列化二叉树

 [二叉树前序遍历、中序遍历、后序遍历、层序遍历的直观理解](https://blog.csdn.net/u013834525/article/details/80421684) 

序列化是指,将二叉树的输出转成字符串形式
反序列化是指,将字符串形式的输出转成二叉树的形式输出

截屏2022-11-28 10.31.12.png

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
 /*
    它这道题没有要求说字符串要一定按照什么样的顺序,所以不一定非要写层序遍历
 */
class Solution {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        string res;     // 把序列化的答案放到res中去
        dfs_s(root, res);
        return res;
    }

    // 前序遍历
    void dfs_s(TreeNode *root, string &res)
    {
        if (!root) {    // 如果说根节点是空的,那么就在结果res中加入“null ”
            res += "null ";     
            return;     // 然后返回
        }
        res += to_string(root->val) + ' ';  // 根节点,用空格隔开
        dfs_s(root->left, res);
        dfs_s(root->right, res);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        int u = 0;  // 从第0个位置开始
        return dfs_d(data, u);
    }

    // 反序列就比较麻烦,因为它需要将字符转成整数
    TreeNode* dfs_d(string data, int &u)    // 所有递归的时候,u都是同一个u,牵一发而动全身
    {
        if (u == data.size()) return NULL;  // 序列完后,到最后一个点以后,说明没有了已经,就返回空就行了
        // 否则就把下一个元素取出来
        int k = u;
        while(data[k] != ' ') k++; //k记录当前数字的位数如134是个三位数的数字,56是个两位数的数字,退出的时候,k指向了字符后面的空格,所以回到下个字符的首部需要加1.
        // k指向了字符后面的空格,这句话要重点注意

        if(data[u] == 'n') {    //如果当前字符串是“null”
            u = k+1;    //回到下一个数字的首部,注意是u = k+1, 不是u = u+1;
           // cout << "k = " << k <<endl;
            return NULL;    //表示这次构造的是一个null节点,并没有孩子节点,所以跳过后面的递归
        }
        // 否则的话,当前节点就是一个数字,我们需要把这个数字给算出来
        int val = 0, sign = 1;  
        if (data[u] == '-') sign = -1, u ++ ;   // 如果为负数
        for (int i = u; i < k; i ++ ) val = val * 10 + data[i] - '0';
        // 将字符串转成整数
        // 也就是把整体往前面移动一下,把个位空出来,让个位能够填充数字
        val *= sign;
        u = k + 1;  // 把u放到下一个字符的首部位置上去
        // 因为是按前序遍历“加密”的,所以这里也按前序遍历“解密”,这样取的顺序就跟原来的一样了
        auto root = new TreeNode(val);
        root->left = dfs_d(data, u);
        root->right = dfs_d(data, u);
        return root;
    }
};

截屏2022-11-28 13.33.49.png

问题1、在反序列化函数里面,为什么while(data[k] != ‘ ‘) k++的时候,难道不会担心k跳出去吗?越界了怎么办?
答:其实在后面if(data[u] == ‘n’)的时候,就是面对最后一个字符“null”,最后一个一定是“null”,会直接return出来,所以不会再进行递归下去,这样的话,k的指向也就停留在“null”前面的一个空格上,也就不会为了寻找空格而越界了。

一般来说,这里加上个k < data.size()这个判断条件会更好。

2、TreeNode* dfs_d(string data, int &u) 为什么参数u要取&引用
截屏2022-11-28 14.10.28.png
截屏2022-11-28 14.10.55.png




力扣题解

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode *head, *pre; // 创建两个指针,只会指向已有节点
    TreeNode* convert(TreeNode* root) {
        if (root == NULL) return root;

        dfs(root);
        return head;        
    }

    void dfs(TreeNode* cur) {
        // 中序遍历
        if (cur == NULL) return;
        // 左子树:可认为左半边已经处理好(最左点是head,最右点是pre)
        dfs(cur->left);

        // 根节点:将当前的cur加入到左半边
        if (head == NULL && pre == NULL) { // 递归到最左边的特殊情况 // 如果你们两个指针什么都没有,就来指向我吧,起码你们有个去处
            head = cur;     // 这里应该也就是一开始进入到这个函数来的时候,head和pre都是空,让head指向cur,也就是指向头节点
            pre = cur;
        } else {  // 将当前的cur加入到左半边    // 这里的pre是表示前面指针的意思,这里的pre已经是有指向了的,不是空的
            pre->right = cur;
            cur->left = pre;
            pre = cur;
        }

        // 右子树
        dfs(cur->right);
    }
};

截屏2022-11-27 15.42.42.png
截屏2022-11-27 16.14.56.png