Android byte数组传输到jni uint8t_数组

数组

初始化

数组可以保存指定长度的多个数据,且这些数据的类型都相同,数据类型可以是原始类型,如整型和字符串等,也可以是自定义类型。

数组通过索引来访问元素,索引从 0 开始,第一个元素的索引为 0,第二个为 1,依此类推。

在 Go 语言中声明数组的格式为:

1var variable [len]type

例如,声明名称为 arr1,长度和类型分别为 5 和 int 的数组:

1var arr1 [5]int

我们可以让编译器根据元素个数自动推断数组长度,只需要在声明长度时用 ... 替代:

1var numArray = [...]int{1, 2, 3}

我们还可以根据索引来声明数组:

1a := [...]string{0: "北京", 1: "上海"} // 索引 0 对应的元素为"北京",1 对应的元素为"上海"

整型数组中所有元素都初始化为 0,数组 arr 中第 i 个元素为 arr[i - 1],最后一个元素为 arr[len(arr) - 1]

数组是可变的,可以通过索引对元素进行赋值:arr[1] = 1

注意:在程序中若索引超出数组最大有效索引,会引发 index out of range 错误。

遍历数组
  • 普通 for 循环
1package main
 2
 3import "fmt"
 4
 5func main() {
 6	a := [...]int{2, 4, 6, 8, 10}
 7
 8	for i := 0; i < len(a); i++ {
 9		fmt.Println(a[i])
10	}
11}
  • for-range 循环
1package main
 2
 3import "fmt"
 4
 5func main() {
 6	a := [...]int{2, 4, 6, 8, 10}
 7
 8	for k, v := range a {
 9		fmt.Println(k, v)
10	}
11}

切片

概念

切片 slice 是对数组的引用,因此切片是一个引用类型(类似于 python 中的 list)。

切片是一个长度可变的数组

可以通过 cap() 函数来获取切片的容量,而 len() 函数获取的是切片的长度(切片保存的元素个数),对于切片 s ,存在这样的数量关系:

0 <= len(s) <= cap(s)

声明切片:

1var variable []type

可以通过类似数组的声明方式来声明切片:

1var s = []int{1, 2, 3}

若 arr 是数组,可以通过切割数组来声明切片 s,如:

1var arr = [...]int{1, 2, 3, 4, 5}
2var s []int = arr[0:3] // 声明切片 s,s 由 arr 中索引 0 到 2 的元素构成3var s1 []int = arr[:]  // s1 由 arr 所有元素构成4var s2 []int = arr[2:] // s2 由 arr 中索引 2 开始到最后的元素构成5var s3 []int = arr[:3] // s3 由 arr 中索引 0 ~ 2 的元素构成
用 make() 创建切片

我们可以通过 make() 来创建一个切片:

1var s []type = make([]type, len)

其中,type 是类型,len 是切片的长度。

我们来演示下切片的内存结构:

1package main
 2
 3import "fmt"
 4
 5func main() {
 6	var slice1 []int = make([]int, 10)
 7
 8	for i := 0; i < len(slice1); i++ {
 9		slice1[i] = 5 * i
10	}
11
12	for i := 0; i < len(slice1); i++ {
13		fmt.Printf("Slice at %d is %d\n", i, slice1[i])
14	}
15	fmt.Printf("\nThe length of slice1 is %d\n", len(slice1))
16	fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
17}

运行结果为:

1Slice at 0 is 0  
 2Slice at 1 is 5  
 3Slice at 2 is 10  
 4Slice at 3 is 15  
 5Slice at 4 is 20  
 6Slice at 5 is 25  
 7Slice at 6 is 30  
 8Slice at 7 is 35  
 9Slice at 8 is 40  
10Slice at 9 is 45  
11The length of slice1 is 10  
12The capacity of slice1 is 10

我们可以在初始化切片时候,指定切片初始长度切片容量

1slice1 := make([]type, length, cap) // type 为类型,length 为初始长度, cap 为切片容量
切片重组

切片会自动扩容,我们也可以手动进行扩容,比如将切片 s1 扩展 1 位:

1sl = sl[0 : len(sl)+1]

切片可以反复扩容直至切片长度到达切片容量:

1package main
 2
 3import "fmt"
 4
 5func main() {
 6	slice1 := make([]int, 0, 10)
 7	// load the slice, cap(slice1) is 10: 8	for i := 0; i < cap(slice1); i++ {
 9		slice1 = slice1[0 : i+1]
10		slice1[i] = i
11		fmt.Printf("The length of slice is %d\n", len(slice1))
12	}
13
14	// print the slice:15	for i := 0; i < len(slice1); i++ {
16		fmt.Printf("Slice at %d is %d\n", i, slice1[i])
17	}
18}

上述代码运行结果为:

1The length of slice is 1
 2The length of slice is 2
 3The length of slice is 3
 4The length of slice is 4
 5The length of slice is 5
 6The length of slice is 6
 7The length of slice is 7
 8The length of slice is 8
 9The length of slice is 9
10The length of slice is 10
11Slice at 0 is 0
12Slice at 1 is 1
13Slice at 2 is 2
14Slice at 3 is 3
15Slice at 4 is 4
16Slice at 5 is 5
17Slice at 6 is 6
18Slice at 7 is 7
19Slice at 8 is 8
20Slice at 9 is 9
切片的复制与追加

我们可以通过 copy 函数和 append 函数实现切片元素的复制追加,如:

1package main
 2
 3import "fmt"
 4
 5func main() {
 6	sl_from := []int{1, 2, 3}
 7	sl_to := make([]int, 10)
 8
 9	n := copy(sl_to, sl_from)
10	fmt.Println(sl_to)
11	fmt.Printf("Copied %d elements\n", n) // n == 312
13	sl3 := []int{1, 2, 3}
14	sl3 = append(sl3, 4, 5, 6)
15	fmt.Println(sl3) // 1, 2, 3, 4, 5, 616}

append 函数可以将多个相同类型的元素追加到切片后面,同时返回新的切片。若切片的容量不够,append 会分配新的切片保证能存储原切片和新元素。

如果想将切片 y 追加到 x 后面,只需将在 y 后面添加 .. 将其扩展成列表即可,如:

1x = append(x, y...)

注意append 虽然很好用,但是如果想要理解切片追加元素的原理,可以自己来实现一个 AppendByte 方法:

1func AppendByte(slice []byte, data ...byte) []byte {
2 m := len(slice) // 原切片长度 3 n := m + len(data) // 新切片长度 4 if n > cap(slice) { // 若新切片长度大于切片容量,手动扩容 5 newSlice := make([]byte, (n+1)*2)
6 copy(newSlice, slice)
7 slice = newSlice
8 }
9 // 复制元素10 slice = slice[0:n]
11 copy(slice[m:n], data)
12 return slice
13}

字符串、数组和切片的应用

从字符串生成字节切片

若 s 为一个字符串,可以通过 c := []bytes(s) 来获取 s 对应的字节切片。也可以通过 copy 函数来实现:copy (b []byte, s string)

截取字符串

substr := str[start:end] 可以从字符串 str  获取到从索引 start  开始到 end-1 位置的子串;

str[start:] 则表示获取从 start 开始到 len(str)-1 位置的子串;

str[:end] 表示获取从 0 开始到 end-1 的子串。

字符串和切片的内存结构

字符串是一个双字结构,即一个指向实际数据的指针和记录字符串长度的整数,如下图所示:

Android byte数组传输到jni uint8t_git_02

修改字符串的字符

Go 语言的字符串是不可变的,对于 str[index],我们不能执行这样的语句:

1str[index] = 'A'

编译器会报 cannot assign to str[i] 错误。

因此,如果要修改字符串中的字符,需要先将字符串转换成字节数组,然后修改字节数组中的元素来实现修改字符串字符的目的,最后再将字节数组转换成字符串。例如:

1s := "hello"
2c := []byte(s)
3c[0] = 'm'
4s2 := string(c) // "mello"
append 函数
  1. 切片 b 的元素追加到切片 a 之后:a = append(a, b...)
  2. 复制切片 a 的元素到新的切片 b 上:
1b = make([]T, len(a))
2copy(b, a)
  1. 删除位于索引 i 的元素:a = append(a[:i], a[i+1:]...)
  2. 切除切片 a 中从索引 i 至 j 位置的元素:a = append(a[:i], a[j:]...)
  3. 为切片 a 扩展  j 个元素长度:a = append(a, make([]T, j)...)
  4. 在索引 i 的位置插入元素 xa = append(a[:i], append([]T{x}, a[i:]...)...)
  5. 在索引 i 的位置插入长度为 j 的新切片:a = append(a[:i], append(make([]T, j), a[i:]...)...)
  6. 在索引 i 的位置插入切片 b 的所有元素:a = append(a[:i], append(b, a[i:]...)...)
  7. 取出位于切片 a 最末尾的元素 xx, a = a[len(a)-1], a[:len(a)-1]
  8. 将元素 x 追加到切片 aa = append(a, x)
切片和垃圾回收

切片的底层指向一个数组,该数组的实际容量可能大于切片的长度。只有没有任何切片指向该数组时,底层数组才会被释放,这有可能会导致占用多余内存。

示例 :

函数 FindDigits 将一个文件加载到内存,然后搜索其中所有的数字并返回一个切片:

1var digitRegexp = regexp.MustCompile("[0-9]+")
2
3func FindDigits(filename string) []byte {
4	b, _ := ioutil.ReadFile(filename)
5	return digitRegexp.Find(b)
6}

上述代码中返回了 []byte,它指向底层整个文件的数据。若该切片不被释放,垃圾回收器就不能释放整个文件所占用的内存。为了避免这个问题,可以复制我们需要的部分到一个新切片:

1func FindDigits(filename string) []byte {
2	b, _ := ioutil.ReadFile(filename)
3	b = digitRegexp.Find(b)
4	c := make([]byte, len(b))
5	copy(c, b)
6	return c
7}