LeetCode Hot100 在线人数看板

最近每天都会做几道LeetCode Hot100题,突发奇想,使用AI做一个采集数据并展示出来的小功能,看一下哪些题目刷的人比较多(毫无疑问,two-sum一直稳居榜首),最终的页面能够实时看到在线人数前十的题目,并且能够看到每一道题最近7天的在线人数变化趋势。整个项目涉及数据采集、API 服务、存储和前端展示四个部分,分别部署在 Railway、Cloudflare Workers、Cloudflare KV/D1 和 Vercel 上。

由于使用的是cloudflare的免费额度,KV写入读取有限制,所以采集数据采取每2分钟上报一次,这样每天就只有720次写入,不超过免费额度。D1的写入读取也有相应的优化来满足要求。

Read More

Comments

ring buffer(二)

在并发编程中,Data Race(数据竞争) 是一个常见的问题。当多个 goroutine 同时访问共享资源(如变量、数据结构等),并且至少有一个 goroutine 对资源进行写操作时,就可能发生 Data Race。Data Race 会导致程序行为不可预测,甚至引发崩溃。

在上一篇文章中,我们实现了一个简单的环形缓冲区。然而,这个实现并没有考虑并发访问的情况,因此在多 goroutine 环境下可能会出现 Data Race。本文将介绍如何通过 Golang 的同步机制(如 sync.Mutexsync.RWMutex)来避免环形缓冲区中的 Data Race。


为什么需要避免 Data Race?

在环形缓冲区的实现中,headtailisFull 是共享资源。如果多个 goroutine 同时读写这些资源,可能会导致以下问题:

  1. 数据不一致:例如,一个 goroutine 正在写入数据,而另一个 goroutine 同时读取数据,可能会导致读取到错误的值。
  2. 缓冲区状态错误:例如,isFull 的状态可能被错误地更新,导致缓冲区无法正确判断是否已满或为空。

为了避免这些问题,我们需要使用同步机制来保护共享资源。

Read More

Comments

ring buffer(一)

在软件开发中,环形缓冲区(Ring Buffer) 是一种非常常见的数据结构,尤其在需要高效处理数据流的场景中,如音频处理、网络数据包处理、日志系统等。环形缓冲区的主要特点是固定大小,当缓冲区满时,新的数据会覆盖旧的数据,形成一个“环形”的结构。

本文将介绍环形缓冲区的基本概念,并使用 Golang 实现一个简单的环形缓冲区。

什么是环形缓冲区?

环形缓冲区是一种线性数据结构,但它通过逻辑上的循环使用内存空间,使得当缓冲区满时,新的数据可以覆盖旧的数据。它的主要优点包括:

  1. 高效的内存使用:由于缓冲区大小固定,内存分配一次后不再需要动态调整。
  2. 高效的插入和删除操作:由于数据是顺序存储的,插入和删除操作的时间复杂度为 O(1)。
  3. 适用于流式数据处理:环形缓冲区非常适合处理连续的数据流,如音频、视频、网络数据包等。

Read More

Comments

RSA算法

RSA算法

RSA算法是一种非对称加密算法,有一对秘钥,公钥加密,私钥解密

关于秘钥长度,最常见的有1024bit,秘钥越长越安全,但是对应的需要加密时间也会变长,这个自己做取舍,一般1024bit已经很安全了。

加密的内容跟秘钥的长度也有关系,一次能加密的内容等于秘钥的长度,所以对于1024bit长度的秘钥,每次能加密(1024/8)=128byte的内容,超过128bit的内容就需要分段加密了。

Comments

关于Django执行原生SQL查询参数问题

开端

最近跟同事吃饭时,同事说遇到一个奇怪的问题,在使用Django执行原生查询的时候得到了意想不到的结果,排查两小时无果,听起来比较好奇,就想探究下,下面记录下探索过程。

问题描述

很简单的一个sql类似这样

1
2
3
4
5
6
7
8
9
10
11
// SELECT * FROM myapp_person WHERE id in (...)

// 1. 转换成Django查询语法可以这么写
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE id in (%s)'% ('1,2,3'))
>>> print(qs.query)
SELECT * FROM myapp_person WHERE id in (1,2,3)

// 2. 按照django文档也可以使用params参数
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE id in (%s)', ['1,2,3'])
>>> print(qs.query)
SELECT * FROM myapp_person WHERE id in (1,2,3)

Read More

Comments

基于位操作的角色权限设计

在软件开发中,权限管理是一个常见的需求。无论是Web应用、桌面应用还是移动应用,都需要对用户的权限进行精细化管理,以确保系统的安全性和功能的合理分配。传统的权限管理方法通常使用角色和权限的关联表,但这种方法在权限数量较多时可能会变得复杂且低效。本文将介绍一种基于位操作的角色权限设计方法,通过位运算实现高效、灵活的权限管理。

1. 位操作简介

位操作是计算机科学中的一种基本操作,它直接对二进制位进行操作。常见的位操作包括:

  • 与运算(AND)&,两个位都为1时结果为1,否则为0。
  • 或运算(OR)|,两个位中至少有一个为1时结果为1,否则为0。
  • 异或运算(XOR)^,两个位不同时结果为1,否则为0。
  • 取反运算(NOT)~,将每一位取反,0变1,1变0。
  • 左移(<<):将二进制位向左移动,右侧补0。
  • 右移(>>):将二进制位向右移动,左侧补0或补1(取决于符号位)。

位操作的高效性使其在权限管理中具有很大的优势。

2. 基于位操作的权限设计

2.1 权限定义

在基于位操作的权限设计中,每个权限用一个二进制位表示。例如,假设我们有四个权限:

  • 权限A:0001(二进制)
  • 权限B:0010(二进制)
  • 权限C:0100(二进制)
  • 权限D:1000(二进制)

每个权限对应一个唯一的二进制位,这样我们可以通过一个整数的二进制表示来存储多个权限。

Read More

Comments

129. 求根到叶子节点数字之和

leetcode每日一题疑惑

今天的leetcode每日一题129. 求根到叶子节点数字之和

看完题以后想了种解法(比较繁琐,层序遍历,记录到根节点的所有数字,最后再求和),不要在意细节,看完题解发现自己写的太复杂了,此文不讨论算法,记录下其中的一个疑惑,希望有大佬给解答下。

先上代码

Read More

Comments

惊喜的一天

2020年10月26日,星期一,本是平凡的一天,枯燥的周一,没想到晚上来了点惊喜,十四五年没见的同学突然联系上了。

初中在离家挺远的安国上学,认识了好多朋友,但是初中毕业以后就回家上学了,好多人慢慢失去了联系,晚上几个常在安国本地的同学聚在一起,想起了我,其中一人留有我的联系方式,还是上大学加上的,给我开了视频,让我一个一个说名字,说实话,好多年了,名字突然叫不上来,但是看着面容都能认出来,还有原来的影子,最大的特点就是全部变胖了。

有太多的话题,激动的说不出话来,有点想哭的感觉,当时大家在一起玩的很好,关系都不错,只是没有联系了,再联系还是感觉很亲切,大家都喝多了,跟他们视频完脑海中一直浮现十四五年前的画面。

初中离家远,老师同学都很照顾我,很感激!

最后约定找时间一定见面好好叙叙旧,太想念大家了,初中的友谊真的深,大部分关系都很好,越往后年龄越大,交心的朋友越来越少了,尤其工作以后,在北京这个快节奏的城市中,大家忙忙碌碌,没有了生活,这一瞬间让人忘记了生活的烦恼,全是对过去美好的回忆,真的很好,无法入眠的一夜。

Comments

Go中var、new、make介绍

go中声明变量有很多方式,五花八门(乱七八糟),实际上写多了会固化几种写法,比如全局变量会在外面用var声明,方法内变量一般都是 x := xxx 这样,至于channel、slice、map都会使用make关键字,而new实际当中用的很少

var

使用var关键字声明变量,系统会默认为变量分配内存空间,并赋值该类型的0值

1
2
var i int 
fmt.Println(i) // 0

如果声明指针类型的变量,就不会分配内存空间,默认是nil

1
2
var i *int
fmt.Println(i == nil) // true

此时需要配合new关键字使用,i指向一块内存空间

1
2
3
i = new(int)
fmt.Println(i) // 0xc000018030
fmt.Println(*i) // 0

new和make的区别

new 和 make 都会分配内存,new返回指向该类型内存地址的指针,并赋值该类型的0值,make只能用于channel、slice、map,返回的是引用类型,并初始化该类型

项目中使用较多的是slice,如果我们能明确知道slice的长度,声明的时候直接指定,能够避免reslice扩容造成的元素搬迁(追求性能,不过也是一个好习惯)

s := make([]int, 0, 10)

如果不能明确知道,一般这么定义

s := make([]int, 0)

今天发现一段代码这么定义直接使用的

var s []int

在实际项目中一直习惯于用make关键字来初始化slice,很少用关键字var,这种定义的slice为nil,比较好奇为啥没有出错,查看发现后面用了append操作,append操作可以接收nil的切片,在内部会使用mallocgc进行内存分配,所以这么写是可以的,觉得容易记混的话,还是都使用make来进行初始化,就会避免出现不必要的bug

Comments

leetcode -- 前缀树

leetcode上的一道设计题,要求实现trie这种数据结构,包含插入、查找、前缀查找的操作
https://leetcode-cn.com/problems/implement-trie-prefix-tree/

首先定义Trie struct

1
2
3
4
5
type Trie struct {
children []*Trie
isWord bool //是否为单词
part byte
}

Read More

Comments