Java转go算法之Scanner使用一篇搞懂

311 阅读5分钟

文章摘要:go语言中使用scanner去标准输入中扫描数据的过程以及原理

1、Scanner的简单使用

会默认以换行符作为结束标准

//创建一个新的Scanner,它将从标准输入读取数据
scanner := bufio.NewScanner(os.Stdin)
​
for scanner.Scan() {
    fmt.Println("输入的文本是:", scanner.Text())
}

2、Scanner读取文件

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    defer file.Close()
​
    //bufio.NewScanner中传参io.Reader 接口的类型,为了可以从输入流中读取数据的
    scanner := bufio.NewScanner(file)
    //读取到的数据会以字节切片([]byte)的形式存储在内部缓冲区s.buf中。
    //之后可以通过scanner.Bytes()方法获取缓冲区中的数据。或者Text将其转换为字符串格式
    for scanner.Scan() {
        fmt.Println(scanner.Text()) // 打印每一行
    }
}

3、Scanner通过io.Reader读取数据的过程

用到的方法就是scanner.Scan()

1、详细看Scan()内部

首先看Scan是一个方法,调用它的接口类型是Scanner,下面是这个对象的三个变量

  • r:是io.Reader接口类型的对象,代表输入数据的源头。可以是文件、网络连接、标准输入等任何实现了 io.Reader 接口的数据源
  • split:是 一个可选的分割函数,用于定义如何从输入数据中提取 token(分割的标识)
  • maxTokenSize:是指定了单个 token 的最大长度,默认是64k,超过会报错!
func NewScanner(r io.Reader) *Scanner {
    return &Scanner{
        r:            r,
        split:        ScanLines,
        maxTokenSize: MaxScanTokenSize,
    }
}

从输入源(io.Reader)中读取到的数据会以字节的形式存储到内部缓冲区s.buf,之后就可以根据split,找到数据中的token(分割的标识)进行分割。

2、详细看scan扫描分割符(token)原理

比如说 “世 界” 这个字符串,采用让他用空格作为分隔符将两个字打印 世界转换为字节码就是[228 184 150 32 231 149 140],一个汉字三个字节,最中间的32,就是空格符的Ascall码,当scan()在扫描 内部缓冲区s.buf 扫到32,然后就进行分割

func main() {
    // 将字符串转换成字节数组
    str := "世 界"
    byteArr := []byte(str)
    // 输出字节数组
    fmt.Println(byteArr) //[228 184 150 32 231 149 140]// 将字符串转换为 io.Reader 类型
    reader := bytes.NewReader([]byte(str))
​
    // 创建 Scanner 对象并指定分割函数为空格
    scanner := bufio.NewScanner(reader)
    scanner.Split(bufio.ScanWords)//bufio.ScanWords指的就是token为按照单词为单位进行分割// 遍历 Scanner 输出每个 token
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    //世
    //界
}
  • 空格(space) ASCII 码值: 32
  • 制表符(Tab) ASCII 码值: 9
  • 换行符(Newline) ASCII 码值: 10 (LF, Line Feed)
  • 回车符(carriage Return) ASCII 码值: 13 (CR)
  • EOF(End of File) ASCII 码值: 4

当然记这些ASCLL码符号还不够麻烦呢,Go语言有内置的几个预定义函数,可以实现简单的分割类型

3、详细看scanner内置的Split分割函数使用

  1. bufio.ScanBytes
    • 按照字节为单位进行分割。
    • 每次返回一个字节的 []byte
    • func main() {
          input := "Hello, world!"
          scanner := bufio.NewScanner(strings.NewReader(input))
          scanner.Split(bufio.ScanBytes)
      ​
          for scanner.Scan() {
              fmt.Printf("%q ", scanner.Bytes())
          }
          if err := scanner.Err(); err != nil {
              fmt.Println("Error:", err)
          }
      }
      //"H" "e" "l" "l" "o" "," " " "w" "o" "r" "l" "d" "!"
      
  2. bufio.ScanRunes
    • 按照 Unicode 字符为单位进行分割。
    • 每次返回一个 Unicode 字符的 []rune
    • func main() {
          input := "世界"
          scanner := bufio.NewScanner(strings.NewReader(input))
          scanner.Split(bufio.ScanRunes)
      ​
          for scanner.Scan() {
              fmt.Printf("%q ", scanner.Text())
          }
          if err := scanner.Err(); err != nil {
              fmt.Println("Error:", err)
          }
      }//"" ""
      
  3. bufio.ScanLines
    • 按照行为单位进行分割。
    • 每次返回一行数据,不包括行尾的换行符。
    • func main() {
          input := "Hello,\nworld!\n"
          scanner := bufio.NewScanner(strings.NewReader(input))
          scanner.Split(bufio.ScanLines)
      ​
          for scanner.Scan() {
              fmt.Println(scanner.Text())
          }
          if err := scanner.Err(); err != nil {
              fmt.Println("Error:", err)
          }
      }
      //Hello,
      //world!
      
  4. bufio.ScanWords
    • 按照单词为单位进行分割。
    • 每次返回一个由非空白字符组成的连续片段。
    • func main() {
          input := "Hello world"
          scanner := bufio.NewScanner(strings.NewReader(input))
          scanner.Split(bufio.ScanWords)
      ​
          for scanner.Scan() {
              fmt.Println(scanner.Text())
          }
          if err := scanner.Err(); err != nil {
              fmt.Println("Error:", err)
          }
      }
      //Hello
      //world
      
  5. bufio.ScanRegexp
    • 按照正则表达式匹配的内容进行分割。
    • 用户需要提供一个正则表达式作为参数。
    • func main() {
          input := "apple123banana456cherry"
          scanner := bufio.NewScanner(strings.NewReader(input))
          scanner.Split(bufio.ScanRegexp(regexp.MustCompile(`\d+`)))//匹配一个或多个数字字符
      ​
          for scanner.Scan() {
              fmt.Println(scanner.Text())
          }
          if err := scanner.Err(); err != nil {
              fmt.Println("Error:", err)
          }
      }
      //123
      //456
      

4、自定义分割符(token)使用

最后如果上面的几个封装的都不够用,可以通过自定义token

func main() {
    scanner := bufio.NewScanner(os.Stdin)
​
    // 自定义分割函数,以逗号为分隔符
    scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        // 查找第一个逗号的位置
        i := strings.IndexByte(string(data), ',')
        if i >= 0 {
            return i + 1, data[:i], nil
        }
        // 如果没有找到逗号,且已经到达输入流的末尾,则返回结束
        if atEOF {
            return len(data), data, nil
        }
        // 否则要求 Scan 方法继续读取数据
        return 0, nil, nil
    })
​
    for scanner.Scan() {
        fmt.Println("Token:", scanner.Text())
    }
​
    if err := scanner.Err(); err != nil {
        fmt.Println("Error:", err)
    }
}

4、如果判断扫描到的数据是什么类型的数据

在 Go 中,标准输入的数据都是通过io.Reader以字节数组的形式暂存的。

那么怎么判断标准输入中的内容是字符串?还是整数?还是浮点数?......

  • 可以通过strconv包的函数来做判断
    1、判断是否为整数:
    ​
    val, err := strconv.Atoi(string(inputBytes))
    if err == nil {
        // inputBytes 包含一个整数
    } else {
        // inputBytes 不是整数
    }
    2、判断是否为浮点数:
    ​
    val, err := strconv.ParseFloat(string(inputBytes), 64)
    if err == nil {
        // inputBytes 包含一个浮点数
    } else {
        // inputBytes 不是浮点数
    }
    3、将其视为字符串:
    ​
    inputStr := string(inputBytes)
    // 现在 inputStr 包含从标准输入读取的字符串数据
    
  • 也可以使用类型断言从标准输入读取数据类型
    func main() {
        reader := bufio.NewReader(os.Stdin)
        inputBytes, _ := reader.ReadBytes('\n')
    ​
        // 使用类型断言判断数据类型
        switch v := interface{}(inputBytes).(type) {
        case int:
            fmt.Printf("Input is an integer: %d\n", v)
        case float64:
            fmt.Printf("Input is a float: %f\n", v)
        default:
            inputStr := string(inputBytes)
            fmt.Printf("Input is a string: %s\n", inputStr)
        }
    }