0%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package main

import (
"fmt"
"net/http"
"os"
"regexp"
"strconv"
"strings"
)

func httpGet(url string) (result string, err error) {
// https://www.gushiwen.org/default_2.aspx

resp, err := http.Get(url)
if err != nil {
fmt.Println("http.Get() err:", err)
return
}
defer resp.Body.Close()

buf := make([]byte, 1024*4)
for {
n, err := resp.Body.Read(buf)
if n == 0 || err != nil {
break
}
result += string(buf[:n])
}

return
}

// 爬取详细内容
func spiderContent(url string) (title, content string) {
result, err := httpGet(url)
if err != nil {
fmt.Println("httpGet() err:", err)
return
}
// 创建正则表达式对象 匹配标题
re := regexp.MustCompile(`<h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">(?s:(.*?))</h1>`)
if re == nil {
fmt.Println("re is nil")
return
}
// 使用创建的正则表达式对象对返回的result进行匹配FindAllStringSubmatch(需要匹配的字符串,匹配多少次,-1为匹配所有)
reTitle := re.FindAllStringSubmatch(result, 1)

for _, v := range reTitle {
title = strings.Replace(v[1], "\t", "", -1)
title = strings.Replace(title, "\n", "", -1)
title = strings.Replace(title, "\r", "", -1)
title = strings.Replace(title, " ", "", -1)
}

// 匹配内容
contsonID := url[32:44]
re = regexp.MustCompile(`<div class="contson" id="contson` + contsonID + `">(?s:(.*?))</div>`)
if re == nil {
fmt.Println("re is nil")
return
}
reContent := re.FindAllStringSubmatch(result, 1)

for _, v := range reContent {
content = strings.Replace(v[1], "\t", "", -1)
content = strings.Replace(content, "\n", "", -1)
content = strings.Replace(content, "\r", "", -1)
content = strings.Replace(content, " ", "", -1)
content = strings.Replace(content, "<br/>", "", -1)
content = strings.Replace(content, "<p>", "", -1)
content = strings.Replace(content, "</p>", "", -1)
}
return
}

func save(index int, titleArr, contentArr []string) {
f, err := os.Create(strconv.Itoa(index) + ".txt")
if err != nil {
fmt.Println("create file errr:", err)
return
}
defer f.Close()
n := len(titleArr)
for i := 0; i < n; i++ {
f.WriteString(titleArr[i] + "\n")
f.WriteString(contentArr[i] + "\n")
f.WriteString("\n")
}
return

}

// 爬取每一页的连接
func spiderPage(index int, isExit chan int) {
url := "https://www.gushiwen.org/default_" + strconv.Itoa(index) + ".aspx"
result, err := httpGet(url)
if err != nil {
fmt.Println("httpGet() err:", err)
return
}
// 创建正则表达式对象
re := regexp.MustCompile(`<p><a style="font-size:18px; line-height:22px; height:22px;" href="(?s:(.*?))"`)
if re == nil {
fmt.Println("re is nil")
return
}
reUrls := re.FindAllStringSubmatch(result, -1)

titleArr := make([]string, 0)
contentArr := make([]string, 0)

for _, v := range reUrls {
title, content := spiderContent(v[1])
titleArr = append(titleArr, title)
contentArr = append(contentArr, content)
}
// 保存到文件
save(index, titleArr, contentArr)
isExit <- index
}

func doWork(start, end int) {
fmt.Printf("正在抓取 %d 到 %d 页面数据\n", start, end)

isExit := make(chan int)

for i := start; i <= end; i++ {
go spiderPage(i, isExit)
}

for i := start; i <= end; i++ {
<-isExit
fmt.Printf("已经保存了:%d个\n", i)
}
}

func main() {
var start, end int
fmt.Println("请输入起始页:")
fmt.Scan(&start)
fmt.Println("请输入结束页:")
fmt.Scan(&end)

doWork(start, end)

}

关于正则表达式的模式修饰符:

  • (?i)即匹配时不区分大小写。表示匹配时不区分大小写。
  • (?s)即Singleline(单行模式)。表示更改.的含义,使它与每一个字符匹配(包括换行 符\n)。
  • (?m)即Multiline(多行模式) 。 表示更改^和$的 含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的 精确含意是:匹配\n之前的位置以及字符串结束前的位置.)
  • (?x):表示如果加上该修饰符,表达式中的空白字符将会被忽略,除非它已经被转义。
  • (?e):表示本修饰符仅仅对于replacement有用,代表在replacement中作为PHP代码。
  • (?A):表示如果使用这个修饰符,那么表达式必须是匹配的字符串中的开头部分。比如说”/a/A”匹配”abcd”。
  • (?E):与”m”相反,表示如果使用这个修饰符,那么”$”将匹配绝对字符串的结尾,而不是换行符前面,默认就打开了这个模式。
  • (?U):表示和问号的作用差不多,用于设置”贪婪模式”。

整体思路

Go并发聊天服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package main

import (
"fmt"
"io"
"net"
"strings"
"time"
)

// Client 连接的客户端
type Client struct {
// 用来存储要发送消息
C chan string
Name string
Addr string
}

// 用来存储在线的用户
var onlineMap map[string]*Client

var messages chan string

func init() {
// 初始化map
onlineMap = make(map[string]*Client)
// 初始化消息
messages = make(chan string, 128)
}

func makeMsg(cli *Client, msg string) string {
return fmt.Sprintf("用户[%s]:%s\n", cli.Name, msg)
}

// Manager 转发所有消息到用户
func Manager() {
for {

// 如果没有消息 这里会阻塞
msg := <-messages
fmt.Println(msg)
// 遍历在线用户 转发消息
for _, cli := range onlineMap {
cli.C <- msg
}
}
}

// 把消息发送给对应的用户
func writeMsgToClient(cli *Client, conn net.Conn) {
for {
msg := <-cli.C
_, err := conn.Write([]byte(msg))
if err != nil {
fmt.Println("[writeMsgToClient] err:", err)
}
}
}

func readClientMsg(cli *Client, conn net.Conn, isLogout, hasData chan bool) {
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
isLogout <- true
return
}
fmt.Println("server read client messages err:", err)
continue
}

// 如果读到数据长度为0 则用户退出
if n == 0 {
isLogout <- true
return
}

// 如果用户发送了消息 则hasData为true
hasData <- true

msg := string(buf[:n])
// 如果发送的是who 返回当前在线用户列表
if msg == "who" {
var onlinelist string
conn.Write([]byte("online user list:\n"))
for _, cli := range onlineMap {
onlinelist += fmt.Sprintf("name:%s\n", cli.Name)
}
conn.Write([]byte(onlinelist))
// 如果用户发送的消息是rename 则修改当前用户的名字
} else if len(msg) >= 6 && msg[:6] == "rename" {
cli.Name = strings.Split(msg, "|")[1]
conn.Write([]byte("rename success:\n"))
} else {
messages <- makeMsg(cli, string(buf[:n]))
}

}
}

func handleConn(conn net.Conn) {
defer conn.Close()
// 获取用户ip
cliAddr := conn.RemoteAddr().String()

// 把用户信息保存到结构体
client := Client{
C: make(chan string),
Name: cliAddr,
Addr: cliAddr,
}
// 把用户保存到map
onlineMap[cliAddr] = &client
// 把用户上线的消息放到全局chan
messages <- makeMsg(&client, "上线了")
// 用户是否退出了
isLogout := make(chan bool)
// 用户是否发消息了
hasData := make(chan bool)

// 每创建新增一个用户 就对应创建一个协程用来发送消息
go writeMsgToClient(&client, conn)
// 每创建新增一个用户 就对应创建一个协程用来接收用户发的消息
go readClientMsg(&client, conn, isLogout, hasData)

for {
select {
case <-isLogout:
// 如果isLogout中有值 把用户从onlineMap中删除 并广播用户下线
delete(onlineMap, client.Addr)
messages <- makeMsg(&client, "logout")
return
case <-hasData:
case <-time.After(time.Second * 15): // 15秒没有发送消息则超时
delete(onlineMap, client.Addr)
messages <- makeMsg(&client, "timeout")
return
}
}
}

func main() {
// 创建监听
listener, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("net.listen err", err)
return
}

// 专门负责转发消息 有消息时 遍历所有在线用户 把消息转发到所有用户
go Manager()

for {
// 阻塞等待用户连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept() err", err)
// 连接出错则跳过这个连接
continue
}
go handleConn(conn)
}

}

简单文件传输

文件传输分为客户端和服务端,通过下图的方式进行文件的传输
文件传输

客户端实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package main

import (
"fmt"
"io"
"net"
"os"
)

func sendFile(fp string, conn net.Conn) {
file, err := os.Open(fp)
if err != nil {
fmt.Println("open file faild")
return
}
buf := make([]byte, 1024*4)
for {
n, err := file.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("文件发送完毕")
return
}
return
}
conn.Write(buf[:n])
}
}

func main() {
// 获取文件路径
fmt.Println("请输入需要传输文件的路径:")
var filePath string
fmt.Scan(&filePath)

fileInfo, err := os.Stat(filePath)
if err != nil {
fmt.Println("err:", err)
return
}
fileName := fileInfo.Name()
fileSize := fileInfo.Size()
fmt.Printf("fileName:%s\nfileSize:%d\n", fileName, fileSize)

// 连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("net.Dial() faild")
return
}
defer conn.Close()
// 发送文件名
_, err = conn.Write([]byte(fileName))
if err != nil {
fmt.Println("conn.Write() faild")
return
}
buf := make([]byte, 4)
// 接收服务器返回
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read() faild")
return
}

if "ok" == string(buf[:n]) {
sendFile(filePath, conn)
}
}

服务端实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main

import (
"fmt"
"io"
"net"
"os"
)

func recvFile(fileName string, conn net.Conn) {
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println("os.OpenFile() faild")
return
}
defer file.Close()
buf := make([]byte, 1024*4)
for {
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("文件接收完毕")
return
}
fmt.Println("conn.Read() faild")
return
}
if n == 0 {
fmt.Println("文件接收完毕")
return
}
file.Write(buf[:n])
}
}

func main() {
listener, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("net.Listen() faild")
return
}
defer listener.Close()
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept() faild")
return
}
defer conn.Close()
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read() faild")
return
}
fileName := string(buf[:n])
conn.Write([]byte("ok"))
recvFile(fileName, conn)
}

什么是Socket?

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

Socket分类:

为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字,以供用户在设计网络应用程序时根据不同的要求来选择。这三种套接为流式套接字(SOCK-STREAM)、数据报套接字(SOCK-DGRAM)和原始套接字(SOCK-RAW)。

1.流式套接字(TCP)

它提供了一种可靠的、面向连接的双向数据传输服务,实现了数据无差错、无重复的发送。流式套接字内设流量控制,被传输的数据看作是无记录边界的字节流。在TCP/IP协议簇中,使用TCP协议来实现字节流的传输,当用户想要发送大批量的数据或者对数据传输有较高的要求时,可以使用流式套接字。

2.数据报套接字(UDP)

它提供了一种无连接、不可靠的双向数据传输服务。数据包以独立的形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会丢失或重复,并且不能保证在接收端按发送顺序接收数据。在TCP/IP协议簇中,使用UDP协议来实现数据报套接字。在出现差错的可能性较小或允许部分传输出错的应用场合,可以使用数据报套接字进行数据传输,这样通信的效率较高。

3.原始套接字(IP或者ICMP)

该套接字允许对较低层协议(如IP或ICMP)进行直接访问,常用于网络协议分析,检验新的网络协议实现,也可用于测试新配置或安装的网络设备。

Go Socket编程

Go提供net网络包实现基本的tcp/udp网络编程能力。使用net包非常简单。

服务端实现思路:

  • 首先启动一个监听器,声明传输协议与主机端口信息;
  • 启动一个无限循环不断从监听器接收客户端连接;
  • 为每个客户端连接创建一个处理协程,处理协程实现对客户端的响应。

客户端实现思路:

  • 首先客户端拨号连接服务端,声明传输协议与服务端主机和端口信息;
  • 准备一个读写缓冲的字节数组
  • 启动一个无限循环不断从终端读取用户输入,通过缓冲字节数组发送给服务端并等待服务端响应。

服务端实现 Server.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"net"
"strings"
"time"
)

func handlerConn(conn net.Conn) {
defer conn.Close()
// 拿到客户端地址
addr := conn.RemoteAddr().String()
fmt.Printf("[%s] connection success\n", addr)

buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
// 客户端主动关闭
if err.Error() == "EOF" {
fmt.Printf("[%s] exit\n", addr)
return
}

fmt.Printf("[%s] conn read err:%s\n", addr, err)
return
}
msg := string(buf[:n])
fmt.Printf("[%v] Received a message from [%s]:\n %s\n\n", time.Now().UTC(), addr, msg)
if "exit" == msg {
fmt.Printf("[%s] exit\n", addr)
return
}
conn.Write([]byte(strings.ToUpper(msg)))
}
}

func server() {
listener, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("listener err", err)
return
}
defer listener.Close()

for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("conn client err", err)
return
}

go handlerConn(conn)
}

}

func main() {
server()
}

-------------------------
[2019-08-07 03:35:21.6288183 +0000 UTC] Received a message from [127.0.0.1:62777]:
hello

[2019-08-07 03:35:28.3784244 +0000 UTC] Received a message from [127.0.0.1:62777]:
你好呀

[2019-08-07 03:35:35.9991891 +0000 UTC] Received a message from [127.0.0.1:62777]:
exit

客户端实现 Client.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
"bufio"
"fmt"
"net"
"os"
"time"
)

func received(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
addr := conn.RemoteAddr()
for {
n, err := conn.Read(buf)
if err != nil {
fmt.Println("read err:", err)
continue
}
msg := string(buf[:n])
fmt.Printf("[%v] Received a message from [%s]:\n %s\n\n", time.Now().UTC(), addr, msg)
}
}

func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("[client]: Dial err")
return
}
defer conn.Close()

go received(conn)

scan := bufio.NewScanner(os.Stdin)
for scan.Scan() {
line := scan.Text()
conn.Write([]byte(line))
if "exit" == line {
break
}
}

}
---------------
hello
[2019-08-07 03:35:21.6395571 +0000 UTC] Received a message from [127.0.0.1:8888]:
HELLO

你好呀
[2019-08-07 03:35:28.3891645 +0000 UTC] Received a message from [127.0.0.1:8888]:
你好呀

exit

1.database/sql标准包

sql包提供了保证SQL或类SQL数据库的泛用接口。使用sql包时必须注入(至少)一个数据库驱动。参见http://golang.org/s/sqldrivers 获取驱动列表。如使用mysql,则需要注入github.com/go-sql-driver/mysql。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package main

import (
"database/sql"
"fmt"
"os"
"strconv"

_ "github.com/go-sql-driver/mysql"
)

// Person pojo
type Person struct {
UserID int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}

func (p *Person) String() string {
return strconv.Itoa(p.UserID) + p.UserName + p.Sex + p.Email
}

// ErrHandle 错误处理
func ErrHandle(err error, where string) {
if err != nil {
fmt.Println("[ERR]:", err, where)
os.Exit(0)
}
}

func main() {
db, err := sql.Open("mysql", "root:toor@tcp(127.0.0.1:3306)/test?charset=utf8")
ErrHandle(err, "connection mysql faild")
defer func() {
err = db.Close()
ErrHandle(err, "db.close()")
}()

// 新增操作
// 准备sql语句
insertSQL := "insert into person (`username`,`sex`,`email`) VALUES (?,?,?)"
// 预执行语句,stmt的主要方法:Exec、Query、QueryRow、Close返回*Stmt声明句柄,stmt的主要方法:Exec、Query、QueryRow、Close
stmt, err := db.Prepare(insertSQL)
ErrHandle(err, "dn.prepar(insertSQL)")
// 执行
result, err := stmt.Exec("database/sql", "man", "[email protected]")
ErrHandle(err, "stmt.Exec(insertSQL)")
// 获取插入结果的id
id, err := result.LastInsertId()
ErrHandle(err, "LastInsertId()")
fmt.Println("LastInsertId:", id)

// 修改操作
updataSQL := "UPDATE PERSON SET sex = ? WHERE user_id = ?"
// 预执行语句
stmt, err = db.Prepare(updataSQL)
ErrHandle(err, "db.prepar(updataSQL)")
// 执行 修改上面新增的数据
result, err = stmt.Exec("woman", id)
ErrHandle(err, "stmt.Exec(updataSQL)")
id, err = result.RowsAffected()
ErrHandle(err, "RowsAffected()")
fmt.Println("RowsAffected:", id)

// 查询操作
// 1. 查询一条记录 必须定义一个用来接收查询结果的变量
resultPerson := new(Person)
// 准备SQL语句
querySQL := "SELECT person.username, person.sex, person.email FROM person WHERE user_id =?"
//查询一条,返回一条结果。并赋值到resultPerson这个结构体类型的变量中,就算查询到的是多条,返回的还是一条
err = db.QueryRow(querySQL, id).Scan(&resultPerson.UserName, &resultPerson.Sex, &resultPerson.Email)
ErrHandle(err, "db.QueryRow(querySQL)")
fmt.Printf("[query row]:%v\n", resultPerson)

// 2. 查询多条记录
querySQL = "SELECT * FROM person"
// 执行查询,返回多行
queryRows, err := db.Query(querySQL)
ErrHandle(err, "db.Query")
defer func() {
err = queryRows.Close()
ErrHandle(err, "queryRows.Close()")
}()
// 遍历结果集 *Rows.NEXT() bool
for queryRows.Next() {
err = queryRows.Scan(&resultPerson.UserID, &resultPerson.UserName, &resultPerson.Sex, &resultPerson.Email)
ErrHandle(err, "queryRows.Scan(&resultPerson)")
fmt.Printf("[query rows]:%v\n\n", resultPerson)
}

// 删除操作
delSQL := "delete from person where user_id = ?"
stmt, err = db.Prepare(delSQL)
ErrHandle(err, "db.Prepare(delSQL)")
// 执行 删除上面新增的数据
result, err = stmt.Exec(id)
ErrHandle(err, "stmt.Exec(delSQL)")
id, err = result.RowsAffected()
ErrHandle(err, "RowsAffected()")
fmt.Println("RowsAffected:", id)

// 事务操作
// 开启事务
tx, err := db.Begin()
ErrHandle(err, "db.Begin()")
// 开始事务操作
_, err1 := tx.Exec("UPDATE PERSON SET sex = ? WHERE user_id = ?", "woman", id)
_, err2 := tx.Exec("DELETE FROM person WHERE user_id = ?", id)
if err1 != nil || err2 != nil {
fmt.Println("事务操作出错,开始回滚")
// 事务回滚
tx.Rollback()
} else {
// 事务提交
tx.Commit()
}
}

2.jmoiron/sqlx”第三方包

sqlx为对database/sql的封装,提高对sql支持的易用性。其使用方式和database/sql相差不大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main

import (
"fmt"
"os"

_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)

// Person pojo
type Person struct {
UserID int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}

func (p *Person) String() string {
return string(p.UserID) + p.UserName + p.Sex + p.Email
}

// Db mysql连接
var Db *sqlx.DB

// 初始化
func init() {
// 初始化连接
database, err := sqlx.Open("mysql", "root:toor@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("connection mysql faild:", err)
return
}
Db = database
}

// ErrHandle 通用错误处理
func ErrHandle(err error, where string) {
if err != nil {
fmt.Println("[Err]:", err, where)
os.Exit(0)
}
}

// 新增
func insert() {
result, err := Db.Exec("insert into person (`username`,`sex`,`email`) VALUES (?,?,?)", "stu03", "man", "[email protected]")
ErrHandle(err, "mysql insert")
id, err := result.LastInsertId()
ErrHandle(err, "result.LastInsertId()")
fmt.Println("LastInsertId:", id)
}

// 修改
func updata() {
result, err := Db.Exec("UPDATE PERSON SET sex = ? WHERE user_id = ?", "woman", 1)
ErrHandle(err, "mysql updata")
id, err := result.RowsAffected()
ErrHandle(err, "result.RowsAffected()")
fmt.Println("RowsAffected:", id)
}

// 删除
func del() {
result, err := Db.Exec("delete from person where user_id = ?", 6)
ErrHandle(err, "mysql delete")
id, err := result.RowsAffected()
ErrHandle(err, "result.RowsAffected()")
fmt.Println("RowsAffected:", id)
}

// 查询
func query() {
// 定义一个slice接收查询结果
var ps []Person
err := Db.Select(&ps, "SELECT person.username, person.sex, person.email FROM person WHERE user_id =?", 1)
ErrHandle(err, "mysql select")
fmt.Println(ps)
}

func main() {
insert()
updata()
del()
query()
}

sqlx对事务的操作非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package main

import (
"fmt"
"os"

_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)

// Person pojo
type Person struct {
UserID int `db:"user_id"`
UserName string `db:"username"`
Sex string `db:"sex"`
Email string `db:"email"`
}

func (p *Person) String() string {
return string(p.UserID) + p.UserName + p.Sex + p.Email
}

// Db mysql连接连接池
var Db *sqlx.DB

// 初始化
func init() {
// 初始化连接
database, err := sqlx.Open("mysql", "root:toor@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("connection mysql faild:", err)
return
}

Db = database
}

// ErrHandle 错误处理
func ErrHandle(err error, where string) {
if err != nil {
fmt.Println("[ERR]:", err, where)
os.Exit(0)
}
}

// 事务管理
func mysqlTransaction() {
// 开启事务
tx, err := Db.Begin()
ErrHandle(err, "db.begin")

// 开始事务操作
_, err1 := tx.Exec("UPDATE PERSON SET sex = ? WHERE user_id = ?", "woman", 1)
_, err2 := tx.Exec("DELETE FROM person WHERE user_id = ?", 1)
if err1 != nil || err2 != nil {
fmt.Println("事务操作出错,开始回滚")
// 事务回滚
tx.Rollback()
} else {
// 事务提交
tx.Commit()
}
}

func main() {
mysqlTransaction()
}

sqlx自带连接池管理,在项目中可直接用连接池管理对mysql的访问,以实现对IO的访问控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
"fmt"
"os"
"time"

_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)

var db *sqlx.DB

func init() {
database, err := sqlx.Open("mysql", "root:toor@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("connection mysql faild")
}

//设置最大打开的连接数,设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。默认值为0表示不限制。
database.SetMaxOpenConns(512)
// 设置最大空闲连接数
database.SetMaxIdleConns(64)
// 设置超时时间
database.SetConnMaxLifetime(time.Second * 60)

db = database
}

// ErrHandle 错误处理
func ErrHandle(err error, where string) {
if err != nil {
fmt.Println("[ERR]:", err, where)
os.Exit(0)
}
}

func insert() {
result, err := db.Exec("insert into person (`username`,`sex`,`email`) VALUES (?,?,?)", "stu03", "man", "[email protected]")
ErrHandle(err, "mysql insert")
id, err := result.LastInsertId()
ErrHandle(err, "result.LastInsertId()")
fmt.Println("LastInsertId:", id)
}

func main() {
insert()
}

拓展:

3.gorm第三方包

ORM(Object Relation Mapping)中文翻译为对象关系映射,这是一种把数据库记录映射为对象类型的一种技术,它把对数据的的操作变成对对象的操作,而不用在意其内部的sql语句,这对许多基于OOP开发的程序员无疑是福音。go非常热门的orm框架 github.com/jinzhu/gorm ,已经实现了全功能的orm,使用方式请直接参考开发文档,该文档已提供中文支持:https://gorm.io/zh_CN/docs/index.html。 下面我们来简单使用一下其基本功能。

安装库:go get -u github.com/jinzhu/gorm

导入库及设置连接池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//导入库与mysql驱动
import(
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/jinzhu/gorm"
)

//定义一个通用的错误处理函数
func ErrorHandler(err error, where string) {
if err != nil {
fmt.Println("出现错误:", err, where)
os.Exit(1)
}
}


//连接数据库
var db *gorm.DB
//包初始化时连接
func init() {
var err error
db, err = gorm.Open("mysql", "<username>:<password>/<database>?charset=utf8&parseTime=True&loc=Local")
ErrorHandler(err,"gorm.Open()")

//database/sql 已内置连接池设置
db.DB().SetMaxIdleConns(20)
db.DB().SetMaxOpenConns(100)
}

模型定义:

1
2
3
4
5
6
7
8
9
10
//通过标签设置 mysql 里面的约束
type User struct {
gorm.Model //嵌入gorm基础模型,可获得模型基础能力
Name string `gorm:"type:varchar(12);not null;unique_index"`
Email string `gorm:"type:varchar(24);unique_index"`
Phone string `gorm:"type:varchar(11);unique_index"`
Avatar string `gorm:"type:varchar(64)"`
Title string `gorm:"type:varchar(20)"`
Gender int32 `gorm:"type:varchar(2)"`
}

表操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//创建表
if !db.HasTable(&User{}) {
err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").CreateTable(&User{}).Error
ErrorHandler(err,"CreateTable()")
}


//1.插入操作
user := &User{
Name: "fun",
Email:"[email protected]",
Phone:"13813812138",
Avatar:"link/to/your/avatar.jpg",
Title:"Gopher",
Gender:1
CreatedAt: time.Now(),
}

if err := db.Create(user).Error; err != nil {
log.Println("Create data fail!")
}

//2.更新操作
// UPDATE users SET name='joker', updated_at='2016-04-20 22:12:52' WHERE id=1;
db.Model(&user).Update("name", "joker")
// UPDATE users SET name='joker',title='Coder', updated_at='2016-04-20 22:12:52' WHERE id=1;
db.Model(&user).Updates(User{Name: "joker", Title: "Coder"})

//3.查询操作
//通过主键查询第一条记录
db.First(&user)
//// SELECT * FROM users ORDER BY id LIMIT 1;
// 随机取一条记录
db.Take(&user)
//// SELECT * FROM users LIMIT 1;
// 通过主键查询最后一条记录
db.Last(&user)
//// SELECT * FROM users ORDER BY id DESC LIMIT 1;
// 拿到所有的记录
db.Find(&users)
//// SELECT * FROM users;
// 查询指定的某条记录(只可在主键为整数型时使用)
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;

//条件查询
// 获取单条记录
db.Where("name = ?", "fun").First(&user)
// SELECT * FROM users WHERE name = 'fun' limit 1;
// 获取多条记录
db.Where("name = ?", "fun").Find(&users)
//// SELECT * FROM users WHERE name = 'fun';
// <>
db.Where("name <> ?", "fun").Find(&users)
// IN
db.Where("name IN (?)", []string{"fun", "joker"}).Find(&users)
// LIKE
db.Where("name LIKE ?", "%f%").Find(&users)
// AND
db.Where("name = ? AND age >= ?", "fun", "18").Find(&users)
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)


//4.删除操作
// 删除一条
db.Delete(&user)
// DELETE from users where id=1;
//条件删除多条
db.Where("name LIKE ?", "%fun%").Delete(User{})
// DELETE from users where name LIKE "%f%";
//条件删除多条
db.Delete(User{}, "name LIKE ?", "%fun%")
// DELETE from users where name LIKE "%fun%";

Template渲染

Golang原生支持对Template的数据渲染(text/template)

Go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
"fmt"
"net/http"
"text/template"
)

// 模板

// Person 结构体
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

func templateHandle(respWrite http.ResponseWriter, req *http.Request) {
// 准备渲染数据
p := Person{
Name: "张三",
Age: 20,
}
// 加载html文件
temp, err := template.ParseFiles("./go_dev/day10/example7/main/index.html")
if err != nil {
fmt.Println("parse file err:", err)
return
}
// 把数据渲染到模板
err = temp.Execute(respWrite, p)
if err != nil {
fmt.Println("template Execute err:", err)
return
}
}

func main() {
http.HandleFunc("/", templateHandle)
err := http.ListenAndServe(":8888", nil)
if err != nil {
fmt.Println("listen err:", err)
return
}
}

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>hi {{.Name}}</h1>
</body>
</html>

Template 条件语句

  • if判断:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <html>
    <head>
    </head>
    <body>
    {{if gt .Age 18}}
    <p>hello, old man, {{.Name}}</p>
    {{else}}
    <p>hello,young man, {{.Name}}</p>
    {{end}}
    </body>
    </html>
    常见操作符:
    • not 非
      1
      2
      {{if not .condition}} 
      {{end}}
    • and 与
      1
      2
      {{if and .condition1 .condition2}} 
      {{end}}
    • or 或
      1
      2
      {{if or .condition1 .condition2}} 
      {{end}}
    • eq 等于
      1
      2
      {{if eq .var1 .var2}} 
      {{end}}
    • ne 不等于
      1
      2
      {{if ne .var1 .var2}} 
      {{end}}
    • lt 小于 (less than)
      1
      2
      {{if lt .var1 .var2}} 
      {{end}}
    • le 小于等于
      1
      2
      {{if le .var1 .var2}} 
      {{end}}
    • gt 大于
      1
      2
      {{if gt .var1 .var2}} 
      {{end}}
    • ge 大于等于
      1
      2
      {{if ge .var1 .var2}} 
      {{end}
  • range循环
    1
    2
    3
    4
    5
    6
    7
     {{range .}}
    {{if gt .Age 18}}
    <p>hello, old man, {{.Name}}</p>
    {{else}}
    <p>hello,young man, {{.Name}}</p>
    {{end}}
    {{end}}

    例子:

  • main.go:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package main

    import (
    "fmt"
    "net/http"
    "text/template"
    )

    type Person struct {
    Name string
    Age int
    }

    var temp *template.Template

    // 模板-条件渲染

    func userInfo(respWrite http.ResponseWriter, req *http.Request) {
    // 加载模板
    temp, err := template.ParseFiles("./go_dev/day10/example8/main/index.html")
    if err != nil {
    fmt.Println("load template file err:", err)
    return
    }

    // 准备渲染数据
    var pArr []Person

    p1 := Person{
    Name: "张三",
    Age: 18,
    }

    p2 := Person{
    Name: "张四",
    Age: 21,
    }
    pArr = append(pArr, p1)
    pArr = append(pArr, p2)
    // 把数据渲染到模板
    err = temp.Execute(respWrite, pArr)
    if err != nil {
    fmt.Println("template execute err:", err)
    }
    }

    func main() {
    http.HandleFunc("/user", userInfo)
    err := http.ListenAndServe(":8888", nil)
    if err != nil {
    fmt.Println("http listen err:", err)
    return
    }
    }

  • index.html:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <!DOCTYPE html>
    <html lang="zh-cn">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    </head>

    <body>
    <!-- 循环 range-->
    {{range .}}
    <h1>hi {{.Name}}</h1>
    <!-- 条件判断 -->
    {{if gt .Age 18}}
    <p>Your age is {{.Age}} and you are an adult.</p>
    {{else}}
    <p>Your age is {{.Age}} and you are a minor.</p>
    {{end}}
    {{end}}
    </body>

    </html>

Golang创建一个简单的http服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"net/http"
)

func hello(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintf(resp, "hello")
}

func main() {
// 创建http监听 路径 方法
http.HandleFunc("/", hello)
// 配置监听地址 ListenAndServe会一直处于挂起状态
err := http.ListenAndServe("0.0.0.0:8888", nil)
if err != nil {
fmt.Printf("http err:%s\n", err)
}
}

发送一个Http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"net/http"
"net/http/httputil"
)

// http client
func main() {
res, err := http.Get("http://www.baidu.com/")
if err != nil {
fmt.Println("get err:", err)
return
}
data, err := httputil.DumpResponse(res, true)
if err != nil {
fmt.Println("get data err:", err)
return
}
fmt.Println(string(data))
}

HTTP常见的请求方法:GET、POST、PUT、DELETE、HEAD

发送一个HEAD请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"net/http"
)

var urls = []string{
"http://www.baidu.com/",
"http://www.google.com",
"http://www.taobao.com",
}

// head 测试url是否能打开
func main() {
for _, url := range urls {
resp, err := http.Head(url)
if err != nil {
fmt.Println("request err:", err)
continue
}
fmt.Printf("head %s success,status:%d\n", url, resp.StatusCode)
}
}

HEAD请求如果对应URL长时间没有响应,会触发超时处理

自定义HEAD超时时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"fmt"
"net"
"net/http"
"time"
)

var urls = []string{
"http://www.baidu.com/",
"http://www.google.com",
"http://www.taobao.com",
}

func main() {
// 自定义超时时间
c := http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (conn net.Conn, err error) {
timeout := time.Second * 3
return net.DialTimeout(network, addr, timeout)
},
},
}

for _, url := range urls {
resp, err := c.Head(url)
if err != nil {
fmt.Println("request err:", err)
continue
}
fmt.Printf("head %s success,status:%d\n", url, resp.StatusCode)
}
}

简单的表单处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

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

// 表单处理

func hello(respWrite http.ResponseWriter, req *http.Request) {
io.WriteString(respWrite, "hello world ")
}

func loginHandle(respWrite http.ResponseWriter, req *http.Request) {
var form = `<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="#" method="POST">
<input type="text" name="username">
<input type="text" name="passwd">
<input type="submit" value="登录">
</form>
</body>
</html>`
respWrite.Header().Set("content-type", "text/html; charset=UTF-8")
switch req.Method {
case "GET":
io.WriteString(respWrite, form)
case "POST":
req.ParseForm()
io.WriteString(respWrite, req.FormValue("username"))
io.WriteString(respWrite, "\n")
io.WriteString(respWrite, req.FormValue("passwd"))

}
}

func main() {
http.HandleFunc("/", hello)
http.HandleFunc("/login", loginHandle)
if err := http.ListenAndServe(":8888", nil); err != nil {
fmt.Println("http server start err:", err)
}
}

如果不进行panic处理,某一次请求发生了panic,则会导致整个http服务终止

panic处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

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

func loginHandle(respWriter http.ResponseWriter, req *http.Request) {
io.WriteString(respWriter, "login")
panic("login err")

}

// panic 处理
func logPanicHandle(hand http.HandlerFunc) http.HandlerFunc {
return func(respWriter http.ResponseWriter, req *http.Request) {

defer func() {
if err := recover(); err != nil {
log.Printf("[%v] url:[%v] panic: %v", req.RemoteAddr, req.RequestURI, err)
}
}()

hand(respWriter, req)
}
}

func main() {
http.HandleFunc("/login", logPanicHandle(loginHandle))
err := http.ListenAndServe(":8888", nil)
if err != nil {
fmt.Println("server err:", err)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// goroutine异常处理

func test() {

// 建议在写每个goroutine的时候 首先写处理panic的方法
defer func() {
if err := recover(); err != nil {
fmt.Println("panic:", err)
}
}()

// 这里map没有初始化 会发生panic 如果不捕获会导致整个进程退出
var m map[string]string
m["str"] = "s"
}

func calc() {
for {
fmt.Println("calc.....")
time.Sleep(time.Second)
}
}

func main() {
go test()
for i := 0; i < 2; i++ {
go calc()
}
time.Sleep(time.Second * 10)
}

定时器的创建和初始化:

1
2
3
4
5
6
7
8
9
// 定时器

func main() {
t := time.NewTicker(time.Second)
for v := range t.C {
fmt.Println(v)
}
}

一次性定时器(time.After() 和 time.NewTicker()的区别):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 一次性定时器
func test1() {
var ch chan int
var ch1 chan int
ch = make(chan int, 10)
ch1 = make(chan int, 10)
go func() {
for i := 0; i < 10; i++ {
ch <- i
ch1 <- i * i
time.Sleep(time.Second)
}
}()

for {
select {
case v := <-ch:
fmt.Printf("ch:%d\n", v)
case v := <-ch1:
fmt.Printf("ch1:%d\n", v)
// 设置超时时间为1秒 不建议使用After 容易造成内存泄漏
case t := <-time.After(time.Second):
fmt.Println("get data time out:", t)
}
}
}

func test2() {
var ch chan int
var ch1 chan int
ch = make(chan int, 10)
ch1 = make(chan int, 10)
go func() {
for i := 0; i < 10; i++ {
ch <- i
ch1 <- i * i
time.Sleep(time.Second)
}
}()

for {
t := time.NewTicker(time.Second)
select {
case v := <-ch:
fmt.Printf("ch:%d\n", v)
case v := <-ch1:
fmt.Printf("ch1:%d\n", v)
// 设置超时时间为1秒 推荐使用
case t := <-t.C:
fmt.Println("get data time out:", t)
}
// 使用完手动关闭
t.Stop()
}
}

func main() {
test2()
}

获取命令行参数

  • os.Args是一个string的切片,用来存储所有的命令行参数
  • flag包的使用,用来解析命令行参数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 命令行参数
    func test() {
    var b bool
    var path string
    var port int
    // 预处理配置
    flag.BoolVar(&b, "b", false, "print on newline")
    flag.StringVar(&path, "c", "/conf.config", "Specify the configuration file. The default is /conf.config .")
    flag.IntVar(&port, "p", 8080, "Specify the port, the default is 8080")
    // 解析命令行标志
    flag.Parse()
    fmt.Printf("bool is %v\n", b)
    fmt.Printf("path is %s\n", path)
    fmt.Printf("port is %d\n", port)
    }

    func main() {
    test()
    }
    --------------------
    ./main -c /use/local/conf.config -p 8081 -b true

    bool is true
    path is /use/local/conf.config
    port is 8081

终端读写

  • os.Stdin:标准输入
  • os.Stdout:标准输出
  • os.Stderr:标准错误输出

终端读写示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
fmt.Println("我能把字符串输出到控制台")
fmt.Fprintln(os.Stdout, "我也能把字符串输出到控制台")
var (
name string
age int
score float32
)

// 把字符串格式化到指定变量
fmt.Sscanf("张三,18,98.5", "%s,%d,%f", &name, &age, &score)
fmt.Println(name, age, score)
}

带缓冲区的读写示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
// 带缓冲区的读
var inputReader *bufio.Reader
inputReader = bufio.NewReader(os.Stdin)
fmt.Println("please enter some input:")
input, err := inputReader.ReadString('\n')
if err != nil {
fmt.Printf("input err:\n", err)
}
fmt.Printf("the input was %s\n", input)
}
------------------------
please enter some input:
123
the input was 123

文件读写:

  • os.File封装所有文件相关操作,os.Stdin,os.Stdout, os.Stderr都是*os.File
  • 打开一个文件进行读操作: os.Open(name string) (*File, error)
  • 关闭一个文件:File.Close()
  • 打开一个文件进行写操作:os.OpenFile(“output.dat”, os.O_WRONLY|os.O_CREATE, 0666)

第二个参数:文件打开模式
os.O_WRONLY:只写、os.O_CREATE:创建文件、os.O_RDONLY:只读、os.O_RDWR:读写、os.O_TRUNC:清空

第三个参数:权限控制:r ——> 004、w——> 002、x——> 001

文件读写操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 带缓冲区的读
func buffRead(rd io.Reader) {
var bufferReader *bufio.Reader
bufferReader = bufio.NewReader(rd)
str, err := bufferReader.ReadString('\n')
if err != nil {
fmt.Printf("input err:%s\n", err)
return
}
fmt.Printf("the input was %s\n", str)
}

// 从文件获取输入
func test2() {
file, err := os.Open("/1.txt")
if err != nil {
fmt.Printf("open file err:%s\n", err)
return
}
defer file.Close()
buffRead(file)
}

func main() {
test2()
}
-------------------------
the input was 1234567890

从文件中读取字符串,统计英文、数字、空格以及其他字符的数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
type charCount struct {
c int
n int
spance int
other int
}

func test() {
file, err := os.Open("/1.txt")
if err != nil {
fmt.Printf("open file err:%s\n", err)
return
}
defer file.Close()
var count charCount
var inputReader *bufio.Reader
inputReader = bufio.NewReader(file)
for {
str, err1 := inputReader.ReadString('\n')
if err1 == io.EOF {
fmt.Printf("read to the end of file\n")
break
}
if err != nil {
fmt.Printf("read file err:%s", err)
break
}

ru := []rune(str)
for _, v := range ru {
switch {
case v >= 'a' && v <= 'z':
fallthrough
case v >= 'A' && v <= 'Z':
count.c += 1
case v == ' ':
count.spance += 1
case v >= '0' && v <= '9':
count.n += 1
default:
count.other += 1
}
}
}
fmt.Printf("char num :%d \nspance num :%d\nnumber num:%d\nother num :%d\n", count.c, count.spance, count.n, count.other)
}

func main() {
test()

}
-------------------------
read to the end of file
char num :547
spance num :115
number num:11
other num :330

使用ioutil读取整个文件,并写到指定文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func test() {
inputFile := "/inputfile.txt"
outputFile := "/outputfile.txt"
// 一次读完整个文件
f, err := ioutil.ReadFile(inputFile)
if err != nil {
fmt.Printf("read file err:%s\n", err)
}

str := string(f)
fmt.Printf("file content:%s\n", str)
// 这里第三个参数设置输出文件的权限
err1 := ioutil.WriteFile(outputFile, f, 0x777)
if err1 != nil {
fmt.Printf("write file err:%s\n", err1)
}

}

func main() {
test()
}

读取压缩文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 读取解压文件示例:

func test() {
// 1.打开文件
fName := "go_dev/day7/example5/main/1.gz"
fi, err := os.Open(fName)
var r *bufio.Reader
if err != nil {
fmt.Printf("open file err:%s\n", err)
return
}
defer fi.Close()
// 2.gzip读取打开的文件句柄
grfi, err := gzip.NewReader(fi)
if err != nil {
fmt.Printf("open gzip failed err:%v\n", err)
return
}
// 3.用bufio.Reader接收gzip读取出的数据
r = bufio.NewReader(grfi)
for {
line, err := r.ReadString('\n')
if err != nil {
fmt.Println("Done reading file")
return
}
fmt.Println(line)
}
}

func main() {
test()
}

拷贝文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 拷贝文件
func copyFile(dstName, srcName string) (written int64, err error) {
// 打开源文件
srcFile, err := os.Open(srcName)
if err != nil {
fmt.Printf("open src file err:%s\n", err)
return
}
defer srcFile.Close()
// 打开或创建目标文件
dstFile, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Printf("open dst file err:%s\n", err)
return
}
defer dstFile.Close()

return io.Copy(dstFile, srcFile)
}

func main() {
num, _ := copyFile("/2.txt", "/1.txt")
fmt.Println(num)
}