昨天我不得不在go中执行一个任务。
正如您可能已经从本文中了解到的那样,我最近开始使用Go进行编程,因此,我脑海中常浮现的第一个问题是:我将如何在Java中进行编程的?
我不确定它是好是坏,但我发现从多个角度看同一件事对我有些好处。这不是使用Go或Java,而是使用函数式或命令式,并混合不同的概念,我从中看到了真正的价值。
我将记录如何从不同的角度看待问题,从而形成解决方案。
问题
我将仅简单地完成一些任务,来突出本文中的有趣之处。 任务的一部分涉及将文件的一部分加载到内存中:一个超级简单的解析器。
该文件如下所示:
// a comment here
#HEADER
Key1=value1
//another comment
Key2=value2
Key3=value3
Key4=value4
我们对属于一个部分的两个键感兴趣。 该部分以标头开头,标头以井号标签开头。 我们将获得该部分的名称和两个键的名称。 即部分:“人”,键:[“名称”,“姓”] 因此,我们想在文件中找到“人”部分,并将名称和姓氏的值加载到内存中。
关于文件的一些注意事项:
- 标头可能不在文件中
- 文件中可能没有名称和/或
- 姓氏名称始终在文件中位于姓氏之前
- 它可能包含注释(//)或其他标记
一种可能的解决方案是简单地逐行读取文件并:
- 如果该行以#开头,则这是节的开始,请准备读取此标头的密钥对
- 读名字和姓氏
- 忽略注释和其余键
所以,让我们暂时戴上Java程序员的帽子。
所以,逐行读取文件,过滤某些行……
这看起来像是java.nio.file的工作、java.util.stream文件.流和自定义java.util.函数、谓词过滤流。
我说习惯,意思是我们需要在谓词中保持某种状态。状态只是谓词下次调用时应该匹配的字符串,也就是说,如果它已经匹配了报头,那么应该匹配name next。
这基本上意味着两件事:Stream和Lambda函数。
简而言之,Golang中没有像流这样的东西。 让我在这里稍作改动,这种精神锻炼的全部目的不是实现与语言本身相反的东西,而是从中获得最大的收益。 所以没有流的话, 我们将在没有它的情况下生活来进行救援。
但是lambdas呢? 好吧,Go中的功能是一等公民。 我们可以将它们分配给变量、创建函数数组,像其他任何参数一样传递它们,所以我想我们有lambda :)
好的讨论,让我们看一下代码... 在查看代码之前,请先回顾一下我们想做的事情。 因此,我们希望有一个for循环,在该循环中,我们逐行从源代码中读取内容,并针对每一行检查是否应将其写入某处。 检查是否应写入该行的位是一个过滤器,该过滤器在内部保持状态。
好吧,让我们看看这一点:
func copy(profileName string, in io.Reader, out io.Writer) error {
var (
line string
readError error
writeError error
)
profileFilter := newProfileFilter(profileName)
reader := bufio.NewReader(in)
for {
line, readError = reader.ReadString('\n')
if profileFilter.match(line) {
_, writeError = io.WriteString(out, line)
}
if readError != nil || writeError != nil {
break
}
}
if writeError != nil {
return writeError
}
if readError != io.EOF {
return readError
}
return nil
}
这与Java几乎“等效”
Files.lines(resource())
.filter(profileFilter)
.forEach(ProfileWriter::write);
让我们分解一下:
- Files.lines是for循环
- 过滤器是if条件profileFilter.match(行)
- forEach只是if条件io的主体。WriteString(线),
因此,让我们看一下过滤器:
func newProfileFilter(profileName string) *profileFilter {
var matchers [](func(line string) bool)
matchers = append(matchers,
matcher(startsWith).apply("#"+profileName),
matcher(startsWith).apply("name="),
matcher(startsWith).apply("surname="),
)
return &profileFilter{matchers, profileName}
}
func startsWith(line string, toMatch string) bool {
return strings.HasPrefix(
strings.ToLower(strings.TrimSpace(line)),
strings.ToLower(toMatch),
)
}
这是profileFilter的构造函数,它负责检测该节的标题和键。 如果需要的话,它基本上是一个函数或lambda数组。 每次过滤器与文件中的一行匹配时,我们都会从数组中弹出一个元素,因此在下一次迭代中,过滤器将与其他字符串匹配。 我从代码中真正喜欢的是我们构造lambda的方式:
matcher(startsWith).apply("name=")
因此,我们正在构建一个匹配器,它匹配以“name=”开始的行
什么是真正的匹配器?
type matcher func(line string, toMatch string) bool
这是函数的类型别名。 因此,这只是一个函数,它获得输入作为匹配的行和字符串并返回布尔值。就是这样。 语法Matcher(startsWith)只是一个强制转换,我们正在将功能startsWith强制转换为匹配器。 当然可以做到这一点,因为匹配器是一个函数!
当我写这篇文章的时候,我就像...好吧,go中的函数真是太酷了
那个应用方法是什么?
func (f matcher) apply(toMatch string) func(line string) bool {
return func(line string) bool {
return f(line, toMatch)
}
}
这只是一种处理函数的方法。长话短说,我们有一个有两个参数的函数,我们想要一个有一个参数的函数另一个参数已经设置好了。
在这里,我们应用我们想要匹配的字符串,比如"name="然后我们得到一个函数它的参数只有一行。
我们将apply方法附加到matcher中,所以最终的结果是通过调用:
matcher(startsWith).apply("name=")
我们返回一个函数,该函数返回一个布尔值(java域中的Predicate),如果该行以name开头,则该布尔值为true。
仅出于完整性考虑,这是这里的其余代码。
type profileFilter struct {
matchers []func(line string) bool
profileName string
}
func (p *profileFilter) match(text string) bool {
if len(p.matchers) == 0 {
return false
}
shouldFilter := p.matchers[0](text)
if shouldFilter {
p.matchers = p.matchers[1:len(p.matchers)]
}
return shouldFilter
}
Java 版本
在写这篇文章时,我是为了娱乐,实际上是可以用Java编写相同的程序。 这是一个快速实现:
public static void main(String[] args) throws IOException, URISyntaxException{
Matcher startsWith = (line, toMatch) -> startsWith(line, toMatch);
ProfileFilter profileFilter =
new ProfileFilter(
startsWith.apply("#some-profile"),
startsWith.apply("key2="),
startsWith.apply("key3=")
);
Files.lines(resource())
.filter(profileFilter)
.forEach(System.out::println);
}
static class ProfileFilter implements Predicate<String> {
private LinkedList<Predicate<String>> predicates;
ProfileFilter(Predicate<String>...predicates) {
this.predicates = new LinkedList<>(Arrays.asList(predicates));
}
@Override
public boolean test(String s) {
if (predicates.size() == 0) {
return false;
}
boolean shouldFilter = predicates.getFirst().test(s);
if (shouldFilter) {
predicates.removeFirst();
}
return shouldFilter;
}
}
interface Matcher extends BiPredicate<String, String> {
default Predicate<String> apply(String applied) {
return s -> this.test(s, applied);
}
}
static Boolean startsWith(String line, String toMatch) {
return line.toLowerCase().trim().startsWith(toMatch.toLowerCase());
}
这两个版本显然非常相似,不同的是,在Java版本中,我使用流而不是for循环,并且在控制台上打印行而不是将其写入Writer。
这里有一个附加说明:流确实很酷,但是它们不能处理异常。例如,写入文件就是引发异常的操作之一,因此在实际的用例中,我认为我要么使用for循环,而不是流,要么使用RxJava Observable。
结论
好吧,这比我预期的要长,考虑到我只想说Golang可能不是那么糟糕的XD。
玩笑归玩笑,希望你喜欢。
谢谢!
资源资源
文章代码:github
我找到了两个关于Golang函数的有趣的演讲。
完全值得一看:
闭包是go的泛型
不要成就一流的功能