和 Java 中的字符串类 String
一样,Go 中的字符串也是一个不可改变的字节序列
内置的 len
函数表示字符串的长度,即包含的字节数目,索引操作 s[i]
表示第i
个字符,i
的取值范围为0<=i
s := "this is a string"
fmt.Println(len(s)) //16
fmt.Println(s[16]) //panic: runtime error: index out of range
fmt.Println(s[0:4]) //this
fmt.Println(s[5:7]) //is
fmt.Println(s[:7]) //this is
fmt.Println(s[5:]) //is a string
fmt.Println(s[:]) //this is a string
因为字符串是不可改变的,所以对于字符串内容的修改是不被允许的
s[0] = "T" //无法编译通过
标准库中的 bytes、strings、strconv、unicode
四个包对字符串的处理
strings
中有对字符串的截取,查找,替换等多种方法
//查找一个路径中的文件的基础文件名,不带后缀
func BaseName(s string) string {
slash := strings.LastIndex(s, "/")
s = s[slash+1:]
if dot := strings.LastIndex(s,".");dot >= 0 {
s = s[:dot]
}
return s
}
var s string = "D:/a/b/c/d.go"
fmt.Println(part1.BaseName(s)) //d
//将一个整型的数据用逗号分隔,如123456789 --> 123,456,789
func SplitInt(n string) string{
len := len(n)
if len <= 3 {
return n
}
return SplitInt(n[:len-3]) + "," + n[len-3:]
}
var s = 123456789
fmt.Println(part1.SplitInt(strconv.Itoa(s)))
下面是strings
和 strconv
的结构图,方法名大写字母开头的方法即为可以被外部引用的方法,分布在不同的 go 文件里,可以根据语义去查找对应的API
strings 包结构
|-- strings
| |-- builder.go
| |-- builder_test.go
| |-- compare.go
| |-- compare_test.go
| |-- example_test.go
| |-- export_test.go
| |-- reader.go
| |-- reader_test.go
| |-- replace.go
| |-- replace_test.go
| |-- search.go
| |-- search_test.go
| |-- strings.go
| |-- strings.s
| `-- strings_test.go
strconv 包结构
|-- strconv
| |-- atob.go
| |-- atob_test.go
| |-- atof.go
| |-- atof_test.go
| |-- atoi.go
| |-- atoi_test.go
| |-- decimal.go
| |-- decimal_test.go
| |-- doc.go
| |-- example_test.go
| |-- export_test.go
| |-- extfloat.go
| |-- fp_test.go
| |-- ftoa.go
| |-- ftoa_test.go
| |-- internal_test.go
| |-- isprint.go
| |-- itoa.go
| |-- itoa_test.go
| |-- makeisprint.go
| |-- quote.go
| |-- quote_test.go
| |-- strconv_test.go
| `-- testdata
| `-- testfp.txt
字符串和字符slice
之间的切换
s := "abc"
b := []byte(s)
s2 := string(b)
bytes
包还提供了Buffer
类型用于字节slice
的缓存,可以随着byte,[]byte,string
等类型的写入而动态变化
//把整型数组准换为字符串数组
func IntsToString(ints []int) string {
var buf bytes.Buffer
buf.WriteByte('[')
for i, v := range ints {
if v > 0 {
buf.Write([]byte(strconv.Itoa(v)))
if i < len(ints)-1 {
buf.WriteString(",")
}
}
}
buf.WriteByte(']')
return buf.String()
}
fmt.Println(part2.IntsToString([]int{
1,2,3})) //[1,2,3]
通过byte.Buffer
来实现上面的用逗号分隔字符串的非递归实现
//将一个整型的数据用逗号分隔,如123456789 --> 123,456,789
func SplitIntByBuffer(n string) string {
l := len(n)
if l <= 3 {
return n
}
var buf bytes.Buffer
a := l % 3
buf.WriteString(n[:a])
for i := a; i < l; i += 3 {
if i != 0{
buf.WriteString(",")
}
buf.WriteString(n[i : i+3])
}
return buf.String()
}
var s = 1234567
fmt.Println(part2.SplitIntByBuffer(strconv.Itoa(s))) //1,234,567
常量表达式的值在编译期完成,而不是运行期
常量用关键字 const
来标记,可以声明一组常量
const (
a = 1
b
c = 2
d
)
fmt.Println(a,b,c,d) //1 1 2 2
如果是批量声明的常量,除了第一个常量外其他的常量右边的表达式都可以省略,如果省略了则表示后面的常量使用前面常量的初始化写法,并且对应的类型也是一样的,上面的b
与a
的值和类型是一样的,同理d
和c
也是一样的
在第一个声明常量所在行,iota
会被初始化为 0
type Count int
const (
FIRST Count = 1 + iota
SECOND
THIRD
FOURTH
FIFTH
)
fmt.Println(FIRST,SECOND,THIRD,FOURTH,FIFTH) //1 2 3 4 5
slice
代表变长的序列,序列中每个元素都有相同的类型。slice
的语法和数组很像,只是没有固定长度
slice
由指针,长度,容量三部分组成,通过len
函数可以查看长度,通过cap
函数可以查看容量的大小,对于定长的数组来说,容量与长度是等价的,对于slice
来说,长度是指底层数组的长度,但是容量则是值切片后重新切片时可以达到的最大长度;
// The len built-in function returns the length of v, according to its type:
// Array: the number of elements in v.
// Pointer to array: the number of elements in *v (even if v is nil).
// Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
// String: the number of bytes in v.
// Channel: the number of elements queued (unread) in the channel buffer;
// if v is nil, len(v) is zero.
// For some arguments, such as a string literal or a simple array expression, the
// result can be a constant. See the Go language specification's "Length and
// capacity" section for details.
func len(v Type) int
// The cap built-in function returns the capacity of v, according to its type:
// Array: the number of elements in v (same as len(v)).
// Pointer to array: the number of elements in *v (same as len(v)).
// Slice: the maximum length the slice can reach when resliced;
// if v is nil, cap(v) is zero.
// Channel: the channel buffer capacity, in units of elements;
// if v is nil, cap(v) is zero.
// For some arguments, such as a simple array expression, the result can be a
// constant. See the Go language specification's "Length and capacity" section for
// details.
func cap(v Type) int
按照源码中对于cap
的说明,结合代码来理解
month := [...]string{
1:"Jan",2:"Feb",3:"Mar",4:"Apr",5:"May",6:"Jun",
7:"Jul",8:"Aug",9:"Sep",10:"Oct",11:"Nov",12:"Dec"}
for i, v := range month {
fmt.Println(strconv.Itoa(i) + "-----" + v)
}
fmt.Println(cap(month),len(month)) //13 13
Q2 := month[4:7]
fmt.Println(cap(Q2),len(Q2)) //9 3
在没有slice
操作之前,month
的长度和容量是相等的,都为 13,其中索引为 0 的元素零值为空字符串;在切片之后生成了Q2
,Q2
的长度为 7 - 4 = 3,容量则是从 4 开始一直到month
的长度,即 13 - 4 = 9;
对于
s[i:j]
,len(s)=j-i,cap(s)=len(s)-i
因为slice
值包含指向第一个slice
元素的指针,因此向函数传递slice
将允许在函数内部修改底层数组的元素,也就是说,复制一个slice
只是对底层的数组创建了一个新的slice
别名
func reverse(n []int) {
for i, j := 0, len(n)-1; i < j; i, j = i+1, j-1 {
n[i], n[j] = n[j], n[i]
}
}
slice := []int{
1,2,3,4,5}
fmt.Println(slice) //[1 2 3 4 5]
reverse(slice)
fmt.Println(slice) //[5 4 3 2 1]
与数组相比,在传参的时候,数组是直接的值传递,而slice
是可以理解为引用指针的传递
func modifyArr(n [5]int) {
new := n
new[0] = 100
}
func modifySlice(n []int) {
new := n
new[0] = 100
}
arr := [5]int{
1,2,3,4,5}
slice := []int{
1,2,3,4,5}
modifyArr(arr)
fmt.Println(arr) //[1 2 3 4 5]
modifySlice(slice)
fmt.Println(slice) //[100 2 3 4 5]
append
函数可以在一个已有的slice
中添加一个或者多个元素,甚至一个新的slice
但是我们无法明确新的slice
和之前的slice
是否引用相同的底层数组,也就是说无法确认在append
操作后再去操作之前的slice
会不会对新的slice
产生影响
slice := []int{
1,2,3,4,5}
fmt.Printf("slice cap is %d\n",cap(slice)) //slice cap is 5
slice1 := append(slice,9)
fmt.Printf("slice1 cap is %d\t%p\t%p\n",cap(slice1),slice1,slice) //slice1 cap is 10 0xc0000520a0 0xc000078030
slice2 := append(slice,10)
fmt.Printf("slice2 cap is %d\t%p\t%p\n",cap(slice2),slice2,slice) //slice2 cap is 10 0xc0000520f0 0xc000078030
slice3 := append(slice2,10)
fmt.Printf("slice3 cap is %d\t%p\t%p\n",cap(slice3),slice3,slice2) //slice3 cap is 10 0xc0000520f0 0xc0000520f0
slice4 := append(slice2,6,7,7,5,0)
fmt.Printf("slice4 cap is %d\t%p\t%p\n",cap(slice4),slice4,slice2) //slice4 cap is 20 0xc0000b2000 0xc0000520f0
可以看出在初始容量 5 的时候,添加一个元素超出初始容量之后便做了扩容处理,slice1
容量增大一倍到了 10,并且分配了新的内存空间,也就是说不再引用之前slice
的底层数组,在添加没有超出容量的时候,每一次对slice
的添加都会分配一个新的内存空间(slice1
和slice2
),但是对已经append
操作过再继续append
元素并且没有超出容量的情况下,是不会重新分配内存空间的(slice3
和slice2
),超出容量之后,则会重新分配内存空间(slice4
和slice2
)
在 Go 语言中,一个map
就是一个哈希表的引用,map
类型可以写作map[K]V
,其中K
和V
分别对应key
和value
。key
和value
都必须各自有着相同的数据类型,但是key
和value
的类型可以不一样,并且key必须是支持==比较运算符的数据类型,所以map
可以通过测试key
是否相等来判断元素是否存在
map
的创建方式
1、内置的make函数、
m := make(map[string]int)
2、通过 map 的字面值语法
age := map[string]int{
"a":20,
"b":19,
}
map
中的元素被禁止取址操作,因为随着元素数量的增加会重新分配更大的内存空间,类似slice
的扩容,从而导致之前的地址失效
map
的迭代顺序也是不确定的,可以手动显示的对key
值进行排序,使用sort
包的Strings
函数处理
func SortMap(m map[string]string) {
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println("key:" + k + "-------->map:" + m[k])
}
}
//测试上面的函数
func TestSortMap(t *testing.T) {
m := map[string]string{
"d":"13",
"b":"312",
"w":"12",
"f":"12",
}
//一般用布尔变量ok来接收map中是否存在key,适用与马上用来判断的场景
if key, ok := m["d"]; ok {
fmt.Println(key)
}
SortMap(m)
}
//按照key升序排列,输出打印如下:
//13
//key:b-------->map:312
//key:d-------->map:13
//key:f-------->map:12
//key:w-------->map:12
为了便于测试,使用测试文件的测试方法来运行测试程序,避免每次必须main
函数作为入口运行的麻烦。将文件的命名加上_test
,文件命名格式变成"json_test.go
"的格式,然后再文件内创建Test
函数并加上t *testing.T
就好了,类似于 Java 的单元测试
在 Go 语言中,对JSON
数据类型也有着良好的支持,可用于编码map
类型(key
类型是字符串)和结构体。
type Book struct {
IndexNum int `json:"num"`
Name string
Price string
Hardcover bool `json:"favorite,omitempty"`
Language []string
}
这种结构特别适合JSON
格式,并且在两者之间转换特别容易。将类似Book
结构体的slice
转换为JSON
的过程叫做编组(marshaling),通过调用json.Marshal
函数完成,如果需要整齐的缩进格式,可以调用json.MarshalIndent
函数完成。
有编码成JSON
的方法,自然就有逆操作解码,可以通过json.Unmarshal
函数来完成JSON
数据的解码,转换成一个结构体slice
func TestJson(t *testing.T) {
var books = []Book{
{
IndexNum: 1, Name: "Thinking in Java", Price: "78", Hardcover: false,
Language: []string{
"English", "Chinese", "Japanese"}},
{
IndexNum: 2, Name: "The Go Programing Language", Price: "68", Hardcover: true,
Language: []string{
"English", "Chinese", "Japanese"}},
{
IndexNum: 3, Name: "Effective Java", Price: "59", Hardcover: true,
Language: []string{
"English", "Chinese"}},
}
//data, err := json.Marshal(books)
data, err := json.MarshalIndent(books, "", " ")
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
var bookD []Book
if err := json.Unmarshal(data, &bookD); err != nil {
log.Fatalf("JSON unmarshaling failed: %v", err)
}
fmt.Println(bookD)
}
上面测试方法运行后的输出结果为:
[
{
"num": 1,
"Name": "Thinking in Java",
"Price": "78",
"Language": [
"English",
"Chinese",
"Japanese"
]
},
{
"num": 2,
"Name": "The Go Programing Language",
"Price": "68",
"favorite": true,
"Language": [
"English",
"Chinese",
"Japanese"
]
},
{
"num": 3,
"Name": "Effective Java",
"Price": "59",
"favorite": true,
"Language": [
"English",
"Chinese"
]
}
]
[{
1 Thinking in Java 78 false [English Chinese Japanese]} {
2 The Go Programing Language 68 true [English Chinese Japanese]} {
3 Effective Java 59 true [English Chinese]}]
结构体中json:"num"
是结构体成员Tag
, 这些tag
是在编译阶段关联到该成员的元信息字符串,通常有key:"value"
键值对格式组成,在Book
结构体中,IndexNum
的输出值已经由num
代替了,而在HardCover
后面的tag
中,还多了一个omitempty
选项,表示结构体成员为空或者为零值的时候不生成JSON
对象,在这里bool
类型false
为零值,所以在num:1
的那本书中并没有显示favorite
,也就是Hardcover
属性
虽然对于简单的输出,使用Printf
函数就足够,但是需要输出复杂格式的时候就显得比较难处理了。
这时通过文本模版就可以很灵活的自定义显示输出的内容。一个模版是一个字符串或者一个文件,里面包含了一个或者多个由双花括号包含的{ {action}}
对象,模版语言包含通过选择结构体的成员、调用函数或方法,表达式控制流if-else
语句和range
循环语句等多种特性
通过一个调用GitHub API
的例子来理解自定义显示文本以及对返回JSON
格式数据的接收处理
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"testing"
"text/template"
"time"
)
const IssueUrl = "https://api.github.com/search/issues"
type IssueSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Item
}
type Item struct {
Id int
Title string
Number int
HTMLURL string `json:"html_url"`
State string
CreatedAt time.Time `json:"created_at"`
Score float64
User *User
Body string
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
func SearchIssue(key []string) (*IssueSearchResult, error) {
query := url.QueryEscape(strings.Join(key, " "))
res, err := http.Get(IssueUrl + "?q=" + query)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
res.Body.Close()
log.Fatalf("search is failed:%v", err)
return nil, fmt.Errorf("search is failed : %s", res.Status)
}
var result IssueSearchResult
//读取输入流数据,并且将其存储在&result指向的值中
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
res.Body.Close()
return nil, err
}
res.Body.Close()
return &result, err
}
//自定义的计算相差天数的函数
func daysAgo(t time.Time) int {
return int (time.Since(t).Hours() / 24)
}
func TestTextTemplate(t *testing.T) {
const templateText =
`the total issue is : {
{.TotalCount}}
{
{range .Items}}
------------
Numbers: {
{.Number}}
User: {
{.User.Login}}
Title: {
{.Title | printf "%.64s"}}
Age: {
{.CreatedAt | daysAgo}} days ago
{
{end}}`
var report = template.Must(template.New("issueList").Funcs(template.
FuncMap{
"daysAgo":daysAgo}).Parse(templateText))
key := []string{
"go", "java"}
result, err := SearchIssue(key)
//为了便于输出打印显示,只选取前3条items的数据
result.Items = result.Items[:3]
if err != nil {
log.Fatalf("the error is %v", err)
}
if err := report.Execute(os.Stdout,result);err != nil {
log.Fatalf("the error is %v", err)
}
}
上面的输出结果:
the total issue is : 521881
------------
Numbers: 76
User: pantianying
Title: go []string to java []string
Age: 6 days ago
------------
Numbers: 187
User: alourie
Title: Sync java and go sidecar clients
Age: 3 days ago
------------
Numbers: 628
User: wangqj
Title: add java call go doc
Age: 26 days ago