Skip to content

Commit a4ff778

Browse files
committed
refactor & add document & examples
1 parent 94f818a commit a4ff778

File tree

9 files changed

+362
-19
lines changed

9 files changed

+362
-19
lines changed

Diff for: README.md

+194-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,195 @@
1-
# 周期循环字符串
1+
# 循环字符串数据结构(CycleString)
2+
3+
# 一、这是什么?解决了什么问题?
4+
5+
这个库定义了一个名为`CycleString`的数据结构,其实就是基于一个有限长度的字符串进行无限次重复得到的一个长度无限的字符流,同时在这个数据结构的基础上提供了一些字符串操作方法以便将其当做一个正常的字符串使用。
6+
7+
在研究古典密码学的时候发现一个很常见的场景就是秘钥补齐,比如要加密的文本的字符长度是20,但是加密使用的秘钥字符长度是10,则需要将秘钥重复两次凑齐20个字符与要加密的文本对齐再进行加密运算,于是就萌生了一个想法,干脆就定义一个特殊的字符串结构,这个字符串结构可以看做是一个基础字符串进行无限次重复而得到的一个长度无限的字符流,如此在加密场景下则可以忽略秘钥的长度让逻辑更清晰,从而将精力放在更重要的核心逻辑上而不是处理这些边边角角的鸡毛蒜皮,不止古典密码加密算法,其它类似的场景需求都可复用此工具库。
8+
9+
# 二、安装
10+
11+
```bash
12+
go get -u github.com/cryptography-research-lab/go-cycle-string
13+
```
14+
15+
# 三、示例代码
16+
17+
## 3.1 创建一个无限循环字符串
18+
19+
此数据结构需要基于一个有限长度的字符串,比如下面的代码就是创建一个无限长度的字符串:
20+
21+
```go
22+
cycleString := cycle_string.NewCycleString("CC11001100")
23+
```
24+
25+
这个无限长度的字符串的前若干个字符是:
26+
27+
```text
28+
CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100CC11001100
29+
```
30+
31+
## 3.2 字符串真实长度
32+
33+
对于CycleString这种数据结构来说长度是没有意义的,因为它的长度是无穷,但是它所基于的字符串的长度是有限的,所以这里的真实长度都是指的它所基于的字符串的长度,这里的长度有两种,一种是字符长度,使用`RealRuneLength`来获取,一种是字节长度,使用`RealByteLength`来获取,下面是这两个方法的使用示例:
34+
35+
```go
36+
package main
37+
38+
import (
39+
"fmt"
40+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
41+
)
42+
43+
func main() {
44+
45+
cycleString := cycle_string.NewCycleString("CC中文")
46+
fmt.Println(fmt.Sprintf("真实的字节数: %d", cycleString.RealByteLength())) // Output: 真实的字节数: 8
47+
fmt.Println(fmt.Sprintf("真实的字符数: %d", cycleString.RealRuneLength())) // Output: 真实的字符数: 4
48+
49+
}
50+
```
51+
52+
## 3.3 字符 & 字节
53+
54+
`CycleString`本质上是一个字符串,所以也可以按照下标获取对应位置的字符,对于下标的类型,又分为按照字节的下标和按照字符的下标:
55+
56+
```go
57+
package main
58+
59+
import (
60+
"fmt"
61+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
62+
)
63+
64+
func main() {
65+
66+
cycleString := cycle_string.NewCycleString("CC中文")
67+
68+
// 按照字节下标获取,这两个方法是等价的
69+
fmt.Println("At: " + string(cycleString.At(2)))
70+
fmt.Println("ByteAt: " + string(cycleString.ByteAt(2)))
71+
72+
// 按照字符下标获取,这两个方法是等价的
73+
fmt.Println("RuneAt: " + string(cycleString.RuneAt(2)))
74+
fmt.Println("CharAt: " + string(cycleString.CharAt(2)))
75+
76+
// Output:
77+
// At: ä
78+
// ByteAt: ä
79+
// RuneAt: 中
80+
// CharAt: 中
81+
82+
}
83+
```
84+
85+
## 3.4 子串
86+
87+
`CycleString`本质上是一个字符串,所以也可以获取其子串,获取字串有两种类型,一种是按字节长度获取,对应的方法名是`SubString`,一种是按字符数来获取,对应的方法是`SubStringRune`,使用哪个方法请按照自己的需求决定:
88+
89+
```go
90+
package main
91+
92+
import (
93+
"fmt"
94+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
95+
)
96+
97+
func main() {
98+
99+
cycleString := cycle_string.NewCycleString("中国")
100+
101+
// 按字节取子串,因为是中文,所以下面就乱码了
102+
subString := cycleString.SubString(0, 1)
103+
fmt.Println("按字节数:" + subString) // Output: 按字节数:�
104+
105+
// 按字符取子串,则能够完整的取到一个中文字符
106+
subString = cycleString.SubStringRune(0, 1)
107+
fmt.Println("按字符数:" + subString) // Output: 按字符数:中
108+
109+
// 如果下标不合法的话则取到空字符串,不会panic
110+
s := cycleString.SubString(-1, 10)
111+
fmt.Println("越界: " + s) // Output: 越界:
112+
113+
}
114+
```
115+
116+
## 3.5 迭代器模式
117+
118+
`CycleString`有一个迭代器模式的实现`CycleStringIterator`,通过`Iterator`方法来获取:
119+
120+
```go
121+
package main
122+
123+
import (
124+
"fmt"
125+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
126+
"time"
127+
)
128+
129+
func main() {
130+
131+
cycleString := cycle_string.NewCycleString("CC11001100")
132+
iterator := cycleString.Iterator()
133+
for iterator.Next() {
134+
fmt.Println(string(iterator.Value()))
135+
time.Sleep(time.Millisecond * 100)
136+
}
137+
// Output:
138+
// C
139+
// C
140+
// 1
141+
// 1
142+
// 0
143+
// 0
144+
// 1
145+
// 1
146+
// ...
147+
148+
}
149+
```
150+
151+
## 3.6 JSON序列化 & 反序列化
152+
153+
此数据结构可以被正常的序列化和反序列化,只不过比较特殊的是因为`CycleString`这个数据结构本身来说是没有右边界的,而`JSON`序列化又需要它有一个明确的边界,所以`CycleString`重写了`JSON`序列化的`MarshalJSON``UnmarshalJSON`函数,在序列化的时候只保存`CycleString`所基于的字符串,下面的代码是一个`JSON`序列化的例子:
154+
155+
```go
156+
package main
157+
158+
import (
159+
"encoding/json"
160+
"fmt"
161+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
162+
)
163+
164+
// Foo CycleString作为Foo这个Struct的一个字段,Foo可以被安全的JSON序列化
165+
type Foo struct {
166+
Bar *cycle_string.CycleString
167+
}
168+
169+
func main() {
170+
171+
foo := Foo{
172+
Bar: cycle_string.NewCycleString("CC11001100"),
173+
}
174+
marshal, err := json.Marshal(foo)
175+
if err != nil {
176+
fmt.Println("JSON序列化错误: " + err.Error())
177+
return
178+
}
179+
fmt.Println("序列化后的文本是: " + string(marshal)) // Output: 序列化后的文本是: {"Bar":{"CycleStringBaseString":"CC11001100"}}
180+
181+
foo2 := &Foo{}
182+
err = json.Unmarshal(marshal, foo2)
183+
if err != nil {
184+
fmt.Println("JSON反序列化错误: " + err.Error())
185+
return
186+
}
187+
fmt.Println("反序列后的前30个字符: " + foo2.Bar.SubStringRune(0, 30)) // Output: 反序列后的前30个字符: CC11001100CC11001100CC11001100
188+
189+
}
190+
```
191+
192+
# 四、TODO
193+
194+
暂未发现更多需求,如您使用了这个库又有相关功能当前未提供,可提`Issues`区讨论。
2195

3-
TODO 写API文档

Diff for: cycle_string.go

+26-14
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,36 @@ import (
88

99
// CycleString 循环字符串
1010
type CycleString struct {
11+
1112
// 原始的字符串,就是对这个字符串进行周期重复
12-
s string
13+
baseString string
14+
1315
// 原始字符串的字符表示形式,用于一些中文场景的处理
14-
runeSlice []rune
16+
baseStringRuneSlice []rune
1517
}
1618

1719
var _ json.Marshaler = &CycleString{}
1820
var _ json.Unmarshaler = &CycleString{}
1921
var _ iterator.Iterable[rune] = &CycleString{}
2022

2123
// NewCycleString 创建一个循环字符串
22-
func NewCycleString(s string) *CycleString {
24+
func NewCycleString(baseString string) *CycleString {
2325
return &CycleString{
24-
s: s,
25-
runeSlice: []rune(s),
26+
baseString: baseString,
27+
baseStringRuneSlice: []rune(baseString),
2628
}
2729
}
2830

2931
// ------------------------------------------------- --------------------------------------------------------------------
3032

3133
// RealRuneLength 返回真实的字符长度
3234
func (x *CycleString) RealRuneLength() int {
33-
return len(x.runeSlice)
35+
return len(x.baseStringRuneSlice)
3436
}
3537

3638
// RealByteLength 返回真实的字节长度
3739
func (x *CycleString) RealByteLength() int {
38-
return len(x.s)
40+
return len(x.baseString)
3941
}
4042

4143
// ------------------------------------------------- --------------------------------------------------------------------
@@ -47,14 +49,20 @@ func (x *CycleString) At(index int) byte {
4749

4850
// ByteAt 获取给定下标的字节,会按照字节长度获取
4951
func (x *CycleString) ByteAt(index int) byte {
50-
targetIndex := index % len(x.s)
51-
return x.s[targetIndex]
52+
if index < 0 {
53+
panic("Out of Index")
54+
}
55+
targetIndex := index % len(x.baseString)
56+
return x.baseString[targetIndex]
5257
}
5358

5459
// RuneAt 获取给定下标的字符,会按照字符长度计算下标
5560
func (x *CycleString) RuneAt(index int) rune {
56-
targetIndex := index % len(x.runeSlice)
57-
return x.runeSlice[targetIndex]
61+
if index < 0 {
62+
panic("Out of Index")
63+
}
64+
targetIndex := index % len(x.baseStringRuneSlice)
65+
return x.baseStringRuneSlice[targetIndex]
5866
}
5967

6068
// CharAt 返回给定位置的字符,同RuneAt
@@ -103,15 +111,18 @@ func (x *CycleString) Iterator() iterator.Iterator[rune] {
103111

104112
// 转为string的时候只返回原始的字符串
105113
func (x *CycleString) String() string {
106-
return x.s
114+
return x.baseString
107115
}
108116

109117
// ------------------------------------------------- --------------------------------------------------------------------
110118

119+
// CycleStringBaseStringJsonFieldName JSON序列化后存储真实字符串的字段名称,用于让序列化后的JSON能够具有一定的辨识和阅读性
120+
const CycleStringBaseStringJsonFieldName = "CycleStringBaseString"
121+
111122
// MarshalJSON JSON序列化
112123
func (x *CycleString) MarshalJSON() ([]byte, error) {
113124
m := make(map[string]string)
114-
m["s"] = x.s
125+
m[CycleStringBaseStringJsonFieldName] = x.baseString
115126
return json.Marshal(m)
116127
}
117128

@@ -122,7 +133,8 @@ func (x *CycleString) UnmarshalJSON(bytes []byte) error {
122133
if err != nil {
123134
return err
124135
}
125-
x.s = m["s"]
136+
x.baseString = m[CycleStringBaseStringJsonFieldName]
137+
x.baseStringRuneSlice = []rune(x.baseString)
126138
return nil
127139
}
128140

Diff for: cycle_string_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,16 @@ func TestCycleString_MarshalJSON(t *testing.T) {
6464
s := "CC是个中国人"
6565
cycleString := NewCycleString(s)
6666
b, err := cycleString.MarshalJSON()
67-
assert.Equal(t, []byte("{\"s\":\"CC是个中国人\"}"), b)
67+
assert.Equal(t, []byte("{\"baseString\":\"CC是个中国人\"}"), b)
6868
assert.Nil(t, err)
6969
}
7070

7171
func TestCycleString_UnmarshalJSON(t *testing.T) {
72-
s := "{\"s\":\"CC是个中国人\"}"
72+
s := "{\"baseString\":\"CC是个中国人\"}"
7373
cycleString := &CycleString{}
7474
err := json.Unmarshal([]byte(s), &cycleString)
7575
assert.Nil(t, err)
76-
assert.Equal(t, "CC是个中国人", cycleString.s)
76+
assert.Equal(t, "CC是个中国人", cycleString.baseString)
7777
}
7878

7979
func TestNewCycleString(t *testing.T) {

Diff for: examples/at/main.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
6+
)
7+
8+
func main() {
9+
10+
cycleString := cycle_string.NewCycleString("CC中文")
11+
12+
// 按照字节下标获取,这两个方法是等价的
13+
fmt.Println("At: " + string(cycleString.At(2)))
14+
fmt.Println("ByteAt: " + string(cycleString.ByteAt(2)))
15+
16+
// 按照字符下标获取,这两个方法是等价的
17+
fmt.Println("RuneAt: " + string(cycleString.RuneAt(2)))
18+
fmt.Println("CharAt: " + string(cycleString.CharAt(2)))
19+
20+
// Output:
21+
// At: ä
22+
// ByteAt: ä
23+
// RuneAt: 中
24+
// CharAt: 中
25+
26+
}

Diff for: examples/create/main.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
6+
)
7+
8+
func main() {
9+
10+
cycleString := cycle_string.NewCycleString("CC11001100")
11+
fmt.Println(cycleString.String())
12+
13+
}

Diff for: examples/iterator/main.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
cycle_string "github.com/cryptography-research-lab/go-cycle-string"
6+
"time"
7+
)
8+
9+
func main() {
10+
11+
cycleString := cycle_string.NewCycleString("CC11001100")
12+
iterator := cycleString.Iterator()
13+
for iterator.Next() {
14+
fmt.Println(string(iterator.Value()))
15+
time.Sleep(time.Millisecond * 100)
16+
}
17+
// Output:
18+
// C
19+
// C
20+
// 1
21+
// 1
22+
// 0
23+
// 0
24+
// 1
25+
// 1
26+
// ...
27+
28+
}

0 commit comments

Comments
 (0)