Docker保存当前容器镜像

当我们在容器里面安装了一些软件或者修改一些设置之后,我们希望能把修改同步到相关的镜像,下次用这个镜像生成容器的时候,可以略过搭建环境这一步了。

比如我现在有一个容器叫centos

$ docker ps
b58e39dca53d        centos              "/bin/bash"         3 minutes ago       Up 3 minutes        0.0.0.0:4000-4100->4000-4100/tcp   centos-huchao

进入容器

docker exec -it /bin/bash centos-huchao

安装一些软件之后,通过docker commit 容器名称 镜像名称 命令将我们的修改保存的新的镜像。

docker commit centos-huchao centos:web

上面的镜像名称我加了一个标签。

计算机语言学习指南

这篇文章讨论基于语言的基本要素,如何快速入门一种计算机语言。是一篇语言从学习到使用的指导手册,并且这种学习方式是一个系统的学习,相比于碎片化的学习,这种学习更加不容易遗忘。

语言的基本成分

语言的基本成分为数据运算控制传输。想想你学过的语言,是不是都是这样。归结语言的组成成分,学习一门语言可以从这四个方面下手,这四个方面掌握之后,对这个语言就有个最基本的了解了。

(更多…)

Go中使用json.Unmarshal interface{}之后的数据类型

有时候设计不好的情况下,一个json类型很难用固定的数据结构去表示,因此我们会用interface{}去表示它。

在使用inteface的时候,需要了解json的数据类型对应的go的默认类型,使用才会不会出现疑惑,它们的类型关系对应如下

JSONGo
booleanbool
数值float64
字符串string
array[]interface{}
objectmap[string]interface{}
nullnil

了解他们默认的类型转换,使用时候就不会出问题了。

Go语言的 make 和 new

new 和 make 是两个内置函数,主要用来创建并分配类型的内存。在我们定义变量的时候,可能会觉得有点迷惑,不知道应该使用哪个函数来声明变量,其实他们的规则很简单,

new 只分配内存,make 只能用于 slice、map 和 channel 的初始化。

下面我们就来具体介绍一下

new

在Go语言中,new 函数描述如下:

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

从上面的代码可以看出,new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针。同时 new 函数会把分配的内存置为零,也就是类型的零值。

(更多…)

Go单元测试与性能测试

在Go语言中做单元测试和性能测试是一件非常容易的事情——Go自带了测试工具包,testing包。

通常,测试代码和功能代码在同一个包中,测试代码以“_test”结尾。测试有两种类型,一种是单元测试(UnitTest),一种是性能测试(Benchmark)。接下来将会介绍一下如何写这两种测试。

单元测试

首先创建一个测试文件,命名foo_test.go(提示: 以test结尾

接下来是创建测试函数,Test_Foo(提示:测试函数以Test开头,传入*testing.T指针),代码如下:

func Test_Foo(t *testing.T) {

}

这里什么也没做,只是创建了一个空的测试函数,但确实是能运行的测试函数!如果你的编辑器支持测试提示(提示:vscode支持),那么你可以在函数上面看到一行小字,如下:

(更多…)

Go笔记六:常用标准库

这是我的Go学习的第六篇笔记,也是Go入门的最后一篇笔记。在大多数语言中,了解了变量和数据类型,流程控制,函数,面向对象,再加上标准库,就可以用这门语言去写一些项目了。

首先让我想想,在工作中通常会用语言频繁处理什么问题或者处理什么数据?最常见的应该是各种字符串操作,日期和时间,读写文件、socket等IO相关的操作!

字符串处理 — Strings

String提供了一组处理字符串的操作,常用的有:

  1. 判断一个字符串是否在另一个字符串中
  2. 分割字符串为[]string和组合[]string为一个字符串
  3. 字符串替换

太多了,就不一一列举了,这里列出一些常用的字符串操作。

字符串判断

// 判断子串substr是否在s中
func Contains(s, substr string) bool

字符串分割与合并

// 根据sep将字符串分割成一个数组
func Split(s, sep string) []string
// 将数组a用sep拼接成一个字符串
func Join(a []string, sep string) string

字符串转换

// 转换成大写
func ToUpper(s string) string
// 转换成小写
func ToLower(s string) string

字符串清理

// 去除首尾的cut字符串
func Trim(s string, cut string) string
// 去除左侧的cut字符串
func TrimLeft(s string, cut string) string
// 去除右侧的cut字符串
func TrimRight(s string, cut string) string
(更多…)

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
(更多…)