Golang中strcut结构体的的值方法和指针方法

平时我们在写struct的时候,经常会用到一些方法,有些方法是我们熟悉的普通方法,在golang中我们称之为值方法,而另一种则是指针方法。

type Person struct {
    Firstname string
    Lastname string
    Age uint8
}
// 值方法
func (p Person) show() {
    fmt.Println(p.Firstname)
}

// 指针方法
func (p *Person) show2() {
    fmt.Println(p.Firstname)
}

可以看到所谓的值方法与指针方法在编写的时候,只是有无*号的区别,这个*就是指针的意思。

那么用法又有何不同呢?

// 值方法
func (p Person) setFirstName(name string) {
	p.Firstname = name
}
// 指针方法
func (p *Person) setFirstName2(name string) {
	p.Firstname = name
}

func main() {
	p := Person{"sun", "xingfang", 30}

	//不一致的情况
	p.show() // sun 修改前
	p.setFirstName("tom")	// 值方法
	p.show() // sun, 未变化

	p.show() // sun 修改前
	p.setFirstName2("tom")	// 指针方法
	p.show() // tom 修改后的tom
}

通过上面的输出我们可以看到,当调用值方法setFirstName后,输出的还是原来的值sun,而调用指针方法 setFirstNam2后,则输出的是新值。主要原因就是值方法在传递总结体的时候,用的只是原来结构体的一个副本,做的任何修改也只是对副本的修改,而打印的还是原来的结构体,两者互不影响。而指针方法,传递的则是指向结构体指针的值副本,指针值一样(X012242R424),指定的都是底层的数据结构,所以才会出现这种情况。

总结:

  1. 值方法的接收者是该方法所属的那个类型值的一个副本。我们在该方法内对该副本的修1改一般都不会体现在原值上,除非这个类型本身是某个引用类型(比如切片或字典)的别名类型。 而指针方法的接收者,是该方法所属的那个基本类型值的指针值的一个副本。我们在这样的方法内对该副本指向的值进行修改,却一定会体现在原值上。
  2. 一个自定义数据类型的方法集合中仅会包含它的所有值方法,而该类型的指针类型的方法集合却囊括了前者的所有方法,包括所有值方法所有指针方法
    严格来讲,我们在这样的基本类型的值上只能调用到它的值方法
    但是,Go 语言会适时地为我们进行自动地转译,使得我们在这样的值上也能调用到它的指针方法。本示例中的 p.show() 在调用的时候会自动转换成 show2() 这种指针方式,可以试着将例子中的 show() 改成 show2() 输出结果是一样的)比如,在Cat类型的变量cat之上,之所以我们可以通过cat.SetName(“monster”)修改猫的名字,是因为 Go 语言把它自动转译为了(&cat).SetName(“monster”),即:先取cat的指针值,然后在该指针值上调用SetName方法。以上是由“郝林”老师在“Go语言核心36讲”专栏中总结。
  3. 两种写法在使用接口的时候也会有所不同。

Golang中的unsafe.Sizeof()简述

测试环境:
系统 win7 64位
go version: go1.10 windows/amd64

我们先看一下代码的输出

package main

import "unsafe"

func main() {
	// string
	str1 := "abc"
	println("string1:", unsafe.Sizeof(str1)) // 16
	str2 := "abcdef"
	println("string2:", unsafe.Sizeof(str2)) // 16
	
	// 数组
	arr1 := [...]int{1, 2, 3, 4}
	println("array1:", unsafe.Sizeof(arr1)) // 32 = 8 * 4

	arr2 := [...]int{1, 2, 3, 4, 5}
	println("array2:", unsafe.Sizeof(arr2)) // 40 = 8 * 5

	// slice 好多人分不清切片和数组的写法区别,其实只要记住[]中间是空的就是切片,反之则是数组即可
	slice1 := []int{1, 2, 3, 4}
	println("slice1:", unsafe.Sizeof(slice1)) // 24

	slice2 := []int{1, 2, 3, 4, 5}
	println("slice2:", unsafe.Sizeof(slice2)) // 24

	slice3 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	println("slice3:", unsafe.Sizeof(slice3)) // 24
}
1、字符串类型
为什么字符串类型的 unsafe.Sizeof() 一直是16呢?
实际上字符串类型对应一个结构体,该结构体有两个域,第一个域是指向该字符串的指针,第二个域是字符串的长度,每个域占8个字节,但是并不包含指针指向的字符串的内容,这也就是为什么sizeof始终返回的是16。
组成可以理解成此结构体
typedef struct {
    char* buffer;
    size_tlen;
} string;
2、数组类型
编译的时候系统自动分配内存,int的长度是由系统平台来决定的,我用的是64位的系统,所以一个int 代表的就是 int64 数据类型,每个数字占用8个字节,成员数组元素个数正好等于输出的值。byte(1字节),uint8(1字节),uint16(2字节),uint32(4字节),uint64(占用8字节), byte是uint8的别名,这些全是无符号型的,对应的还有有符号型的,区别也就是一个值范围不同而已。

不同数据类型占用字节大小如下:

func main() {
	var a uint8
	a = 2
	fmt.Println("uint8 type size:", unsafe.Sizeof(a))

	var b uint16
	b = 2
	fmt.Println("uint16 type size:", unsafe.Sizeof(b))

	
	var c uint32
	c = 2
	fmt.Println("uint32 type size:", unsafe.Sizeof(c))
	
	var d uint64
	d = 2
	fmt.Println("uint64 type size:", unsafe.Sizeof(d))
}

数据类型

具体类型 取值范围
int8 -128到127
uint8 0到255
int16 -32768到32767
uint16 0到65535
int32 -2147483648到2147483647
uint32 0到4294967295
int64 -9223372036854775808到9223372036854775807
uint64 0到18446744073709551615
3、切片类型
可以看到切片和数组还是有些不一样的,我们看一下官方包的解释 /src/unsafe/unsafe.go
// Sizeof takes an expression x of any type and returns the size in bytes
// of a hypothetical variable v as if v was declared via var v = x.
// The size does not include any memory possibly referenced by x.
// For instance, if x is a slice, Sizeof returns the size of the slice
// descriptor, not the size of the memory referenced by the slice.
意思是说如果是slice的话,则返回的是slice描述符的长度,而不是slice的内存长度。

数据结构与算法

平时开发中,一般很少用到手动来写算法的情况,但实际上我们一直在接触一些数据结构与算法,如JAVA中的ArrayList 就用到了数据结构与算法,从名字可以看到了用到了Array这种数组结构和链表结构。下面根据目前学习的情况做一个总结。

数据结构

我们常见的数组结构一般有:

  • Array数组、
  • Stack栈、
  • Heap堆、
  • Queue队列
  • Hash 哈希类型
  • LinkedList 链表,其中又分为单向链表、双向链表、还有最少用的环形链表
  • Tree 这个Tree分的太多了,如B-Tree、 B+Tree(mysql使用)、Binary Search Tree二叉搜索树 、AVL高度平衡树、Red Black Tree红黑树 等
数据结构

http://www.bigocheatsheet.com/

 

算法

一般开发中用到的基本上排序算法居多,而算法大体上又分为比较排序和非比较排序。我们常用的比较排序算法有(参考:http://www.cnblogs.com/eniac12/p/5329396.html):

  • 快速排序 (Quick Sort)
  • 冒泡排序 (Bubble Sort)
  • 插入排序 (Insertion Sort)
  • 堆排序 (Heap Sort)
  • 归并排序 (Merge Sort)

而非比较排序算法有(由于排序算法时间复杂度是线性的,有时候我们也称此算法线性排序,参考http://www.cnblogs.com/eniac12/p/5332117.html):

  • 计数排序(Counting sort)
  • 基数排序 (Radix Sort)
  • 桶排序 (Bucket Sort)

http://www.bigocheatsheet.com/

 

Bucket sort 排序

Bucket sort 排序 (图出自:极客时间)

计数排序 Counting sort

计数排序 Counting sort (图出自:极客时间)

注意:
有些算法是属于稳定算法,也有些属于非稳定算法。所谓的稳定是指相同的排序值原来的顺序经过排序后,前后顺序并不改变。就像我们平时数据库中根据两个字段进行排序的时候一样。

算法时间复杂度和空间复杂度
时间复杂度:https://baike.baidu.com/item/时间复杂度/1894057
空间复杂度:https://baike.baidu.com/item/空间复杂度

复杂度大致可分为:

  • O(1) 最好
  • O(log n)
  • O(n)
  • O(n log n)
  • O(n^2)
  • O(2^n)
  • O(n!) 最差

O(log n):当数据增大n倍时,耗时增大log n倍(这里的log是以2为底的,比如,当数据增大256倍时,耗时只增大8倍,是比线性还要低的时间复杂度)。二分查找就是O(logn)的算法,每找一次排除一半的可能,256个数据中查找只要找8次就可以找到目标。

O(n log n):同理,就是n乘以logn,当数据增大256倍时,耗时增大256*8=2048倍。这个复杂度高于线性低于平方。归并排序就是O(nlogn)的时间复杂度。

O(n^2),就代表数据量增大n倍时,耗时增大n的平方倍,这是比线性更高的时间复杂度。比如冒泡排序、插入排序、选择排序,就是典型的O(n^2)的算法,对n个数排序,需要扫描n x n次。

Heap And Stack 堆与栈的区别

堆与栈的区别

推荐:https://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/

栈是上下顺序存储的,且“先进后出LIFO”规则,只能删除顶部的元素,而堆是没有特定的顺序的存储,您可以删除任意元素。堆分配需要维护分配的内存和未分配的内存的完整记录,以及一些开销维护以减少碎片,找到足够大以适应请求大小的连续内存段,等等。内存可以随时释放,留出自由空间。有时,内存分配器将执行维护任务,比如通过将分配的内存到处移动来对内存进行碎片整理,或者在运行时进行垃圾收集——当内存不再处于作用域中时对其进行标识并释放它。

1. 栈用在线程中,程序执行时由线程创建有限数量的栈空间,当线程结束的时候会自动回收,属于系统级。
堆一般是由应用程序在启动时创建,由应用程序回收,属于应用级。

The stack is attached to a thread, so when the thread exits the stack is reclaimed. The heap is typically allocated at application startup by the runtime, and is reclaimed when the application (technically process) exits.

2. 栈的大小固定,通常在程序启动时已经确定了最大大小(部分语言支持动态扩容)。如果超出会出现“stack overflow”异常,经常在“递归”函数里出现。
堆是由”动态分配“的,其大小不固定,你可以在任何时间创建,再在任何时间回收(还是受限限系统虚拟内存 (ie: RAM and swap space))。

3. 每个线程可有一个堆栈,而应用程序通常只有一个堆(尽管对于不同类型的分配有多个堆并不罕见)

4. 栈的读取速度要更快。因为分配内存的机制,只是移动指针,而堆还要做查找等操作。

Which is faster – the stack or the heap? And why?

The stack is much faster than the heap. This is because of the way that memory is allocated on the stack. Allocating memory on the stack is as simple as moving the stack pointer up.

 

Variables allocated on the stack, or automatic variables, are stored directly to this memory. Access to this memory is very fast, and it’s allocation is dealt with when the program is compiled. Large chunks of memory, such as very large arrays, should not be allocated on the stack to avoid overfilling the stack memory (known as stack overflow). Stack variables only exist in the block of code in which they were declared. For example:

堆和栈在内存分配方面的不同:http://timmurphy.org/2010/08/11/the-difference-between-stack-and-heap-memory-allocation/

5. 堆和栈在用在函数变量的时候有些不同,一般动态创建的变量是存储在堆heap中,访问速度慢一些,而放在栈里的变量访问速度要快的多。

Variables allocated on the heap, or dynamic variables, have their memory allocated at run time (ie: as the program is executing). Accessing this memory is a bit slower, but the heap size is only limited by the size of virtual memory (ie: RAM and swap space). This memory remains allocated until explicitly freed by the program and, as a result, may be accessed outside of the block in which it was allocated.

如编码中的定义一个变量 int i = 20 则变量 i 存储在stack中;创建一个对象 user = new Account(); //这时user变量则存储在heap中。

堆与栈用法示例

int foo()
{
  char *pBuffer; //<--nothing allocated yet (excluding the pointer itself, which is allocated here on the stack).
  bool b = true; // Allocated on the stack.
  if(b)
  {
    //Create 500 bytes on the stack
    char buffer[500];

    //Create 500 bytes on the heap
    pBuffer = new char[500];

   }//<-- buffer is deallocated here, pBuffer is not
}//<--- oops there's a memory leak, I should have called delete[] pBuffer;

https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap

http://www.programmerinterview.com/index.php/data-structures/difference-between-stack-and-heap/

视频 https://www.youtube.com/watch?v=450maTzSIvA

https://blog.csdn.net/waidazhengzhao/article/details/76651923

Golang中的调度器

介绍(Introduction)
———————
Go 1.1最大的特色之一就是这个新的调度器,由Dmitry Vyukov贡献。新调度器让并行的Go程序获得了一个动态的性能增长,针对它我不能再做点更好的工作了,我觉得我还是为它写点什么吧。

这篇博客里面大多数东西都已经被包含在了[原始设计文档](https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw)中了,这个文档的内容相当广泛,但是过于技术化了。

关于新调度器,你所需要知道的都在那个设计文档中,但是我这篇博客有图片,所以更加清晰易懂。

带调度器的Go runtime需要什么?(What does the Go runtime need with a scheduler?)
——————————————————————————-
但是在我们开始看新调度器之前,我们需要理解为什么需要调度器。为什么既然操作系统能为我们调度线程了,我们又创造了一个用户空间调度器? Continue reading

Git中的git reset的三种参数的区别

我们平时在使用git的时候,经常会遇到需要撤销上次操作的需求,这时候需要用到git reset的这个命令,他的使用就是 “git-reset – Reset current HEAD to the specified state”, 注意这里主要操作的就是这个 HEAD

为了方便我们先了解一下 Git 的工作流程

相信大家对这个图已经很熟悉了,其中index也叫stage暂存区或者暂存索引区。git reset 共有三个互斥参数分别为”–soft”、”–mixed(默认参数)” 和 “–hard”,每种参数表示一种恢复模式,下面我们将分别看一下这git reset 三个参数的用法区别。 Continue reading

IO多路复用机制详解(转)

服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

(1)同步阻塞IO(Blocking IO):即传统的IO模型。

(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

(3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构。

(4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。不经常用。

高性能I/O设计模式Reactor和Proactor:https://blog.csdn.net/xiongping_/article/details/45152333

转自:https://blog.csdn.net/baixiaoshi/article/details/48708347