• Golang知识点汇总


    1.for select时,如果通道已经关闭会怎么样?如果只有一个case呢?

        select用于监听channel,在通道关闭时,channel会返回类型的空值,直接用`<-`取值时无论通道是否关闭都会有值返回.所以当使用select监听通道时,如果某个channel已经关闭,则每次都会执行这个case;

        如果只有一个case,当通道关闭后会进入死循环.

    var c = make(chan int)
    go func() {
    	time.Sleep(3 * time.Second)
    	c <- 8
    	close(c)
    }()
    for {
    	time.Sleep(1 * time.Second)
    	select {
    	case s, ok := <-c:
    		fmt.Println(s, ok)
    	default:
    		fmt.Println("i am default")
    	}
    }
    ~~~~~~~~~~~~~~~结果:~~~~~~~~~~~~~~~~~~~~
    i am default
    i am default
    8 true
    0 false
    0 false
    0 false
    0 false
    exit status 0xc000013a
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        处理办法: 可以通过对`ok`的值进行判断的方式处理:  

    var c = make(chan int)
    go func() {
    	time.Sleep(3 * time.Second)
    	c <- 8
    	close(c)
    }()
    for {
    	time.Sleep(1 * time.Second)
    	select {
    	case s, ok := <-c:
    		fmt.Println(s, ok)
    		if !ok {
    			c = nil
    		}
    	default:
    		fmt.Println("i am default")
    	}
    }
    ~~~~~~~~~~~~~~~~结果:~~~~~~~~~~~~~~~~~~~~~
    i am default
    i am default
    8 true
    0 false
    i am default
    i am default
    i am default
    i am default
    exit status 0xc000013a
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

    2.nil切片和空切片的区别

        切片是数组的引用,nil切片是一个未初始化的切片,它指向的数组地址为0, 相当于尚未被分配地址;空切片是已经初始化完成的切片,只不过这个切片内不含任何的值,它引用的数组是有地址的,并且这个地址固定.

    var (
    		a []int
    		b = make([]int, 0)
    		c = make([]int, 0)
    	)
    // SliceHeader是切片的底层结构
    fmt.Printf("a: %+v\n", *(*reflect.SliceHeader)(unsafe.Pointer(&a)))
    fmt.Printf("b: %+v\n", *(*reflect.SliceHeader)(unsafe.Pointer(&b)))
    fmt.Printf("c: %+v", *(*reflect.SliceHeader)(unsafe.Pointer(&c)))
    ~~~~~~~~~~~~~~~~~~~结果:~~~~~~~~~~~~~~~~~~~~~~~~~~
    a: {Data:0 Len:0 Cap:0}
    b: {Data:824635080432 Len:0 Cap:0}
    c: {Data:824635080432 Len:0 Cap:0}
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    3.字符串转成byte数组,会发生内存拷贝吗?

        会发生内存拷贝,在go中所有的类型强转都会发生内存拷贝.如果出现频繁的内存拷贝显然性能会受到影响,那怎么才能不发生内存拷贝呢?只需要把 StringHeader 的地址强转成 SliceHeader 就行:

    a := "aaa"
    aa := *(*reflect.StringHeader)(unsafe.Pointer(&a)) // 获取a的底层结构
    sa := *(*[]byte)(unsafe.Pointer(&aa))
    

    4.翻转含有中文、数字、英文字母的字符串

        可以通过go的内置类型rune(int32)实现:

    s := "中国zhongguo123加油"
    rs := []rune(s)
    var res []rune
    for i := len(rs) - 1; i >= 0; i-- {
    	res = append(res, rs[i])
    }
    fmt.Println(string(res))
    ~~~~~~~~~~~~结果:~~~~~~~~~~~~~~~~~
    油加321ouggnohz国中
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    5.拷贝大切片一定比小切片代价大吗?

        不是,切片在拷贝时都是相同大小的.切片拷贝本质上只是拷贝了切片的底层结构,由三个字段组成:Data,Len,Cap

    type SliceHeader struct {
    	Data uintptr
    	Len  int
    	Cap  int
    }
    

    6.对未初始化的的chan进行读写,会怎么样?为什么?

        会发生deadlock错误.

        当往未初始化的chan中写数据时(源码如下),如果未阻塞,则直接返回false,表示写入失败,如果能够阻塞,则通过throw抛出错误;

        

        当读取未初始化的chan时(源码如下):

        

        

    7.json包变量不加tag会怎么样

      1.  如果结果体中字段首写字母是小写,则该字段为私有字段,无法转换(a);
      2. 如果首写字母大写且无tag,可以转换,并且转换后的字段名和原字段名一致(B);
      3. 如果首写字母大写且有tag,可以转换,转换后字段名为tag中指定的名称(C).
    type test struct {
    	a string
    	B string
    	C string `json:"CC"`
    }
    t := test{
    	a: "a",
    	B: "b",
    	C: "c",
    }
    jsonT, _ := json.Marshal(t) fmt.Println(string(jsonT)) ~~~~~~~~~~~~~结果:~~~~~~~~~~~~~~~~~ {"B":"b","CC":"c"} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ~

    8.切片深浅拷贝

        浅拷贝复制的是指针,而指针指向的值只有一份,go中浅拷贝就是slice赋值操作;深拷贝复制的是指针指向的值,通过copy方法实现:

    a1 := []int{1, 2, 3, 4}
    b := a1
    fmt.Println("a1:", a1)
    b[0] = 11
    fmt.Println("浅拷贝: ", a1)
    
    a2 := []int{11, 22, 33, 44}
    fmt.Println("a2: ", a2)
    var c = make([]int, len(a2))
    copy(c, a2)
    c[0] = 0
    fmt.Println("深拷贝: ", a2)
    ~~~~~~~~~~~~~结果:~~~~~~~~~~~~~~ a1: [1 2 3 4] 浅拷贝: [11 2 3 4] a2: [11 22 33 44] 深拷贝: [11 22 33 44] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    9.make和new的区别

        make和new都用于内存分配.make只能用于map,slice,channel的内存分配,new返回的是指向Type的指针, make直接返回的是Type类型值.

    func make(t Type, size ...IntegerType) Type
    func new(Type) *Type
    

    10.Go 语言的局部变量分配在栈上还是堆上?

        go语言编译器会做逃逸分析,分析局部变量的作用域是否逃出了函数的作用域,要是没有就放到栈上,要是超出了函数的作用域就自动放到堆上.
        注意: 当使用fmt.println函数时,局部变量的作用域会超出函数的作用域,所以局部变量是在堆上.而println是内联函数,并没有使局部变量的作用域逃出函数的作用域,所以是在栈上.

    11.程序强制停止信号捕获

        当强制停止程序时,如使用ctrl+c停止一个程序时,可以通过os和os/signal进行捕获:
    var ch = make(chan os.Signal)
    signal.Notify(ch, os.Interrupt)
    // 主程序
    go func() {
    	for {
    		select {
    		case ss := <-ch:
    			fmt.Println(ss)
    		}
    	}
    }()

    12.两个interface能进行比较吗

         可以.interface中包含两个字段,类型(T)和值(V),两个interface相等必须满足T和V都相等.

    type tes struct {
    	name string
    }
    
    type tester interface{}
    
    var (
    	t1, t2 tester = &tes{"Tom"}, &tes{"Tom"}
    	t3, t4 tester = tes{"Tom"}, tes{"Tom"}
    )
    println(t1 == t2)  // false
    println(t3 == t4)  // true
    

        t1和t2的类型都是*tes,但是其值分别为t1和t2的地址,两个地址是不同的,所以false;

        t3和t4的类型都是tes,值是tes结构体,并且name字段对应的值都是"Tom",所以true.

    13.切片和数组的区别

      1. 切片是指针类型,数组是值类型;
      2. 切片的长度是不固定的(可以看做动态的数组),数组的长度是固定的;
      3. 切片比数组多一个容量属性;
      4. 切片的底层是数组.

    14.怎样获取结构体所有的tag 

        可以通过反射reflect来获取结构体的tag:

    type Author struct {
    	Name         string `json:"name"`
    	Age          int
    	Publications []string `json:"publication"`
    }
    t := reflect.TypeOf(Author{})
    for i := 0; i < t.NumField(); i++ {
    	tag := t.Field(i).Tag
    	println(tag)
    }
    
    ~~~~~~~~~~~~~结果:~~~~~~~~~~~~~~~~~
    json:"name"
    
    json:"publication"
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    15.结构体打印时,"%v","%+v","%#v"的区别

    • %v输出结构体各成员的值;
    • %+v输出结构体各成员的名称和值;
    • %#v输出结构体名称和结构体各成员的名称和值.

    16.go中GC(垃圾回收)原理 

        垃圾回收机制是Go一大特色.Go1.3采用标记清除法, Go1.5采用三色标记法,Go1.8采用三色标记法+混合写屏障.

        标记清除法

          a. 分为两个阶段:标记和清除

          b. 标记阶段:从根对象出发寻找并标记所有存活的对象.

          c. 清除阶段:遍历堆中的对象,回收未标记的对象,并加入空闲链表.

          d. 缺点是需要暂停程序STW.

        三色标记法

          将对象标记为白色,灰色或黑色.

          白色:不确定对象(默认色);黑色:存活对象. 灰色:存活对象,子对象待处理.

          标记开始时,先将所有对象加入白色集合(需要STW).首先将根对象标记为灰色,然后将一个对象从灰色集合取出,遍历其子对象,放入灰色集合.同时将取出的对象放入黑色集合,直到灰色集合为空.最后的白色集合对象就是需要清理的对象.

          这种方法有一个缺陷,如果对象的引用被用户修改了,那么之前的标记就无效了.因此Go采用了写屏障技术,当对象新增或者更新会将其着色为灰色.

        一次完整的GC分为四个阶段:

          a. 准备标记(需要STW),开启写屏障.
          b. 开始标记
          c. 标记结束(STW),关闭写屏障
          d. 清理(并发)
        基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,带来性能瓶颈.混合写屏障分为以下四步:

          a. GC开始时,将栈上的全部对象标记为黑色(不需要二次扫描,无需STW)
          b. GC期间,任何栈上创建的新对象均为黑色
          c. 被删除引用的对象标记为灰色
          d. 被添加引用的对象标记为灰色
          总而言之就是确保黑色对象不能引用白色对象,这个改进直接使得GC时间从 2s降低到2us.

    17.函数返回局部变量的指针是否安全?

        在Go里面返回局部变量的指针是安全的. 因为Go会进行逃逸分析,如果发现局部变量的作用域超过该函数则会把指针分配到堆区,避免内存泄漏.

    18.切片是怎么扩容的

      • 如果当前容量小于1024,则判断所需容量是否大于原来容量2倍,如果大于,当前容量加上所需容量;否则当前容量乘2.
      • 如果当前容量大于1024,则每次按照1.25倍速度递增容量,也就是每次加上cap/4,按照这个规则扩容后还会做内存对齐操作(并不是超过1024就*1.25).
  • 相关阅读:
    ArcGIS 9.4的技术展望 (转)
    逻辑斯蒂方程的应用
    Logistic混沌映射(转)
    投入产出分析的应用和编制方法
    在matlab中实现逻辑斯蒂映射
    国内的城市GIS面临的机遇和挑战(转载)
    arcgis中曼哈顿距离、路网距离、near工具、Point Distance工具的使用
    apache no services installed 安装apache后没有服务
    LoadRunner录制脚本问题收集
    16进制枚举值 转换为枚举类型
  • 原文地址:https://www.cnblogs.com/wangtaobiu/p/16248763.html
Copyright © 2020-2023  润新知