GO 切片 map hash类型的底层实现和字符串

内容详细

1 切片

        // 数组---》连续存储同种元素的数据结构---》数组一旦定义,大小是不能改变
        // 存在的问题,不知道要存多少数据,需要一种数据结构像python列表一样,可以只管放值,不用管大小

        // js:数组
        // python:列表
        // java: ArryList
        // go:切片

        // 定义
        切片是由【数组】建立的一种方便、灵活且功能强大的包装。切片本身【不拥有任何数据】。它们只是【对现有数组的引用】---》切片底层依赖于数组,是对数组的引用
        延伸:底层数组如果发生变化,切片也变了;切片发生变化,底层数组也变化
        package main

        import "fmt"

        // 切片
        func main() {
            // 1 切片的定义--->使用数组定义出来---》切片类型--》[]类型
            // 值类型的零值--->区分于引用类型---》零值分别是
            // 数字:0
            // 字符串:""
            // 布尔: false
            //var a [10]int  // 数组是值类型,不初始化,有默认的零值--->默认的零值是数组内类型的零值
            // 基于数组--》生成切片
            //var s =a[:]  // 把数组从第0个位置切到最后一个位置,赋值给s切片
            //var s []int=a[:]    // 中括号中只要不放任何东西就是切片,放了数字就是数组,对新手很容易混
            //var s []int=a[2:4]
            //fmt.Println(s)

            // 2 切片定义并初始化---》通过make初始化--->切片只定义不初始化,默认0值为:nil
            // 引用类型的零值是nil(类比python的None),值类型的零值各不相同
            //var s []int=make([]int,3,4) // 定义一个长度为3的 []int 切片,容量是4
            //fmt.Println(s)  // [0 0 0]


            // 3 切片直接定义并初始化---》类比数组初始化
            //var s []int=[]int{3,4,5}
            //fmt.Println(s)



            // 4 切片的底层实现

            //var a [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
            //var b []int=a[:]
            //fmt.Println("数组a:",a)
            //fmt.Println("切片b:",b)

            // 4.1 切片和数组的修改都会相互影响
            //a[0]=999
            //fmt.Println("数组a:",a)
            //fmt.Println("切片b:",b) // 也改了,影响了
            //b[1]=888
            //fmt.Println("数组a:",a)// 也改了,影响了
            //fmt.Println("切片b:",b)

            // 4.2 切片的长度和容量--》切片的长度指的是目前切片多大,容量指的是,切片总共能存多少
            // len 内置函数查看长度    cap内置函数查看容量 ---》只针对于切片类型
            // 数组类型 a
            //fmt.Println(len(a))    // 数组只有个长度
            //fmt.Println(cap(a))   // 数组没有容量这一说,但是用的时候不报错(没有人这么用)
            // 切片类型b
            //fmt.Println(len(b))    //b切片的长度是10
            //fmt.Println(cap(b))   // b切片的容量是:10
            //var a [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
            //var b []int=a[2:4]
            ////a[0]=999
            //b[0]=999
            //fmt.Println(a)
            //fmt.Println(b)
            //fmt.Println(len(b))    //b切片的长度是2
            //fmt.Println(cap(b))   // b切片的容量是:8   指向起始位置开始到末尾

            // 演示
            //var a [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
            //var b []int=a[7:8]  // 第一个数组和最后一个数字都可以不写
            //fmt.Println(len(b))    //b切片的长度是1
            //fmt.Println(cap(b))   // b切片的容量是:3   指向起始位置开始到末尾

            // 切片底层基于数组,指向数组的某个位置---》切片的容量是从指向位置开始导数组结尾的个数


            // 5 切片追加元素--->数组不允许追加元素,切片可以
            //var a [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
            //var b []int=a[7:8]
            //b[0]=999
            //fmt.Println(a)
            //fmt.Println(b)
            //// 切片追加元素--->内置函数   python列表  列表对象.append()
            //b=append(b,888)
            //fmt.Println("切片b:",b)
            //fmt.Println("数组a:",a)
            //fmt.Println(len(b))  // 2
            //fmt.Println(cap(b))  //3
            //// 现在长度是2,容量是3,再追加 777
            //b=append(b,777)
            //fmt.Println("切片b:",b)
            //fmt.Println("数组a:",a)
            //fmt.Println(len(b))  // 3
            //fmt.Println(cap(b))  //3
            //// 现在长度是3,容量是3,再追加 666---》超过了底层数组大小---》
            ////1 重写申请一个底层数组,把切片的值copy过去 2 切片的容量变为:原来切片容量的两倍  3 现在这个切片跟原来数组就没关系了
            //b=append(b,666)
            //fmt.Println("切片b:",b)  //[999 888 777 666]
            //fmt.Println("数组a:",a) //[1 2 3 4 5 6 7 999 888 777]
            //fmt.Println(len(b))  // 4
            //fmt.Println(cap(b))  //6
            //// 修改切片,不会影响最原来的数组了
            //b[0]=9
            //a[9]=7
            //fmt.Println("切片b:",b)  //[9 888 777 666]
            //fmt.Println("数组a:",a) //[1 2 3 4 5 6 7 999 888 7]


            //6 make创建切片--->底层数组取不到
            //var s =make([]int,3,4)
            //fmt.Println(s)  // [0 0 0 ]
            //fmt.Println(len(s)) // 3
            //fmt.Println(cap(s)) //4

            //var s =[]int{3,4,5}
            //fmt.Println(s)  // [3,4,5]
            //fmt.Println(len(s)) // 3
            //fmt.Println(cap(s)) //3

             // 7 切片的零值   --->nil
            //var s []int=make([]int,2,2)   // 定义,有初始化
            ////var s []int   // 只定义,没有初始化
            //fmt.Println(s[0])  // 不能取值赋值,因为没有初始化
            //fmt.Println(s)    // [ ]
            //if s==nil {
            //	fmt.Println("我是nil")
            //}else {
            //	fmt.Println("我不是nil")
            //}

            // 8 切片的参数传递---》切片是引用类型--》当参数传递在函数中修改---》会影响原来的
            //var s []int=[]int{3,4,5}
            //testS(s)
            //fmt.Println("调用之后:",s) //[999 4  5]
            // 为什么?go语言的参数传递都是copy传递---》因为切片是个引用(地址,指针)--》把切片复制了一份传入了
            // 由于切片是引用,在函数中根据引用改了值,改了原来的底层数组,大家都会受影响

             // 8.1 演示二
            //var s []int=make([]int,3,4)
            //testS(s)
            //fmt.Println("调用之后:",s) //[999 0  0]


            // 9 copy 切片
            //var a [100000]int
            //var b =a[:3]
            //fmt.Println(b)  //[0 0 0]
            //b[0]=999
            //b[2]=222
            //fmt.Println(b) //[999 0 0]  使用b,虽然只用3个值,但是底层数组很大,内存占用大
            //// 把b这个切片,copy另一个新切片上
            //var c= make([]int,2,2)  // 基于的底层数组,数组大小是3
            //copy(c,b)
            //fmt.Println(c)  // [999 0]
            //// copy两个切片,上面的案例是两个切片长度一样,如果不一样呢? 拉链

             //10 多维切片-->每一层都要初始化
            //var s [][]int=make([][]int,2,2)
            //var s [][]int
            //fmt.Println(s[0])
            //fmt.Println(s[0][0])  // 报错,第二层没有初始化

            var s [][]int=[][]int{{2,3},{4,4,4,5},{6,7,8}}
            fmt.Println(s)
            fmt.Println(s[0][1])
            // 循环切片  两层for循环,跟数组一样




        }

        func testS(s []int)  {  // 在函数中追加切片,一定要注意有没有超过容量,如果超过了容量,超过后再改的值,就不会影响原来的了
            fmt.Println(s) //[0 0 0]
            s[0]=999   //[999 0 0]  会影响原来的
            s=append(s,888,777) //[999 0 0 888 777],追加了超过了底层数组,不依赖于原来的数组了
            s[1]=666   //[999 666 0 888 777]  // 不影响原来的数组了
            fmt.Println(s)  // [999 666 0 888 777]

        }

2 python列表底层实现

        // python 的列表是用c语言实现的
        // c语言的结构体实现的,底层就是数组
        {
          变量1---》列表的长度
          变量2 ---》指向了底层数组---》涉及到扩容,这个就变了
          变量3--->底层数组大小
        }


        [0x111,0x222,0x333]   
        0x111:1
        0x222:'lqz'
        0x333:[1,2,3]

        // 插入刘亦菲
        [0x111,0x444,0x222,0x333]   --->底层数组扩容--》列表插入操作,时间和空间复杂度都是o(n)
        0x111:1
        0x444:"刘亦菲"
        0x222:'lqz'
        0x333:[1,2,3]

        // 移除刘亦菲
        [0x111,0x222,0x333,None] --->移除,右侧位置全都往左移,时间和空间复杂度都是o(n)

        // 最后一个位置插入值,不需要重新构造新数组,直接在原来基础上插入即可

        // 插入两个值,超过了底层数组大小---》扩容


3 map

        //1 什么是 map
            key-value:python中的字典,但是在go中,在定义阶段,key和value类型固定了,后期不能改
            js:对象
            python:字典---》最好用的
            go:map
            java:HashMap

        // 2 如何创建 map ?
        给 map 添加元素
        获取 map 中的元素
        删除 map 中的元素
        获取 map 的长度
        Map 是引用类型
        Map 的相等性

4 hash类型的底层实现



        // hash冲突解决

        1 开放寻址法(open addressing)
        开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还
		是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。

        2 再哈希法
        方法是按顺序规定多个哈希函数,每次查询的时候按顺序调用哈希函数,调用到第一个为空的时候返回不存在,调用到此键的时候返回
		其值。

        3 链地址法(普遍常用:java  hashmap---》红黑树实现原理)
        将所有关键字哈希值相同的记录都存在同一线性链表中,这样不需要占用其他的哈希地址,相同的哈希值在一条链表上,按顺序遍历就
		可以找到

        4 公共溢出区
        其基本思想是:所有关键字和基本表中关键字为相同哈希值的记录,不管他们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填
		入溢出表。



        https://blog.csdn.net/General_zy/article/details/122038164

5 字符串

        package main

        import "fmt"

        // 字符串--》go语言中存储---》utf8存储
        // python:2.x版本     3.x版本字符串的编码unicode
        func main() {
            // 1 字符串长度,len指的是字节
            //var s ="hello world"
            //fmt.Println(len(s))  // 11个字符,11 字节
            //var s ="hello world 中国"
            //fmt.Println(len(s))  //14个字符,18 字节  ,utf-8编码一个中文字符,占3个字节

            // python中的len指的是字符数

            // 字符长度
            //fmt.Println(utf8.RuneCountInString(s)) // run 是字符  14




            //2 for循环字符串
            //var s ="hello world"
            //var s ="hello world 中国"

            //for _,v:=range s{  // range循环的就是字符
            //	fmt.Println(string(v))   // rune类型
            //}

            //for i:=0;i<len(s);i++{  // 按字节循环,转成字符串会出乱码
            //	fmt.Println(string(s[i]))
            //}
            //for i:=0;i<utf8.RuneCountInString(s);i++{  // 按字节循环,转成字符串会出乱码
            //	fmt.Println(string(s[i]))  // 按字节取,取出来转还是有问题,还是要用range循环
            //}


            // 3 字符串是不可变的
            //var s ="hello world 中国"
            ////s[0]='h'
            //fmt.Println(s[0])  // s是unicode编码的104


            // 4 通过字节切片
            //var b []byte=[]byte{104,105,106,'a'}
            //fmt.Println(string(b))  //hija


            // 5 根据字符切片构造
            //var b []rune=[]rune{104,105,10688,'中'}
            //fmt.Println(string(b))  //hij中


            // 6 字符串和字节切片,和字符切片相互转换
            // 根据字符取值---》没有操作---》转成字符切片,再取
            //var s ="hello world 中国"
            //var b []rune=[]rune(s)
            //fmt.Println(b[12])  //20013
            //fmt.Println(string(b[13]))


            var s ="hello world 中国"
            var b []byte=[]byte(s)
            fmt.Println(b[10])  //100
            fmt.Println(string(b[10]))



        }

posted @ 2022-06-07 16:44  风花雪月*  阅读(55)  评论(0编辑  收藏  举报