Linux
在运行 Linux 命令时,有时我们想在看到运行结果的同时,又能把结果保存到文件中。
小白用户可能会这样操作:
# 1. 运行命令echo hello world# 2. 将结果复制,然后打开 vim ,将刚才的内容粘贴进去保存。
稍稍有经验的人可能会这样做:
# 1. 运行命令并将结果重定向到文件echo hello world > my.file# 2. 查看运行结果cat my.file
而本篇讲述的 tee
,它是一个 Linux 命令,它从标准输入读取数据并回显到标准输出,同时将其写入到一个或更多的文件中。所以,
一个有经验的开发会这样解决题述要求:
# 简单到只要这样echo hello world | tee my.file
如果只是写到这里,未免不值用一篇文章来介绍。实际上,这里要介绍的是一种思想(或实践?):编程过程中偶尔会遇到对一个输入做多种处理的情况,上面的 tee
命令即是对这种场景的封装运用。
其它语言也有类似的例子。
Go
如果你了解 Go 语言,很可能已经知道它的标准 i/o 库中有个 TeeReader
,它的签名如下:
func TeeReader(r Reader, w Writer) Reader
等你知道它的用途,一定会觉得它和 Linux 下的 tee
命令很像:它返回一个新的 Reader
,在读取 r
的同时,会将读取结果写入到 w
。
你可能会好奇它的实际用途是什么。举个例子,有些流只能读取一次,再读时会报错。HTTP request body 也是这种流的一种。
试想,如果我们想在服务端将 request body 打印到日志中,由于它只能被读一次,日志服务读取并打印完它后,后续的业务代码要再次读取并解析 request body 就会报错。一个解决方式即是用 TeeReader
。
而在 Java 中,如果要完成同样的打印 request body 的操作且不影响后续的业务,可能要自己写实现,或者利用第三方库了。
不过,Java 中还有其它有关 tee 的这种场景(一个输入多种操作)。
Java
之前的一篇文章有介绍怎么从一个 Stream 中取出多值:自定义 Stream Collector 的一个现实例子:从 stream 中获取多个值。
针对这种需求,Java 在其版本 12 中也提供了一个 Collectors.teeing()
方法,它的签名是:
public static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger)
(啊,Java确实繁琐!)
如果你熟悉 Collector
,可以很容易猜到,这个方法最终是把两个 collector
的结果值,借助 BiFunction
进行合并返回。这样做的好处是,它既避免了自己去写自定义的 Collector
实现,又能复用 jdk 自身提供的 Collectors
工具方法。
总结
对于 一个输入&多种处理需求 这样的场景,文章提供了3个语言环境下的相应实践,供大家思考借鉴:
自己碰到类似的需求时,如何封装一个简单易用的 API?