第十二章:读写数据 
  
     
   
前言 
  
     
   
继续开始读写数据的学习,其实就是I/O流了估计是,这方面还是挺重要的。
读取用户的输入 
  
     
   
Scanln 扫描来自标准输入的文本,将空格分隔的值依次存放到后续的参数内,直到碰到换行。Scanf 与其类似,除了 Scanf 的第一个参数用作格式字符串,用来决定如何读取。(感觉Go这方面类似C的scanf了)
 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" 
 func  main ()  { 
	fmt . Println ( "please input:" ) 
 	var  firstName , lastName  string 
 	var  n1 , n2  string 
 	fmt . Scanln ( & firstName , & lastName ) 
 	fmt . Printf ( "Hello %s %s\n" , firstName , lastName ) 
 	fmt . Scanf ( "%s,%s" , & n1 , & n2 ) 
 	fmt . Printf ( "%s,%s\n" , n1 , n2 ) 
 } 
 /*
 please input:
 a1 a2
 Hello a1 a2
 a1,a2
 a1,a2,
  */  
 
也可以用bufio包来读取:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
  
package  main 
 import  ( 
	"bufio" 
 	"fmt" 
 	"os" 
 ) 
 func  main ()  { 
	inputReader  :=  bufio . NewReader ( os . Stdin ) 
 	fmt . Println ( "please input:" ) 
 	input ,  err  :=  inputReader . ReadString ( '\n' ) 
 	if  err  !=  nil  { 
 		fmt . Printf ( "input error!" ) 
 		return 
 	} 
 	fmt . Println ( input ) 
 	data  :=  [] byte ( input ) 
 	fmt . Println ( data ) //包括\n 
 }  
 
ReadString 返回读取到的字符串,如果碰到错误则返回 nil。如果它一直读到文件结束,则返回读取到的字符串和 io.EOF。如果读取过程中没有碰到 delim 字符,将返回错误 err != nil。
文件读写 
  
     
   
读文件 
  
     
   
逐行读取文件:
 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
  
package  main 
 import  ( 
	"bufio" 
 	"fmt" 
 	"io" 
 	"os" 
 	"reflect" 
 ) 
 func  main ()  { 
	inputFile , inputError  :=  os . Open ( "1.txt" ) 
 	if  inputError != nil  { 
 		fmt . Println ( "file error!" ) 
 		return 
 	} 
 	fmt . Println ( reflect . TypeOf ( inputFile )) 
 	defer  inputFile . Close () 
 
 	inputReader  :=  bufio . NewReader ( inputFile ) 
 	for  { 
 		inputString , readerError  :=  inputReader . ReadString ( '\n' ) 
 		fmt . Println ( inputString ) 
 		if  readerError  ==  io . EOF { 
 			return 
 		} 
 
 	} 
 
 }  
 
也可以利用io/ioutil的ReadFile()函数讲整个文件的内容读取到一个[]byte中:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
  
package  main 
 import  ( 
	"fmt" 
 	"io/ioutil" 
 ) 
 func  main ()  { 
	inputFile  :=  "1.txt" 
 	buf , err  :=  ioutil . ReadFile ( inputFile ) 
 	if  err != nil { 
 		fmt . Println ( "error" ) 
 		panic ( err . Error ()) 
 	} 
 	fmt . Print ( string ( buf )) 
 
 }  
 
带缓冲的读取:
 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
  
package  main 
 import  ( 
	"bufio" 
 	"fmt" 
 	"io" 
 	"os" 
 ) 
 func  main ()  { 
	inputFile , inputError  :=  os . Open ( "1.txt" ) 
 	if  inputError  !=  nil  { 
 		panic ( inputError . Error ()) 
 	} 
 	defer  inputFile . Close () 
 	inputReader  :=  bufio . NewReader ( inputFile ) 
 	var  buf  [] byte  =  make ([] byte , 1024 ) 
 	for  { 
 		n , err  :=  inputReader . Read ( buf ) 
 		if  err == io . EOF { 
 			break 
 		} 
 		fmt . Print ( string ( buf [: n ])) 
 	} 
 
 
 }  
 
写文件 
  
     
   
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
  
package  main 
 import  ( 
	"bufio" 
 	"os" 
 ) 
 func  main ()  { 
	outputFile , _  :=  os . OpenFile ( "1.txt" , os . O_WRONLY | os . O_CREATE ,  0666 ) 
 	defer  outputFile . Close () 
 	outputWriter  :=  bufio . NewWriter ( outputFile ) 
 	outString  :=  "hello,world\nwwwwww" 
 	outputWriter . WriteString ( outString ) 
 	outputWriter . Flush () 
 }  
 
os.OpenFile的第二个参数是flag,有如下常用标志:
os.O_RDONLY:只读 
os.O_WRONLY:只写 
os.O_CREATE:创建:如果指定文件不存在,就创建该文件。 
os.O_TRUNC:截断:如果指定文件已存在,就将该文件的长度截为 0。 
 
也可以不使用缓冲区,直接写入:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
  
package  main 
 import  ( 
	"os" 
 ) 
 func  main ()  { 
	outputFile , _  :=  os . OpenFile ( "1.txt" , os . O_WRONLY | os . O_CREATE ,  0666 ) 
 	defer  outputFile . Close () 
 	outputFile . WriteString ( "hello,world" ) 
 }  
 
练习:
 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
  
package  main 
 import  ( 
	"fmt" 
 	"io/ioutil" 
 	"os" 
 ) 
 type  Page  struct  { 
	Title  string 
 	Body   [] byte 
 } 
 func  ( page  * Page )  save (){ 
	outputFile , _  :=  os . OpenFile ( page . Title , os . O_WRONLY | os . O_CREATE ,  0666 ) 
 	defer  outputFile . Close () 
 	outputFile . Write ( page . Body ) 
 } 
func  ( page  * Page )  load (){ 
	page . Body , _  = ioutil . ReadFile ( page . Title ) 
 	fmt . Print ( string ( page . Body )) 
 } 
 func  main ()  { 
	page  :=  new ( Page ) 
 	page . Title  =  "2.txt" 
 	page . Body   =  [] byte ( "hello!!!!" ) 
 	page . save () 
 	page . load () 
 }  
 
文件拷贝 
  
     
   
利用io.Copy()
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
  
package  main 
 import  ( 
	"io" 
 	"os" 
 ) 
 func  main ()  { 
	CopyFile ( "3.txt" , "1.txt" ) 
 } 
 func  CopyFile ( dstName , srcName  string )( written  int64 , err  error ){ 
	src , err  :=  os . Open ( srcName ) 
 	defer  src . Close () 
 	dst , err  :=  os . OpenFile ( dstName , os . O_WRONLY | os . O_CREATE , 0666 ) 
 	defer  dst . Close () 
 	return  io . Copy ( dst , src ) 
 }  
 
defer 的使用:当打开目标文件时发生了错误,那么 defer 仍然能够确保 src.Close() 执行。如果不这么做,文件会一直保持打开状态并占用资源。
从命令行读取参数 
  
     
   
os包 
  
     
   
os.Args
这个命令行参数会放置在切片 os.Args[] 中(以空格分隔),从索引 1 开始(os.Args[0] 放的是程序本身的名字)。
flag包 
  
     
   
感觉比os包解析命令行参数更好,而且用法更偏向于那些命令行参数的解析。
每个Flag结构体:
1
 2
 3
 4
 5
 6
  
type  Flag  struct  { 
    Name      string  // name as it appears on command line 
     Usage     string  // help message 
     Value     Value   // value as set 
     DefValue  string  // default value (as text); for usage message 
 }  
 
指定参数:
1
 2
  
var  do  =  flag . String ( "w" , "no out" , "print" ) 
//func String(name string, value string, usage string) *string  
 
注意返回的是指针。
flag.Parse() 扫描参数列表(或者常量列表)并设置 flag, flag.Arg(i) 表示第 i 个参数。Parse() 之后 flag.Arg(i) 全部可用,flag.Arg(0) 就是第一个真实的 flag,而不是像 os.Args(0) 放置程序的名字。
flag.Narg() 返回参数的数量。解析后 flag 或常量就可用了。
flag.PrintDefaults() 打印 flag 的使用帮助信息。
需要注意的是,从第一个不能解析的参数开始,后面的所有参数都是无法解析的。即使后面的参数中含有预定义的参数。
例子:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
  
package  main 
 import  ( 
	"flag" 
 	"fmt" 
 ) 
 var  do  =  flag . String ( "w" , "no out" , "print" ) 
 func  main ()  { 
	//flag.PrintDefaults() 
 	flag . Parse ()  // Scans the arg list and sets up flags 
 	fmt . Println ( * do ) 
 }  
 
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
  
C:\U sers\1 5997\D esktop>go run main.go -w 123 
 123 
 C:\U sers\1 5997\D esktop>go run main.go
 no out
 
 C:\U sers\1 5997\D esktop>go run main.go -w= 123 
 123 
 C:\U sers\1 5997\D esktop>  
 
-help可以起到flag.PrintDefaults()的作用,不过它就只能打印帮助信息了,联想到之前使用工具中的-help,这部分还是挺重要的,接下来写工具就需要解析这些参数。
用切片读写文件 
  
     
   
 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
  
package  main 
 import  ( 
	"fmt" 
 	"os" 
 ) 
 func  cat ( f  * os . File ){ 
	const  NBUF  =  1024 
 	var  buf  [ NBUF ] byte 
 	for  { 
 		switch  nr ,  _  :=  f . Read ( buf [:]); true  { 
 		case  nr < 0 : 
 			fmt . Println ( "can not read" ) 
 			os . Exit ( 1 ) 
 		case  nr == 0 : //EOF 
 			return 
 		case  nr > 0 : 
 			fmt . Print ( string ( buf [: nr ])) 
 		} 
 	} 
 } 
 func  main ()  { 
	f , _  :=  os . Open ( "1.txt" ) 
 	defer  f . Close () 
 	cat ( f ) 
 }  
 
JSON数据格式 
  
     
   
将对象转换为JSON:
 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
  
package  main 
 import  ( 
	"encoding/json" 
 	"fmt" 
 	"os" 
 ) 
 type  Person  struct  { 
	Age  int 
 	Name  string 
 	Mate  School 
 } 
type  School  struct { 
	Name  string 
 	Position  string 
 } 
 func  main (){ 
	person  :=  Person { 18 , "feng" , School { "a" , "b" }} 
 	//得到字符串 
 	js , _  :=  json . Marshal ( person ) 
 	fmt . Printf ( "%s" , js ) 
 
 	//写入文件 
 	f , _  :=  os . OpenFile ( "1.json" , os . O_WRONLY | os . O_CREATE , 0666 ) 
 	defer  f . Close () 
 	enc  :=  json . NewEncoder ( f ) 
 	enc . Encode ( person ) 
 }  
 
json.Marshal
注意结构体成员的首字母要大写才能成功转换。
出于安全考虑,在 web 应用中最好使用 json.MarshalforHTML() 函数,其对数据执行 HTML 转码,所以文本可以被安全地嵌在 HTML <script> 标签中。
不是所有的数据都可以编码为 JSON 类型:只有验证通过的数据结构才能被编码:
JSON 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map [string] T(T 是 json 包中支持的任何类型)
 
Channel,复杂类型和函数类型不能被编码
 
不支持循环数据结构;它将引起序列化进入一个无限循环
 
指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)
 
 
将JSON格式转换为对象:
 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
  
package  main 
 import  ( 
	"encoding/json" 
 	"fmt" 
 ) 
 type  Person  struct  { 
	Age  int 
 	Name  string 
 	Mate  School 
 } 
type  School  struct { 
	Name  string 
 	Position  string 
 } 
 func  main (){ 
	person  :=  Person { 18 , "feng" , School { "a" , "b" }} 
 	//得到字符串 
 	js , _  :=  json . Marshal ( person ) 
 	fmt . Printf ( "%s\n" , js ) 
 
 
 	var  p  Person 
 	err  :=  json . Unmarshal ( js , & p ) 
 	if  err != nil { 
 		panic ( err . Error ()) 
 	} 
 	fmt . Println ( p ) 
 
 
 }  
 
利用json.Unmarshal:func Unmarshal(data []byte, v interface{}) error
第二个参数需要是指针。
Go中的密码学 
  
     
   
Go也内置了许多的加密解密包。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
  
package  main 
 import  ( 
	"crypto/md5" 
 	"fmt" 
 	"io" 
 ) 
 func  main (){ 
	hasher  :=  md5 . New () 
 	io . WriteString ( hasher , "feng" ) 
 	fmt . Printf ( "%x\n" , hasher . Sum ( nil )) 
 	// 
 
 	hasher . Reset () 
 	data  :=  [] byte ( "hello" ) 
 	hasher . Write ( data ) 
 	fmt . Printf ( "%x\n" , hasher . Sum ( nil )) 
 
 	fmt . Printf ( "%x" , md5 . Sum ( data )) 
 }