开发工具 密码学 OTP Auth 信息安全
2021-03-08
基础概念
身份验证器
Authenticator
密码就算是身份验证器。
加密密钥,比如 SSH 的公钥私钥(密钥对)
动态密码
One-time Password, 缩写: OTP, 又叫做一次性密码
我们常用的的短信验证码就是一种非常方便快捷的动态密码形式。
早些年一些网络服务,比如谷歌、网易通行证等,可能会提供一个动态口令表,包含十几个密码,可以保存为图片,或者纯文本,上面的密码可以逐个使用,每个密码只能用一次。
- RFC4226 HOTP: An HMAC-Based One-Time Password Algorithm
- RFC6238 TOTP: Time-Based One-Time Password Algorithm
HOTP 基于 HMAC 算法
简单来说就是根据密钥和计数器来生成一个一次性密码。
除了记住密钥之外,你还要记住这是第几次使用。
- HOTP value = HOTP(K, C) mod 10d
- HOTP(K, C) = truncate(HMACH(K, C))
- truncate(MAC) = extract31(MAC, MAC[(19 × 8) + 4:(19 × 8) + 7] × 8)
MAC[(19 × 8) + 4:(19 × 8) + 7] × 8
取第十九字节的后四位(小端序), 转成一个有符号整型数(小端序),作为截取 MAC 的位置
- extract31(MAC, i) = MAC[i × 8 + 1:i × 8 + (4 × 8) − 1]
MAC[i × 8 + 1:i × 8 + (4 × 8) − 1]
从上一步得到的位置处,取四个字节,去掉符号位(小端序)
TOTP 基于时间
- 协商起始时间 T0 和时间间隔 TX
- 双方分别计算时间计数器 CT
- TOTP value(K) = HOTP value(K, CT)
多重要素验证
Multi-factor authentication, 缩写: MFA
Two-factor authentication, 缩写: 2FA
谷歌身份验证器

Google 身份验证器是一款 TOTP 与 HOTP 的两步验证软件令牌,此软件用于 Google 的认证服务。此项服务所使用的算法已列于 RFC 6238 和 RFC 4226 中。
Google 身份验证器给予用户一个六位到八位的一次性密码用于进行登录 Google 或其他站点时的附加验证。其同样可以给第三方应用生成口令,例如密码管理员或网络硬盘。先前版本的 Google 身份验证器开放源代码,但之后的版本以专有软件的形式公开。
谷歌验证器基于 TOTP,但是更进一步简化,以约定代替了协商过程。
- T0 为 Unix 时间
- TX 为 30 秒
- 哈希算法为 sha1
此外:
虽然不是很大的创新,而且这个软件验证器实现很简单,但是免费、开放(不需要做任何谷歌服务绑定),加上谷歌的强大影响力,这个软件验证器被很多系统采用。
最后,其他提供动态口令的应用都需要来兼容谷歌身份验证器。
PS: RedHat 开发并维护了开源的 FreeOTP 分支项目。
PS: 微软也有一个 Microsoft Authenticator,阿里云 APP 中有一个 虚拟MFA 功能,都是一个意思。
微软家的为自己提供 8 位密码,别人家的就 6 位,区别对待(虽然感觉好像也并没有什么影响)
阿里云 MFA 是需要手机 APP 登录进去之后才能使用的。
Just4Fun
import base64
import hashlib
import hmac
import time
DEFAULT_INTERVAL = 30 # Google Authenticator: 30 秒
DEFAULT_HASH = hashlib.sha1
def get_hotp_token(secret: str, counter: int, hash_algorithm=DEFAULT_HASH, length=6):
padding_len = 8 - len(secret) % 8
if padding_len != 8:
assert 1 <= padding_len <= 7
secret += '=' * padding_len
key = base64.b32decode(secret, True)
message = (counter & 0xffffffffffffffff).to_bytes(8, 'big')
mac = hmac.new(key, message, hash_algorithm).digest()
loc = mac[-1] & 0x0F
token = (int.from_bytes(mac[loc:loc+4], 'big') & 0x7fffffff) % (10 ** length)
return token
def get_totp_token(secret, interval=DEFAULT_INTERVAL):
time_counter = int(time.time()) // interval
return get_hotp_token(secret, time_counter)
if __name__ == '__main__':
print('%06d' % get_totp_token(sys.argv[1]))
参考资料与拓展阅读
Windows CMD
2021-03-07
cmd
- 大小写不敏感
- 参数通常是
/
开头,而不像 Unix 下是 -
- 帮助:
-help
, /?
|
管道、>
输出重定向、>>
输出重定向(追加)、<
输入重定向、<< EOF
heredoc、<<< CONTENT
输入重定向(字符串)作用和 Linux 相同
- Ctrl + C 退出执行
help
help | more
dir C:\Windows\System32\*.exe
dir C:\Windows\System32\*.msc
export
echo
cls # 类似 clear
tasklist
taskkill
cd
mkdir / md
del # 类似 rm
del /q anydir
rd # 删除目录(非空目录不可删除)
dir # 类似 ls
tree
find
findstr # 类似 grep
ping
ipconfig
netstat
tracert
route
dig
nslookup
shutdown -s -t 300 # 定时关机
explorer # 文件浏览器
notepad # 记事本
taskmgr # 命令管理器
regedit # 注册表编辑器
services.msc # 服务
注册表
Registry, 港澳台地区称之为登录档。就是一个树形结构的数据库,存放系统和应用程序的配置信息。
Linux 下各个应用程序,甚至操作系统的各组件都采用不同的配置文件存储在不同的地方,但 Windows 却采用了这种集中式存储。
https://zh.wikipedia.org/zh-hans/注册表
名称 |
作用 |
HKEY_CLASSES_ROOT |
存储Windows可识别的文件类型的详细列表,以及相关联的程序。 |
HKEY_CURRENT_USER |
存储当前用户设置的信息。 |
HKEY_LOCAL_MACHINE |
包括安装在计算机上的硬件和软件的信息。 |
HKEY_USERS |
包含使用计算机的用户的信息。 |
HKEY_CURRENT_CONFIG |
这个分支包含计算机当前的硬件配置信息。 |
常见数据结构:
REG_SZ
字符串
REG_BINARY
二进制
REG_DWORD
32 位二进制值,显示为 8 位的十六进制数
REG_MULTI_SZ
多字符串,nul 隔开,结尾两个 nul
REG_EXPAND_SZ
python 有内置库 winreg 可以用来操作注册表。
import winreg
reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
key_name = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
key = winreg.OpenKey(reg, key_name)
for i in range(1024):
try:
sub_key_name = winreg.EnumKey(key, i)
sub_key = winreg.OpenKey(key, sub_key_name)
value = winreg.QueryValueEx(sub_key, "DisplayName")
print('%s: %s' % (sub_key_name, value))
except EnvironmentError:
break
Services
# 小心 sc 命令中比较另类的等于号后有空格设计
sc create memcached binPath= "c:\memcached.exe -l 0.0.0.0 -m 128 -d" DisplayName= "Memcached" start= auto
PowerShell
虽然使用频率不高,但和 PowerShell 少有的几次接触感觉很好,很强大。其结构化而非文本的数据结构让人印象深刻。
get-alias | findstr /I get-alias
gal | findstr ls
man
help
ls # Get-ChildItem
ps # Get-Process
ps | select Name, Id
ps | ft -Property Id, ProcessName, Handles, VM, WS, CPU -AutoSize
ps | where { $_.Name -eq "explorer" }
curl # Invoke-WebRequest
rm # Remove-Item
rmdir # Remove-Item
ni -ItemType File -Path C:\Users\Administrator\file.txt # New-Item
cp # Copy-Item
cp -Path C:\Users\Administrator\file.txt -Destination D:\Temp\
gcm # Get-Command
shcm # Show-Command
get-volume
工具
GUI
- process explorer
- everything
- HeidiSQL
- PuTTY
- WizTree 目录分析
包管理
- choco (chocolatey)
- scoop
- winget
安装包格式
- MSI 官方出品, Windows Installer
- NSIS Nullsoft Scriptable Install System, zlib license
- WiX
- Inno Setup
Redis MQ
2021-03-06
Redis 作者还分叉了项目,专门搞了一个实验项目 disque 来实现一个消息队列管理工具。
https://github.com/antirez/disque
这不是这篇文章的关注重点。这篇文章是想像一下使用 Redis 做 MQ 的情况(没在生产环境使用过)。
一、为什么使用 MQ
无非就是减少耦合,削峰填谷。
中间引入一个组件做代理,自然耦合解开了。
另外,一个稳定高效的 MQ 可以快速吸收消息,避免生产者的等待,同时消费者可以根据自己的处理速度来运行。就好像水库的作用,就是削峰填谷,损有余而补不足。
PS: MQ 好像还起到了一点 HA 的作用。
PS: 一种设计完善的消息推送方案,比如一个事件多方消费,如果没有消息队列则需要多次调用。
至于可能导致的问题嘛,当然也有,就是多了一个组件引入了新的复杂性。
上面提到的好处都是建立在 MQ 稳定可靠运行的基础之上,自然需要很多新的工作来维护消息队列中间件。
小规模系统就应该怎么简单怎么来,但如果系统足够复杂,多点工作总是难免的。
1.1 RabbitMQ
Erlang 所写(我听说过的唯一一个 Erlang 项目),AMQP 0-9-1 (高级消息队列协议) 的开源实现(Mozilla 公共许可证)。
1.2 Kafka
据说性能非常好,而且是倍经考验了,时不时看到有大型项目将其作为基础设施使用。
虽然最近两年 Kafka 很热门,但我没有在生产环境使用过。
二、Redis 实现
基础功能:
拓展功能:
- 重复消费(多个消费者)
- 确认机制
- 持久化
- 广播(消息直接写入多个队列)
2.1 List
采用 List 构建一个简单的 FIFO 队列:
LPUSH
RPOP
/BRPOP
如果没有消息了,会返回 nil
RPOPLPUSH
/ BRPOPLPUSH
可以在处理完成之后再从备份队列删除,实现类似 ACK 的功能
PS: RPOPLPUSH
: 弹出一个元素,并将该元素压入另一个列表。BRPOPLPUSH
: 如果源列表没有元素会阻塞。
redicConn.lpush('queue', 'a')
while True:
msg = redisConn.rpop('queue')
if msg is None:
time.sleep(1)
continue
do_something(msg)
2.2 SortedSet
有序集合,不支持重复消息(有时可以当做功能)。
用时间戳(或加一定权重)当分数,据此排序。
2.3 Pub/Sub
实现了简单的 Pub/Sub 功能。
虽然没有 RabbitMQ 那些多 API,也不支持那些复杂的功能配置,但是在简单的场景下用用还是不错的。
- 消费者订阅指定主题
subscribe foo
- 生产者发布消息
publish foo ooxxooxx
有两个不可忽视的问题:
- Pub 上来的消息必须实时发往订阅者,如果订阅者不在线,那么消息就丢失了。
- 如果消息积压到一定程度,Redis 会强制断开消费者连接,清空消息队列。
2.4 Streams
Redis 5.0 引入的新数据类型。
这个名字可能是来自流式计算。
所有提交上来的消息按顺序串成一个消息链。
非常值得一提的是,Stream 实现的队列允许重复消费、支持 ACK
。
三、总结
- Redis 不保证数据完整性,所以如果是对数据完整性有要求的话,不能使用这个方案。
- Redis 不支持消息确认机制(
Ack
/Nack
/Reject
),需要自己在应用层面实现,但如果这么做的话,不如干脆用 MQ 算了。
- 除了
Pub/Sub
之外,List
、SortedSet
、Streams
都支持消息持久化,但是应用时需要注意避免消息堆积,否则会可能会对内存造成压力。
如何保证消息的完整性呢?
参考资料与拓展阅读
Linux 开发工具 jq JSON
2021-03-04

jq 是我在命令行中解析 JSON 的一个常用工具,用起来非常顺手。
- https://github.com/stedolan/jq
- https://stedolan.github.io/jq/
用法
curl https://24pullrequests.com/users.json | jq
# 取第一个元素
curl https://24pullrequests.com/users.json | jq '.[0]'
# 取第一个元素的指定字段
curl https://24pullrequests.com/users.json | jq '.[0].nickname'
# 切片
curl https://24pullrequests.com/users.json | jq '.[:2]'
# 遍历
curl https://24pullrequests.com/users.json | jq '.[] | .nickname'
# 取字段
curl https://24pullrequests.com/users/changeworld.json | jq .nickname
# 取多个字段
curl https://24pullrequests.com/users/changeworld.json | jq '.nickname,.contributions_count'
# 获取列表长度
curl https://24pullrequests.com/users.json | jq length
# 列出 Keys
curl https://24pullrequests.com/users/changeworld.json | jq keys
curl https://24pullrequests.com/users/changeworld.json | jq "keys[]"
curl https://24pullrequests.com/users.json | jq ".[0] | keys[]"
# 列出 Keys 和 值类型
cat a.json | jq ".[0] | keys,map(type)"
cat a.json | jq ".[0] | to_entries | map([.key, (.value | type)])"
cat a.json | jq '.[0] | to_entries | map("\(.key) : \(.value|type)")[]'
还有很多更强大的用法,可以参考文档,我就会上面几个,在命令行中简单搜索 JSON 也够用了。
刚在文档中学会一招,重新组合 JSON:
curl https://24pullrequests.com/users/changeworld.json | jq '[.nickname, .organisations[].login]'
curl https://24pullrequests.com/users/changeworld.json | jq '{name:.nickname, orgs:[.organisations[].login]}'
👍🏻 Nice!!!
Golang GoStdLib
2021-03-01
- archive: 打包 (tar, zip)
参考:《Golang archive
包》
- compress: 压缩 (bzip2, flate, gzip, lzw, zlib)
压缩 & 解压缩
import (
"bytes"
"compress/gzip"
"io"
)
func compressData(data string) ([]byte, error) {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
_, err := gz.Write([]byte(data))
if err != nil {
return nil, err
}
if err := gz.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func decompressData(compressedData []byte) (string, error) {
r, err := gzip.NewReader(bytes.NewReader(compressedData))
if err != nil {
return "", err
}
defer r.Close()
decompressedData, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(decompressedData), nil
}
压缩 & 解压缩 & 列出压缩包中的内容
import (
"archive/zip"
)
// 压缩文件
func compressFile(zipFilename string, filenames ...string) error {
zipFile, err := os.Create(zipFilename)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
for _, filename := range filenames {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
fi, err := file.Stat()
if err != nil {
return err
}
fileHeader, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
// 设置文件名(压缩后的文件名)
fileHeader.Name = filename
// 写入文件头部
writer, err := zipWriter.CreateHeader(fileHeader)
if err != nil {
return err
}
// 复制文件内容到压缩包中
_, err = io.Copy(writer, file)
if err != nil {
return err
}
}
return nil
}
// 解压缩文件
func decompressFile(zipFilename, targetDir string) error {
zipReader, err := zip.OpenReader(zipFilename)
if err != nil {
return err
}
defer zipReader.Close()
for _, file := range zipReader.File {
// 打开压缩包中的文件
fileReader, err := file.Open()
if err != nil {
return err
}
defer fileReader.Close()
// 创建目标文件
extractedFilePath := fmt.Sprintf("%s/%s", targetDir, file.Name)
extractedFile, err := os.Create(extractedFilePath)
if err != nil {
return err
}
defer extractedFile.Close()
// 复制文件内容到目标文件
_, err = io.Copy(extractedFile, fileReader)
if err != nil {
return err
}
}
return nil
}
func listFilesInZip(zipFilename string) ([]string, error) {
files := []string{}
zipReader, err := zip.OpenReader(zipFilename)
if err != nil {
return files, err
}
defer zipReader.Close()
for _, file := range zipReader.File {
files = append(files, file.Name)
}
return files, nil
}
Golang GoStdLib
2021-03-01
标准库中的 archive
支持 tar
,zip
两种打包格式。
https://en.wikipedia.org/wiki/Tar_(computing)
最常用的 tar 包:
func createTarArchive(sourceDir, targetFile string) error {
file, err := os.Create(targetFile)
if err != nil {
return err
}
defer file.Close()
tarWriter := tar.NewWriter(file)
defer tarWriter.Close()
return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(tarWriter, file)
if err != nil {
return err
}
}
return nil
})
}
func extractTarArchive(sourceFile, targetDir string) error {
file, err := os.Open(sourceFile)
if err != nil {
return err
}
defer file.Close()
tarReader := tar.NewReader(file)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
targetPath := filepath.Join(targetDir, header.Name)
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
return err
}
if header.Typeflag == tar.TypeReg {
file, err := os.Create(targetPath)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, tarReader)
if err != nil {
return err
}
}
}
return nil
}
Golang
2021-02-24
数组声明时可以使用 ...
当作长度,表示自动判断。
数组声明时,可以只初始化其中的部分值。
a := [...]int{2: 1, 4: 2} // [5]int{0, 0, 1, 0, 4}
b := [5]int{1, 2, 3} // [5]int{1, 2, 3, 0, 0}
基本操作
a := [...]int{1, 2, 3}
var b []int
// 新增
b = append(b, 1)
// 删除元素
s = append(s[:index], s[index+1:]...)
// 遍历
for i := range a {
fmt.Println(a[i])
}
for index, value := range a {
fmt.Printf("%v: %v", index, value)
}
for _, value := range a {
fmt.Printf("%v", value)
}
for i := 0; i < len(array); i++ {
fmt.Printf("value: %d\n", array[i])
}
// 判断元素是否存在/获取元素序号
func Index(target int, array []int) int {
for i, v := range array {
if target == v {
return i
}
}
return -1
}
示例
package main
import (
"fmt"
"reflect"
)
func main() {
a := [5]int{1, 3, 5, 7, 9}
fmt.Printf("%s\t%#v\n", reflect.TypeOf(a), a)
// [5]int [5]int{1, 3, 5, 7, 9}
// array => slice
b := a[:] // a[0:len(a)]
fmt.Printf("%s\t%#v\n", reflect.TypeOf(b), b)
// []int []int{1, 3, 5, 7, 9}
// slice => array
// c := ([5]int)(b) // cannot convert b (type []int) to type [5]int
c := (*[5]int)(b) // 切片只能转换成数组指针
fmt.Printf("%s\t%#v\n", reflect.TypeOf(c), c)
// *[5]int &[5]int{1, 3, 5, 7, 9}
// 用类型别名试试:
type NumArr [5]int
c2 := (*NumArr)(b)
fmt.Printf("%s\t%#v\n", reflect.TypeOf(c2), c2)
// *main.NumArr &main.NumArr{1, 3, 5, 7, 9}
// 只能遍历赋值
d := [5]int{}
for index, v := range b {
d[index] = v
}
fmt.Printf("%s\t%#v\n", reflect.TypeOf(d), d)
// 通过 copy 的方法实现 slice2array
e := [5]int{}
copy(e[:], b) // return length of b
fmt.Printf("%s\t%#v\n", reflect.TypeOf(e), e)
}
去重
func Unique(arr []int) []int {
arrLen := len(arr) - 1
for arrLen > 0 {
for i := arrLen - 1; i >= 0; i-- {
if arr[arrLen] == arr[i] {
arr = append(arr[:i], arr[i+1:]...)
break
}
}
arrLen--
}
return arr
}
func UniqueOptimized(arr []int) []int {
uniqueArr := make([]int, 0, len(arr))
uniqueMap := make(map[int]struct{})
for _, num := range arr {
if _, ok := uniqueMap[num]; !ok {
uniqueArr = append(uniqueArr, num)
uniqueMap[num] = struct{}{}
}
}
return uniqueArr
}
// BenchmarkUniqueOriginal-20 135 8658964 ns/op 0 B/op 0 allocs/op
// BenchmarkUniqueOptimized-20 3501 347027 ns/op 285402 B/op 208 allocs/op
可以看到,一个性能好一些(只用 4% 的时间),资源使用多一些。
使用反射
https://blog.csdn.net/youngwhz1/article/details/83026263
func SliceRemoveDuplicate(a interface{}) (ret []interface{}) {
if reflect.TypeOf(a).Kind() != reflect.Slice {
fmt.Printf("<SliceRemoveDuplicate> <a> is not slice but %T\n", a)
return ret
}
va := reflect.ValueOf(a)
for i := 0; i < va.Len(); i++ {
if i > 0 && reflect.DeepEqual(va.Index(i-1).Interface(), va.Index(i).Interface()) {
continue
}
ret = append(ret, va.Index(i).Interface())
}
return ret
}
func SliceInsert(s []interface{}, index int, value interface{}) []interface{} {
rear := append([]interface{}{}, s[index:]...)
return append(append(s[:index], value), rear...)
}
func SliceInsert2(s *[]interface{}, index int, value interface{}) {
rear := append([]interface{}{}, (*s)[index:]...)
*s = append(append((*s)[:index], value), rear...)
}
func SliceInsert3(s interface{}, index int, value interface{}) bool {
if ps, ok := s.(*[]string); ok {
if val, ok := value.(string); ok {
rear := append([]string{}, (*ps)[index:]...)
*ps = append(append((*ps)[:index], val), rear...)
return true
}
} else if ps, ok := s.(*[]int); ok {
if val, ok := value.(int); ok {
rear := append([]int{}, (*ps)[index:]...)
*ps = append(append((*ps)[:index], val), rear...)
}
} else if ps, ok := s.(*[]float64); ok {
if val, ok := value.(float64); ok {
rear := append([]float64{}, (*ps)[index:]...)
*ps = append(append((*ps)[:index], val), rear...)
}
} else {
fmt.Printf("<SliceInsert3> Unsupported type: %T\n", s)
}
return false
}
func SliceRemove(s []interface{}, index int) []interface{} {
return append(s[:index], s[index+1:]...)
}
func SliceRemove2(s *[]interface{}, index int) {
*s = append((*s)[:index], (*s)[index+1:]...)
}
func SliceRemove3(s interface{}, index int) bool {
if ps, ok := s.(*[]string); ok {
*ps = append((*ps)[:index], (*ps)[index+1:]...)
} else if ps, ok := s.(*[]int); ok {
*ps = append((*ps)[:index], (*ps)[index+1:]...)
} else if ps, ok := s.(*[]float64); ok {
*ps = append((*ps)[:index], (*ps)[index+1:]...)
} else {
fmt.Printf("<SliceRemove3> Unsupported type: %T\n", s)
return false
}
return true
}
func SliceClear(s *[]interface{}) {
*s = append([]interface{}{})
}
func SliceClear2(s *[]interface{}) {
*s = (*s)[0:0]
}
func SliceClear3(s interface{}) bool {
if ps, ok := s.(*[]string); ok {
*ps = (*ps)[0:0]
//*ps = append([]string{})
} else if ps, ok := s.(*[]int); ok {
*ps = (*ps)[0:0]
//*ps = append([]int{})
} else if ps, ok := s.(*[]float64); ok {
*ps = (*ps)[0:0]
//*ps = append([]float64{})
} else {
fmt.Printf("<SliceClear3> Unsupported type: %T\n", s)
return false
}
return true
}
泛型实现
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
tot := len(s) + len(v)
if tot <= cap(s) {
s2 := s[:tot]
copy(s2[i+len(v):], s[i:])
copy(s2[i:], v)
return s2
}
s2 := make(S, tot)
copy(s2, s[:i])
copy(s2[i:], v)
copy(s2[i+len(v):], s[i:])
return s2
}
内置方法 make
make(Type, Len, Cap)
可以用来为 slice
, map
, channel
三种类型初始化(类似:C 语言 (int *)malloc(5)
)。
这里就看 Slice 的情况。
s1 := make([]int, 5)
s2 := make([]int, 5, 10)
// s2[8] = 1 // panic: runtime error: index out of range [8] with length 5
s2 = append(s2, 1)
如果有预留空间,append
的时候可以不用重新分配内存并遍历赋值。
如果切片有扩容的需要,就最好采用 make
来初始化。
如果不指定容量,则切片的容量和长度将相等。
Golang
2021-02-22
- 创建临时文件
- 使用编辑器编辑
- 获取内容
- 删除临时文件
package main
import (
"bufio"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
)
func main() {
// create a tmp file
// ioutil.TempFile creates a temp file and opens the file for reading and writing
// and returns the resulting *os.File (file descriptor).
tmpFile, err := ioutil.TempFile(os.TempDir(), "todo-tmp-")
if err != nil {
log.Fatal("Cannot create temporary file", err)
}
// defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
fmt.Println("Created File: " + tmpFile.Name())
// choose editor by env var
editor := os.Getenv("EDITOR")
args := []string{}
if editor == "" {
editor = "vim"
args = append(args, "--clean")
fmt.Println("No EDITOR enviroment variable set, use " + editor)
} else {
fmt.Println("Using EDITOR enviroment variable: " + editor)
}
args = append(args, tmpFile.Name())
fmt.Println("Opening file with " + editor + " with args: " + fmt.Sprint(args))
// check the editor command is available
// _, err = exec.LookPath(editor)
// if err != nil {
// log.Fatal("Cannot find editor: " + editor)
// }
// call the editor
cmd := exec.Command(editor, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatal("Cannot run editor: " + editor)
}
// read the file
// data, err := ioutil.ReadFile(tmpFile.Name())
// if err != nil {
// log.Fatal("Cannot read file: " + tmpFile.Name())
// }
tmpFile.Seek(0, 0)
s := bufio.NewScanner(tmpFile)
for s.Scan() {
fmt.Println("Content:\n\n" + s.Text())
}
if err = s.Err(); err != nil {
log.Fatal("error reading temp file", err)
}
}
Golang GoStdLib
2021-02-21
func Clone(s string) string //
func Compare(a, b string) int // 比较
func Contains(s, substr string) bool // 包含子字符串
func ContainsAny(s, chars string) bool // 包含任意Char
func ContainsRune(s string, r rune) bool // 包含Rune
func Count(s, substr string) int // 计数
func Cut(s, sep string) (before, after string, found bool)
func EqualFold(s, t string) bool //
func Fields(s string) []string // 切割
func FieldsFunc(s string, f func(rune) bool) []string
func HasPrefix(s, prefix string) bool // 前缀判断 startswith
func HasSuffix(s, suffix string) bool // 后缀判断 endswith
func Index(s, substr string) int // 查找
func IndexAny(s, chars string) int // 查找Any
func IndexByte(s string, c byte) int // 查找Byte
func IndexFunc(s string, f func(rune) bool) int // 查找Func
func IndexRune(s string, r rune) int // 查找Rune
func Join(elems []string, sep string) string // 连接
func LastIndex(s, substr string) int // 右查找
func LastIndexAny(s, chars string) int // 右查找Any
func LastIndexByte(s string, c byte) int // 右查找Byte
func LastIndexFunc(s string, f func(rune) bool) int // 右查找Func
func Map(mapping func(rune) rune, s string) string //
func Repeat(s string, count int) string // 重复
func Replace(s, old, new string, n int) string // 替换
func ReplaceAll(s, old, new string) string // 替换 = Replace -1
func Split(s, sep string) []string // 切割
func SplitAfter(s, sep string) []string //
func SplitAfterN(s, sep string, n int) []string //
func SplitN(s, sep string, n int) []string //
func Title(s string) string //
func ToLower(s string) string // 转小写
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToTitle(s string) string // Title
func ToTitleSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string
func ToValidUTF8(s, replacement string) string
func Trim(s, cutset string) string
func TrimFunc(s string, f func(rune) bool) string
func TrimLeft(s, cutset string) string
func TrimLeftFunc(s string, f func(rune) bool) string
func TrimPrefix(s, prefix string) string
func TrimRight(s, cutset string) string
func TrimRightFunc(s string, f func(rune) bool) string
func TrimSpace(s string) string
func TrimSuffix(s, suffix string) string
type Builder
func (b *Builder) Cap() int
func (b *Builder) Grow(n int)
func (b *Builder) Len() int
func (b *Builder) Reset()
func (b *Builder) String() string
func (b *Builder) Write(p []byte) (int, error)
func (b *Builder) WriteByte(c byte) error
func (b *Builder) WriteRune(r rune) (int, error)
func (b *Builder) WriteString(s string) (int, error)
type Reader
func NewReader(s string) *Reader
func (r *Reader) Len() int
func (r *Reader) Read(b []byte) (n int, err error)
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error)
func (r *Reader) ReadByte() (byte, error)
func (r *Reader) ReadRune() (ch rune, size int, err error)
func (r *Reader) Reset(s string)
func (r *Reader) Seek(offset int64, whence int) (int64, error)
func (r *Reader) Size() int64
func (r *Reader) UnreadByte() error
func (r *Reader) UnreadRune() error
func (r *Reader) WriteTo(w io.Writer) (n int64, err error)
type Replacer
func NewReplacer(oldnew ...string) *Replacer
func (r *Replacer) Replace(s string) string
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error)
Builder
Reader
Replacer
参考资料与拓展阅读
Python
2021-02-20

垃圾回收
Garbage Collector, 简写: GC
Python 垃圾回收是简单基于引用计数
弱引用
在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。一些配有垃圾回收机制的语言,如Java、C#、Python、Perl、Lisp等都在不同程度上支持弱引用。
一句话:弱引用不增加计数,对引用计数型 GC 友好一些
垃圾回收与循环引用的问题
import gc
IDS = {}
class A:
def __del__(self):
_id = id(self)
print('A.__del__ %s: 0x%x' % (IDS[_id], _id))
OBJS = {i: A() for i in range(3)}
for i, obj in OBJS.items():
_id = id(obj)
IDS[_id] = f'OBJS[{i}]'
print('%s: 0x%x' % (IDS[_id], _id))
OBJS[1].attr = OBJS[1]
print('1' * 50)
print('====> del OBJS[0]')
del OBJS[0]
print('2' * 50)
print('====> del OBJS[1]')
del OBJS[1]
print('3' * 50)
print('====> del OBJS[2]')
del OBJS[2]
print('4' * 50)
gc.collect()
import weakref
print()
print('=' * 50)
class B:
def __init__(self, obj):
self.attrs = [obj]
def __del__(self):
_id = id(self)
print('B.__del__ %s: 0x%x' % (IDS[_id], _id))
a = A()
b = B(a)
a.xyz = b
IDS[id(a)] = 'a'
IDS[id(b)] = 'b'
del a, b # do nothing
print('=' * 40)
gc.collect() # will del a and b
print()
print('=' * 50)
class C:
def __init__(self, obj):
self.attrs = [weakref.ref(obj)]
def __del__(self):
_id = id(self)
print('C.__del__ %s: 0x%x' % (IDS[_id], _id))
a = A()
c = C(a)
a.xyz = c
IDS[id(a)] = 'a'
IDS[id(c)] = 'c'
del a, c
print('=' * 40)
gc.collect()
标准库:weakref
class weakref.ref(object[, callback])
回调
weakref.proxy(object[, callback])
weakref.getweakrefcount(object)
weakref.getweakrefs(object)
class weakref.WeakKeyDictionary([dict])
.keyrefs()
class weakref.WeakValueDictionary([dict])
.valuerefs()
class weakref.WeakSet([elements])
Set class that keeps weak references to its elements. An element will be discarded when no strong reference to it exists any more.
class weakref.WeakMethod(method)
class weakref.finalize(obj, func, /, *args, **kwargs)
weakref.ReferenceType
weakref.ProxyType
weakref.CallableProxyType
weakref.ProxyTypes
import weakref
class Klass:
pass
obj = Klass()
ref = weakref.ref(obj)
print(ref())
del obj
print(ref()) # None
obj = Klass()
p = weakref.proxy(obj)
print(p)
del obj
print(p) # ReferenceError: weakly-referenced object no longer exists
参考资料与拓展阅读