【go语言圣经1.3】

概念

  1. map 数据类型
    • map 存储了键/值(key/value)的集合,对集合元素,提供常数时间的存、取或测试操作。键可以是任意类型,只要其值能用 == 运算符比较,最常见的例子是字符串;值则可以是任意类型。这个例子中的键是字符串,值是整数。内置函数 make 创建空 map,此外,它还有别的作用。4.3 节讨论 map
    • 从功能和实现上说,Gomap 类似于 Java 语言中的 HashMap,Python 语言中的 dictLua 语言中的 table,通常使用 hash 实现

语言特性

  1. if 语句:条件部分不需要括号,但主体必须用大括号包围。

  2. Go 中只有 for 一种循环形式,可以写成传统 for 循环、while 循环形式或无限循环。

  3. 通过 range 遍历 map 或切片时,返回的顺序是不确定的,这要求程序不能依赖遍历顺序。

  4. 标准输入与文件输入都通过类似方式处理:使用 bufio.Scannerioutil.ReadFile。不同之处在于:前者逐行读取适合大数据流,后者适合一次性加载整个文件

  5. map 是一种引用类型(这里的“引用”类似于指针,但语法上更加简单)

    • 当你使用 make(map[string]int) 创建一个 map 时,实际上 Go 分配了一块内存来存储这个 map 的数据,并返回了一个引用,这个引用指向那块内存。你可以把这个引用看作是一个“地址”,它告诉程序实际数据存储在哪里。
    • 当你将 map 作为参数传递给函数时,传递的是这个引用的一个副本(浅拷贝),而不是整个 map 数据结构的拷贝。副本只是另一个指向同一块内存的引用。
    • 因为传入函数的 map 引用(副本)和原始的 map 引用都指向同一块内存,所以在函数内部对 map 的修改(如增加、删除或更新键值对)会直接影响到这块内存。结果就是,无论是在函数内部还是外部,你看到的都是同一份数据。
    • 再次强调:对于 map,使用赋值语句或者传递参数时,实际上只是把这个 map 的引用复制了一份(浅拷贝)。新变量和原变量都指向同一块内存区域。
    original := map[string]int{"age": 20}
    copyRef := original // 复制的是引用,而非数据内容的独立副本
    
    copyMap := deepCopy(original) // 相当于手动复制了 map 的每个键值对
    
    

要点(案例)

通过 dup 程序的三个版本,我们可以看到 Go 语言在文本处理上的多种方法和技巧:

  • dup1 采用流式读取和 map 统计的方式,适合处理标准输入;
  • dup2 在此基础上扩展到处理具名文件,并引入了错误处理;
  • dup3 则展示了如何一次性读取整个文件,再用字符串分割方法处理数据。

在 dup 程序中,核心思想是扫描输入中的每一行,然后利用数据结构统计每一行出现的次数,最后只输出出现多次的行。

这个思路在许多文本处理工具中都非常常见,比如 Unix 中的 uniq 命令(寻找相邻的重复行)

// Dup1 prints the text of each line that appears more than
// once in the standard input, preceded by its count.
package main

import (
    "bufio"
    "fmt"
    "os"
)

int main() {
	counts := make(map[string]int)
	input := bufio.NewScanner(os.Stdin)
	for input.Scan() {
		counts[input.Text()]++
	}
}

// NOTE: ignoring potential errors from input.Err()
for line, n := range counts{
	if n > 1 {
		fmt.Printf("%d\t%s\n", n, line)
	}
}

  • bufio:提供高效的缓冲输入,主要用于按行读取文本。
  • os:提供与操作系统交互的能力,比如读取标准输入
  • 主要数据结构——map
    • 使用 make(map[string]int) 创建了一个空的 map,该 map 的键为字符串(每一行的内容),值为整型(该行出现的次数)。
    • 当某一行首次出现时,由于 map 默认值为该类型的零值(对 int 来说为 0),直接进行自增操作 counts[input.Text()]++ 即可。
  • bufio.Scanner
    • Scanner 类型是 bufio 包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法
    • bufio.NewScanner(os.Stdin) 创建了一个扫描器,用于逐行读取标准输入。
    • 调用 input.Scan() 读取下一行, 并移除行末的换行符(当没有更多输入时返回 false)
    • 调用 input.Text() 可以获取当前扫描到的行(已经去掉了换行符)
// Dup2 prints the count and text of lines that appear more than once
// in the input.  It reads from stdin or from a list of named files.
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main(){
	counts := make(map[string]int)
	
	files := os.Args[1:]
	if len(files) == 0 {
		countLines(os.Stdin, counts)
	} else {
		for _, arg := range files{
			f, err := os.Open(arg)
			if err != nil {
				fmt.Fprintf(os.Stderr, "dup2:%v\n", err)
				continue
			}
			countLines(f, counts)
			f.Close()
		}
	}

	for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int){
	input := bufio.NewScanner(f)
	for input.Scan() {
		counts[input.Text()]++
	}
	// NOTE: ignoring potential errors from input.Err()
}
  • 扩展读取具名函数的能力
  • 将按行读取并计数的逻辑提取到独立的函数 countLines
    • 由于 map 是引用类型,将其作为参数传递后,函数内部对 map 的修改会反映到外部。
// 使用一种不同的方法:一次性将整个文件内容读入内存,再进行分割处理

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

func main() {
	counts := make(map[string]int)
    for _, filename := range os.Args[1:] {
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
            continue
        }
        for _, line := range strings.Split(string(data), "\n") {
            counts[line]++
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

  • ioutil.ReadFile 函数用于一次性读取整个文件内容,返回的是字节切片。
  • 需要将字节切片转换为字符串string(data),才能使用 strings.Split 按换行符分割成多个行。
  • 适用场景
    • 当输入数据较小或对内存要求不高时,一次性读取所有内容简化了处理过程;
    • 对于大文件或内存敏感的应用,前两种基于流式处理的方式(dup1 和 dup2)更合适。

总结与补充

  1. 标准化输出 format
    • 格式化动词

      %d          十进制整数
      %x, %o, %b  十六进制,八进制,二进制整数。
      %f, %g, %e  浮点数: 3.141593 3.141592653589793 3.141593e+00
      %t          布尔:truefalse
      %c          字符(rune(Unicode码点)
      %s          字符串
      %q          带双引号的字符串"abc"或带单引号的字符'c'
      %v          变量的自然形式(natural format)
      %T          变量的类型
      %%          字面上的百分号标志(无操作数)
      
      
    • 转义字符

  2. strings包的常用函数
    • strings.Split(s, sep string) []string 将字符串s按照分隔符sep分割成子串,并返回字符串切片
    • strings.Join(a []string, sep string) string将字符串切片a中的各个元素以分隔符sep连接成一个单一的字符串
Go语言圣经》是一本广受好评的教材,旨在帮助读者系统地学习和掌握Go语言。这本书以简明清晰的方式介绍了Go语言的各种特性、语法和用法,是入门和进阶者的理想选择。 首先,该书提供了对Go语言基础知识的全面介绍。它从简单到复杂地解释了Go语言的基本概念,诸如变量、函数、循环和条件语句等等。通过丰富的例子和练习,读者能够逐步理解和掌握这些概念。 其次,该书详细介绍了Go语言的高级特性和用法。读者可以学习到Go语言的面向对象编程、并发编程、网络编程等关键技术。并发编程是Go语言的一个独特特性,对于提高程序性能和扩展能力非常重要。 此外,该书还包含了对常见问题和陷阱的讲解,帮助读者避免一些常见的错误和陷阱。同时,书中提供了大量的案例和实践项目,读者可以通过实际操作来巩固所学内容。 《Go语言圣经》以其简洁明了的风格和对细节的深入讲解而闻名。无论是作为初学者的入门指南,还是作为有经验的开发者的参考书,这本书都能满足读者的需求。此外,该书的PDF版本能够方便地在线或离线阅读,为读者提供了更加便捷的学习体验。 综上所述,《Go语言圣经》是一本内容丰富、权威性强的优秀教材。它不仅适合Go语言的初学者,也适用于那些想要深入学习和掌握Go语言的开发者。无论是在线阅读还是PDF版本,读者都能够方便地获取和利用这本宝贵的学习资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值