#510 谷歌身份认证器

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 基于时间

  1. 协商起始时间 T0 和时间间隔 TX
  2. 双方分别计算时间计数器 CT
  3. 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,但是更进一步简化,以约定代替了协商过程。

  1. T0 为 Unix 时间
  2. TX 为 30 秒
  3. 哈希算法为 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]))

参考资料与拓展阅读

#509 Windows 命令行操作

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 少有的几次接触感觉很好,很强大。其结构化而非文本的数据结构让人印象深刻。

  • 别名
  • 函数
  • cmdlet
  • 命令
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

#508 使用 Redis 做消息队列

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 公共许可证)。

  • PS: 摩根大通在 2003 年设计的 AMQP,然后和红帽合作使用 C++ 开发了一个 AMQP 开源实现,就是现在的 Apache Qpid。
  • PS: AMQP v1.0 于 2004 年成为 ISO 标准 (ISO/IEC 19464:2014)。
    据说和 0-9-1 有很大差别。

  • 生产者发布消息给指定 Exchange

  • Exchange 根据路由规则 (RoutingKey) 将消息投递给 Queue
  • 消费者订阅指定 Queue

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,也不支持那些复杂的功能配置,但是在简单的场景下用用还是不错的。

  1. 消费者订阅指定主题 subscribe foo
  2. 生产者发布消息 publish foo ooxxooxx

有两个不可忽视的问题:

  1. Pub 上来的消息必须实时发往订阅者,如果订阅者不在线,那么消息就丢失了。
  2. 如果消息积压到一定程度,Redis 会强制断开消费者连接,清空消息队列。

2.4 Streams

Redis 5.0 引入的新数据类型。
这个名字可能是来自流式计算。
所有提交上来的消息按顺序串成一个消息链。

非常值得一提的是,Stream 实现的队列允许重复消费、支持 ACK

  • XADD
  • XDEL
  • XRANGE

三、总结

  1. Redis 不保证数据完整性,所以如果是对数据完整性有要求的话,不能使用这个方案。
  2. Redis 不支持消息确认机制(Ack/Nack/Reject),需要自己在应用层面实现,但如果这么做的话,不如干脆用 MQ 算了。
  3. 除了 Pub/Sub 之外,ListSortedSetStreams 都支持消息持久化,但是应用时需要注意避免消息堆积,否则会可能会对内存造成压力。

如何保证消息的完整性呢?

参考资料与拓展阅读

#507 使用 jq 命令解析 JSON 数据

2021-03-04

JSON Logo

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!!!

#506 Golang compress

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
}

#505 Golang archive

2021-03-01

标准库中的 archive 支持 tarzip 两种打包格式。

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
}

#504 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 来初始化。

如果不指定容量,则切片的容量和长度将相等。

#503 Golang: 临时文件

2021-02-22
  1. 创建临时文件
  2. 使用编辑器编辑
  3. 获取内容
  4. 删除临时文件
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)
    }
}

#502 Golang strings

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

参考资料与拓展阅读

#501 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

参考资料与拓展阅读