Go入门:五、goroutine和channel

这是我Go学习的第五篇笔记,学习的是go的语言的其他特性,这些特性是其他语言所不具备的。这次主要学习的是goroutine和channel。

我的语言学习过程一般分为下面几个:

1. 变量和数据类型
2. 流程控制方法
3. 函数声明和调用
4. 面向对象
5. 语言特性
6. 常用标准库

goroutine介绍和使用

Go语言中,每个并发执行的单元称为goroutine(可类比线程)。当一个程序启动时候,main函数在一个main goroutine中运行。如果想要创建新的goroutine,使用go关键字!

语法

创建一个新的 goroutine

go 函数名()

channel是goroutine的通信机制,比如创建一个能够接收int类型的channel,使用make创建:

// 创建一个channel
ch := make(chan int)

// 将i发送到channel
i := 1
ch <- i

// 从channel中接收数据
j := <-ch

// 丢弃channel中的数据
<-ch

同样,可以定义接收任意数据类型的channel

ch := make(chan interface{})

使用

并发通常应用在io操作较密集的地方,比如发起多个网络请求!通常串行发起网络请求是很慢的,大部分时间浪费在等待网络io上了。通过并发去请求能够显著提高整体耗时!

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

// 发起一个http get请求
// 把获取到的内容写入到channel
func httpget(url string, chc chan map[string]string) {
	resp, err := http.Get(url)
	if err != nil {
		// 错误处理
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		// 错误处理
	}
	var i = make(map[string]string)
	i[url] = string(body)
	chc <- i
}

func main() {
	chc := make(chan map[string]string)
	go httpget("http://www.baidu.com/", chc)
	go httpget("http://www.qq.com/", chc)
	// 等待完成
	var content map[string]string
	content = <-chc
	fmt.Println(content)
	content = <-chc
	fmt.Println(content)
}

Go入门:四、面向对象

这是我的Go学习笔记的第四篇,面向对象!现代语言几乎都会面向对象进行了支持!当然,Go也具备面向对象的特性!

我的语言学习过程一般分为下面几个:

1. 变量和数据类型
2. 流程控制方法
3. 函数声明和调用
4. 面向对象
5. 语言特性
6. 标准库

Go语言中的面向对象有点特殊。在Go语言里面,没有显式的class、extends等面向对象语言经常使用的关键词,但是却有面向对象的特性。看看Go怎么实现的把!

创建一个类

按照我的理解,类实际上就是某种模板,这个模板里面含有有限多个属性和方法。在Go里面,定义这个模板的语法使用type来实现!

比如单个int类型可以构成一个类(没错,你甚至可以在int数据类型上定义一些操作)

type num int

比如某几种复合类型构成的类

type human struct {
    name string
    age int
    weight int
    stature int
    ...
}

上面的type姑且称之为类,方法之后会通过某种方式绑定在这个类上!

(更多…)

Go入门:三、函数的声明和调用

这是我Go学习笔记的第三篇!接下来学习的是Go的函数声明和调用。

我的语言学习过程一般分为下面几个:

1. 变量和数据类型
2. 流程控制方法
3. 函数声明和调用
4. 面向对象
5. 语言特性
6. 标准库

函数声明

func 函数名称(参数表) 返回值类型 {
   // 函数体
}

写一个函数是非常简单的,掌握语法格式就可以了。函数是一个功能的封装,能让函数体内的代码得到很好的复用。

比如我要输出个人信息,我可以把个人信息封装到函数里面,后续直接调用这个函数而不是每次都print一堆信息了

package main

import "fmt"

// 定义一个能够打印个人信息的函数
func info() {
	fmt.Println("姓名: hc")
	fmt.Println("性别: 男")
	fmt.Println("职位: 程序员")
}

func main() {
        // 调用上面定义的函数
	info()
}

上面定义的函数没有参数,也没有返回值,非常简单的一个函数。如果我想让姓名可变,那么可以定义一个带有参数的函数

package main

import "fmt"

// 定义一个能够打印个人信息的函数
// 这里加入了name参数,类型为string
func info(name string) {
	fmt.Printf("姓名: %s\n", name)
	fmt.Println("性别: 男")
	fmt.Println("职位: 程序员")
}

func main() {
        // 调用的时候传入参数
	info("hc")
}

接下来定义一个有意义的带有参数和返回值的函数,比如计算一个数字的绝对值

package main

import "fmt"

// 返回一个数字的绝对值
func abs(n int) int {
	if n < 0 {
                n = -n
	}
	return n
}

func main() {
	fmt.Println(abs(-1))
}

参数传递

关于go的参数传递,作为一个新手会有些疑惑的地方。go语言只有传值,没有传引用。传入指针也是传入指针的一个拷贝。

传值

函数内部改变参数的值,不改变原值。比如:

i := -1
fmt.Println(abs(i)) // 输出的是1
fmt.Println(i) // 输出的是-1,原值并没有被改变

传指针

传入变量的地址,可以改变原来的变量

package main

import "fmt"

func abs(n *int) int {
	if *n < 0 {
		*n = -*n
	}
	return *n
}

func main() {
	var i int
	i = -1
	fmt.Println(abs(&i)) // 输出的是1
	fmt.Println(i) // 输出的还是1,i原值被改变了
}

一个比较经典的传引用的例子是交换两个变量的值

func swap(x *int, y *int) {
    var temp int
    temp = *x
    *x = *y
    *y = temp
}
// 调用
x := 1
y := 2
swap(&x, &y) //x变成2,y变成1了

可以看到,函数的参数是一个地址。(&在变量的前面是取地址运算符),使用*来从地址取值。

map参数

当我们把map作为参数传递到函数里面时,在函数内改变map值的时候,发现外部的map值也被改变了。map传入的是指针?

通过查资料我们可以发现,创建map的方法如下:

// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap

返回的是一个map的指针!当我们把map传入函数的时候,实际上传递的是一个指针。

chan

func makechan(t *chantype, size int64) *hchan

和map一样的含有chan类型,chan也是一个指针,作为参数传递的也是一个指针类型。

slice

slice和map,chan不太一样。slice是结构体和元素指针的混合结构。

参考:https://www.flysnow.org/2018/02/24/golang-function-parameters-passed-by-value.html

Go入门:一、变量和数据类型

这是学习Go语言的第一篇笔记,主要学习的是变量和基本数据类型。如果您也在开始学习Go语言,那么这篇笔记一定能帮助您学习的更快!

我的语言学习过程一般分为下面几个:

1. 变量和数据类型
2. 流程控制方法
3. 函数声明和调用
4. 面向对象
5. 语言特性
6. 标准库

变量声明

Go语言的变量声明有三种

第一种,var identifier type

// 先声明后赋值
var identifier type
identifier = value
// 声明并且赋值
var identifier type = value

开始实战一下!比如声明一个int类型变量

var i int
i = 1
// 或者
var i int = 1
(更多…)

Go入门:二、流程控制

这是我的go语言学习笔记的第二篇,go语言的流程控制。流程控制是计算机语言的基本组成部分。一般的流程控制有顺序,分支,循环。这次来学习一下go语言的流程控制都有哪些,语法是什么样的。

我将会通过以下步骤来入门go语言

1. 变量和数据类型
2. 流程控制方法
3. 函数声明和调用
4. 面向对象
5. 语言特性
6. 标准库

条件分支

go语言的条件分支有: if语句,if…else…语句,switch…case…语句。和大多数语言差别不大!

if 语句语法

// 纯if
if 条件语句 {
    表达式
}

// 带有else 的 if
if 条件语句1 {
    表达式1
} else 条件语句2 {
    表达式2
}

// 带有if else 的if
if 条件语句1 {
    表达式1
} else if 条件语句2 {
    表达式2
} else {
    表达式3
}

Go语言的 if 语句没有括号(for,switch也没有)!习惯了括号的需要反这个习惯!

(更多…)

linux 创建虚拟内存

场景

vps内存不大,编译软件不太够用,因此需要增加虚拟内存。

步骤

使用dd命令创建一个swap文件,大小为1G

dd if=/dev/zero of=/home/swap bs=1024 count=1024000

将文件格式转换为swap格式的

mkswap /home/swap

再用swapon命令把这个文件分区挂载swap分区

swapon /home/swap

为防止重启后swap分区变成0,要修改/etc/fstab文件

vi /etc/fstab

在文件末尾(最后一行)加上

/home/swap swap swap default 0 0

如果要删除交换分区和交换文件,逆着上面的顺序操作:

  1. 先删除/etc/fstab文件中添加的交换文件行
  2. 停用交换文件swapoff /root/swapfile
  3. 删除交换文件rm -fr /root/swapfile

mysql varchar类型探秘

mysql中varchar能够存储可变长度的字符串。过去我做的诸多业务中,一般存储短字符串的需求,都会使用varchar类型,并且定义长度为255,也就是varchar(255)。

不过为了探究varchar这种类型到底是怎么存储的,它的最大长度能达到多少,我决定区翻一下mysql文档学习一下。

varchar怎么存储

经过一番了解,varchar最大能存储的长度为65535字节。存储字符串的时候,会将字符串的长度存在首部,接着才是内容。

当varchar存储的字符个数小于或等于255的时候,首部需要一个字节来记录字符的个数。当内容大于255的字符的时候,首部需要2个自己来保存长度。

varchar能存的最大字符长度

理论上,varchar能够存储65535个字节,但是由于首部会占用两个字节,因此这会让varchar可用的存储空间变成了65533字节。如果定义的列是非空话,那最大是65533,如果定义的列允许NULL,那么null会占用一个额外的字节,因此最大只能存储65532个字节。

字节并不等于字符长度,varchar括号里面跟着的是字符长度,如果字符集是utf8的话,每一个字符统一会占用3个字节的长度,不管是汉子还是英文字符,因此最大能够存储的长度是65533/3 = 21844。如果字符集是utf8mb4那最大存储长度就更小了,为65533/4=16383。

(更多…)

python学习笔记:三、函数

这是第三篇python学习笔记,我们即将要学习python的函数。内容主要包括两个部分,函数的声明和函数的调用。

函数声明和调用

比如我们要声明一个“吃”的函数,语法如下:

def eat():
    return "eat something"
print(eat())

上面是一个没有参数的函数,做的事情很简单,声明一个函数,然后返回一个字符串。接下来要增加一个参数了。

def ead(food):
    return "eat %s" % food
print(eat('fruit'))

可以看到,上面声明了一个带有一个参数的函数,当然可以声明带两个,三个等。这些都是固定的,那么如果要声明一个不固定参数的函数呢?看代码

# 比如下面就是吃一份主食和一些小吃
# 小吃可以是一个,也可以是多个
def eat(food, *snack):
     result = []
     result.append("eat %s\n" % food)
     for i in snack:
         result.append("also eat some %s\n" % i)
     return ''.join(result)
print(eat('rice', 'banana', 'apple'))

参数传递的注意事项

函数的使用很简单,不过在传递参数的时候需要注意一些事情,比如传值和传引用。这中区别在大部分语言中都会存在,python也不例外。由于在python中,一切皆为对象,因此我们称呼这个为传不可变对象和传可变对象。

先用一个例子来说名这个问题

def change(a):
    a = 1
b = 2
change(b)
print(b)
# 结果是2

上面的就是传递不可变对象,不可变对象传入之后会新建一份拷贝,返回新的对象。不可变对象有:整型、字符串、元组。

再看看传可变对象

def change(a):
    a.append(4)
b = [1,2,3]
change(b)
print(b)
# 结果是 [1,2,3,4]

很显然,b被改变了,这相当于传了一个引用,直接修改了原值。常见的可变对象有列表,字典。

默认参数

声明函数的时候可以预定义一些默认参数,比如查询分页的时候,可以默认每一页20条数据,默认根据时间字段,默认倒序。这样既节省函数的记忆成本又不失灵活性。

def query(page, per_page=20, order_by = 'date', order = 'desc'):
     return "query page %d, per page %d order by %s order %s" % (page, per_page, order_by, order)
print(query(1))
# query page 1, per page 20 order by date order desc

函数调用指定参数名称

这是一个非常好的特性,调用函数的时候可以直指定参数的名称,比如我只想更改查询的循序,python中可以直接这样写

print(query(page=1, order='asc'))

而在其他的语言,可能要把所有的参数都写一遍,python这点非常棒!

openssl生成rsa公钥私钥

输入opensslj你如openssl交互界面

OpenSSL>

生成rsa私钥

OpenSSL> genrsa -out private_key 1024
Generating RSA private key, 1024 bit long modulus
………………….++++++
…………++++++
e is 65537 (0x10001)

生成公钥

OpenSSL> rsa -in private_key -pubout -out public_key
writing RSA key

python学习笔记:二、 流程控制

根据如何学习一门新的语言这篇文章的指导,现在的步骤就是学习这门语言的流程控制了。

通常来讲,流程控制有顺序,选择,循环这几种。python也不例外,也有这三种流程控制。下面的一一学习。

顺序

顺序语句很简单,从上往下依次顺序执行。如:

a = 1
b = 2
print(a+b)

上面的语句就是顺序执行的语句。

选择

选择通常使用if和switch,但python中只有if这一个选择语句。语法是:

if 满足条件1:
    语句1
elif 不满足条件1满足1条件2
    语句2
else 都不满足
    语句3

比如用一个实际的例子

a = 1
b = 2
if a > b:
    print('a>b')
elif a < b:
    print('a<b')
else:
    print('a==b')
# 输出的结果是
a<b

if后面的条件可以使用 and 或者 or 组合。

a = -1
b = -2
if a<0 and a>b:
    print('b<0')
# 输出结果
b<0

循环

python中的循环有for … in 和while循环。

for … in 循环语法

for 变量 in 可迭代序列

其中可迭代序列有:字符串,列表,元组,集合。比如用python遍历一个列表

li = ["a","b","c"]
for i in li:
    print(i)
# 输出结果
a
b
c

使用range生成列表,然后用for…in遍历。这个在python中非常常见

for i in range(0,10):
    print(i)

while循环语法

while 满足条件:
    一直执行

比如

a = 0
while a<10:
    a += 1
    print(a)