浅析golang的strings.split设计

# 背景

在日志中发现存在着空指针报错,看报错行数,是属于在json对象中,没取到对应的值。顺着线索找到了对应的接口,作用是查询数据库数据,支持批量查询,多个id使用逗号分隔,例如:

1
{"ids":"1,2,3,4,5"}

改项目基础语言是golang,在strings包中存在Split方法,支持对字符串进行分隔,返回字符串切片数组,由此产生了一个意想不到的结果。

# 问题导引

项目中的代码不变展示,写了一段新代码,把主要受影响的代码段展示出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"fmt"
	"strings"
)

func main() {

	str := ""
	strList := strings.Split(str, ",")

	for _, s := range strList {
		fmt.Println("asd:", s)
	}
}

根据经验,大家觉得这段代码会打印出asd这一行吗?

结果如下,竟然执行了for循环

1
2
$ go run main.go
asd:

后面又打印出切片的容量和长度,事实上切片中包含了一个为空的字符串。试想一下,字符串为空,返回的切片长度应该是零才对吧。

# strings.Split源码

接着去翻看对应的源码,最后都会调用到genSplit函数,当sep不等于空字符串时,切片长度必然是大于或等于1的。

 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 Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }

func genSplit(s, sep string, sepSave, n int) []string {
	if n == 0 {
		return nil
	}
	if sep == "" {
		return explode(s, n)
	}
	if n < 0 {
		n = Count(s, sep) + 1
	}

	a := make([]string, n)
	n--
	i := 0
	for i < n {
		m := Index(s, sep)
		if m < 0 {
			break
		}
		a[i] = s[:m+sepSave]
		s = s[m+len(sep):]
		i++
	}
	a[i] = s
	return a[:i+1]
}

为什么golang这段代码会这么设计呢?好奇

# 其他语言分隔字符串

接着我查了下其他语言分隔字符串方法,是否都是这样的设计。

# Java

1
2
3
4
5
6
7
8
9
public class HelloWorld {
    public static void main(String[] args) {
        String str = "";
        String[] strList = str.split(",");
        for (int i = 0; i < strList.length; i++) {
            System.out.println("asd:"+strList[i]);
        }
    }
}

结果:

1
asd:

# PHP

1
2
3
4
5
6
//php5支持
<?php
$str = ""; 
$strList = split('[/.-]', $str); 
echo var_dump($strList);
?>
1
2
3
4
5
6
//php7支持
<?php
$str = "";
$strList2 = explode(',', $str);
echo var_dump($strList2);
?>

结果:

1
2
3
4
5
6
7
8
array(1) {
  [0]=>
  string(0) ""
}
array(1) {
  [0]=>
  string(0) ""
}

看来是我想多了,几种流行语言,字符串切割都是会包含空字符串。

# 结论

几种流行语言split函数返回结果是一样的,在日后开发中,需要对接口的边界条件做充分的测试。

Licensed under CC BY-NC-SA 4.0