2020年2月

第一篇文章中,我提到,项目的自动部署是放在 now.sh 上,以方便预览。但出于用户体验和速度的考虑,我们选择了国内的七牛云作为页面的承载。不过,七牛毕竟是一个对象存储,而不是一个专业的静态托管业务,在使用上遇到了一些坑,还好经过努力都得到了解决。

七牛的 Bucket 名规则

和绝大多数的云计算厂商一样,七牛也使用了 Bucket 来作为存储的单元。

由于这个项目要挂 Linux.cn 的二级域名,于是我便让老王创建了一个 Bucket,绑定域名,并通过七牛自带的权限控制机制,将其分发给我,让我来使用。

在我的个人控制台看到了这个 Bucket:

发现问题

我通过控制台,手动上传了生成的文件后,确认没有问题,就将相应的功能写入到 Github Action 的 配置文件(配置文件点这里)中,实现自动的部署。但在部署过程中,屡次报错,不知道为什么。在开启了 DEBUG 信息后发现,竟然是 Bucket 不存在(点我查看 CI 的构建信息)。

解决问题

和老王沟通以后才发现,是七牛的 Bucket 名机制的问题。

在七牛中进行权限分配的时候,会要求你为 Bucket 设定一个别名,而且名字和已有的名字必须是不同的,这导致我看到的 Bucket 的名和老王创建的 Bucket 名是不相同的。

而我使用的 AK 和 SK 又是让老王设置在 Github 后台的 Secrets,Bucket 则是我自己设置的,所以就出现了问题。具体来说,是下面这张图。

由于我填写的 Bucket 是我自己看到的,而不是老王那边真正的 Bucket 名称,导致在上传的时候,无法找到 Bucket。在将 Bucket 名称替换为老王那边看到的 Bucket 名称后,问题得到解决。

七牛不支持 Vue Router 的 History 模式

第二篇文章中,我提到了引入了 Vue 的 History 模式来优化体验。但是,七牛本身作为一个存储系统,没有转发的功能,也就导致其没有办法很好的支持 Vue History 模式。

在经过一番研究后,找到了解决方案,就是将 index 页面,同时作为 404 页面,这样就可以实现从某种意义上的将所有请求都转发给 Index 页面。

你需要做的,就是将 index.html 复制一份,并重命名为 errno-404,并和其他文件一同上传,这样用户请求一些不存在的文件时,会自动将请求转发给 errno-404, 又因为这个文件的内容是索引文件的内容,所以就可以实现了请求的转发。

相关代码的实现,你可以在 https://github.com/LCTT/tldr.linux.cn/blob/master/.github/workflows/nodejs.yml 这里找到。

总结

在这篇文章中,介绍了七牛的 Bucket 问题,以及 Vue Router History 模式在七牛下的解决方案。

在我们的 20 个使用开源提升生产力的系列的第十篇文章中,使用 Rainbow Stream 跟上你的 Twitter 流而无需离开终端。

去年,我在 19 天里给你介绍了 19 个新(对你而言)的生产力工具。今年,我换了一种方式:使用你在使用或者还没使用的工具,构建一个使你可以在新一年更加高效的环境。

通过 Rainbow Stream 跟上Twitter

我喜欢社交网络和微博。它快速、简单,还有我可以与世界分享我的想法。当然,缺点是几乎所有非 Windows 的桌面客户端都对是网站的封装。Twitter 有很多客户端,但我真正想要的是轻量、易于使用,最重要的是吸引人的客户端。

 title=

Rainbow Stream 是好看的 Twitter 客户端之一。它简单易用,并且可以通过 pip3 install rainbowstream 快速安装。第一次运行时,它将打开浏览器窗口,并让你通过 Twitter 授权。完成后,你将回到命令行,你的 Twitter 时间线将开始滚动。

 title=

要了解的最重要的命令是 p 暂停推流、r 继续推流、h 得到帮助,以及 t 发布新的推文。例如,h tweets 将提供发送和回复推文的所有选项。另一个有用的帮助页面是 h messages,它提供了处理直接消息的命令,这是我妻子和我经常使用的东西。还有很多其他命令,我会回头获得很多帮助。

随着时间线的滚动,你可以看到它有完整的 UTF-8 支持,并以正确的字体显示推文被转推以及喜欢的次数,图标和 emoji 也能正确显示。

关于 Rainbow Stream 的最好功能之一就是你不必放弃照片和图像。默认情况下,此功能是关闭的,但是你可以使用 config 命令尝试它。

config IMAGE_ON_TERM = true

此命令将任何图像渲染为 ASCII 艺术。如果你有大量照片流,它可能会有点多,但是我喜欢。它有非常复古的 1990 年代 BBS 感觉,我也确实喜欢 1990 年代的 BBS 场景。

你还可以使用 Rainbow Stream 管理列表、屏蔽某人、拉黑某人、关注、取消关注以及 Twitter API 的所有其他功能。它还支持主题,因此你可以用喜欢的颜色方案自定义流。

当我正在工作并且不想在浏览器上打开另一个选项卡时,Rainbow Stream 让我可以留在终端中。


via: https://opensource.com/article/20/1/tweet-terminal-rainbow-stream

作者:Kevin Sonney 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

学习如何使用 Java 8 中的流 API 和函数式编程结构。

当 Java SE 8(又名核心 Java 8)在 2014 年被推出时,它引入了一些更改,从根本上影响了用它进行的编程。这些更改中有两个紧密相连的部分:流 API 和函数式编程构造。本文使用代码示例,从基础到高级特性,介绍每个部分并说明它们之间的相互作用。

基础特性

流 API 是在数据序列中迭代元素的简洁而高级的方法。包 java.util.streamjava.util.function 包含了用于流 API 和相关函数式编程构造的新库。当然,代码示例胜过千言万语。

下面的代码段用大约 2,000 个随机整数值填充了一个 List

Random rand = new Random2();
List<Integer> list = new ArrayList<Integer>();           // 空 list
for (int i = 0; i < 2048; i++) list.add(rand.nextInt()); // 填充它

另外用一个 for 循环可用于遍历填充列表,以将偶数值收集到另一个列表中。

流 API 提供了一种更简洁的方法来执行此操作:

List <Integer> evens = list
    .stream()                      // 流化 list
    .filter(n -> (n & 0x1) == 0)   // 过滤出奇数值
    .collect(Collectors.toList()); // 收集偶数值

这个例子有三个来自流 API 的函数:

  • stream 函数可以将集合转换为流,而流是一个每次可访问一个值的传送带。流化是惰性的(因此也是高效的),因为值是根据需要产生的,而不是一次性产生的。
  • filter 函数确定哪些流的值(如果有的话)通过了处理管道中的下一个阶段,即 collect 阶段。filter 函数是 高阶的 higher-order ,因为它的参数是一个函数 —— 在这个例子中是一个 lambda 表达式,它是一个未命名的函数,并且是 Java 新的函数式编程结构的核心。

lambda 语法与传统的 Java 完全不同:

n -> (n & 0x1) == 0

箭头(一个减号后面紧跟着一个大于号)将左边的参数列表与右边的函数体分隔开。参数 n 虽未明确类型,但也可以明确。在任何情况下,编译器都会发现 n 是个 Integer。如果有多个参数,这些参数将被括在括号中,并用逗号分隔。

在本例中,函数体检查一个整数的最低位(最右)是否为零,这用来表示偶数。过滤器应返回一个布尔值。尽管可以,但该函数的主体中没有显式的 return。如果主体没有显式的 return,则主体的最后一个表达式即是返回值。在这个例子中,主体按照 lambda 编程的思想编写,由一个简单的布尔表达式 (n & 0x1) == 0 组成。

  • collect 函数将偶数值收集到引用为 evens 的列表中。如下例所示,collect 函数是线程安全的,因此,即使在多个线程之间共享了过滤操作,该函数也可以正常工作。

方便的功能和轻松实现多线程

在生产环境中,数据流的源可能是文件或网络连接。为了学习流 API, Java 提供了诸如 IntStream 这样的类型,它可以用各种类型的元素生成流。这里有一个 IntStream 的例子:

IntStream                          // 整型流
    .range(1, 2048)                // 生成此范围内的整型流
    .parallel()                    // 为多个线程分区数据
    .filter(i -> ((i & 0x1) > 0))  // 奇偶校验 - 只允许奇数通过
    .forEach(System.out::println); // 打印每个值

IntStream 类型包括一个 range 函数,该函数在指定的范围内生成一个整数值流,在本例中,以 1 为增量,从 1 递增到 2048。parallel 函数自动划分该工作到多个线程中,在各个线程中进行过滤和打印。(线程数通常与主机系统上的 CPU 数量匹配。)函数 forEach 参数是一个方法引用,在本例中是对封装在 System.out 中的 println 方法的引用,方法输出类型为 PrintStream。方法和构造器引用的语法将在稍后讨论。

由于具有多线程,因此整数值整体上以任意顺序打印,但在给定线程中是按顺序打印的。例如,如果线程 T1 打印 409 和 411,那么 T1 将按照顺序 409-411 打印,但是其它某个线程可能会预先打印 2045。parallel 调用后面的线程是并发执行的,因此它们的输出顺序是不确定的。

map/reduce 模式

map/reduce 模式在处理大型数据集方面变得很流行。一个 map/reduce 宏操作由两个微操作构成。首先,将数据分散( 映射 mapped )到各个工作程序中,然后将单独的结果收集在一起 —— 也可能收集统计起来成为一个值,即 归约 reduction 。归约可以采用不同的形式,如以下示例所示。

下面 Number 类的实例用 EVENODD 表示有奇偶校验的整数值:

public class Number {
    enum Parity { EVEN, ODD }
    private int value;
    public Number(int n) { setValue(n); }
    public void setValue(int value) { this.value = value; }
    public int getValue() { return this.value; }
    public Parity getParity() {
        return ((value & 0x1) == 0) ? Parity.EVEN : Parity.ODD;
    }
    public void dump() {
        System.out.format("Value: %2d (parity: %s)\n", getValue(),
                          (getParity() == Parity.ODD ? "odd" : "even"));
    }
}

下面的代码演示了用 Number 流进行 map/reduce 的情形,从而表明流 API 不仅可以处理 intfloat 等基本类型,还可以处理程序员自定义的类类型。

在下面的代码段中,使用了 parallelStream 而不是 stream 函数对随机整数值列表进行流化处理。与前面介绍的 parallel 函数一样,parallelStream 变体也可以自动执行多线程。

final int howMany = 200;
Random r = new Random();
Number[] nums = new Number[howMany];
for (int i = 0; i < howMany; i++) nums[i] = new Number(r.nextInt(100));
List<Number> listOfNums = Arrays.asList(nums);  // 将数组转化为 list

Integer sum4All = listOfNums
    .parallelStream()           // 自动执行多线程
    .mapToInt(Number::getValue) // 使用方法引用,而不是 lambda
    .sum();                     // 将流值计算出和值
System.out.println("The sum of the randomly generated values is: " + sum4All);

高阶的 mapToInt 函数可以接受一个 lambda 作为参数,但在本例中,它接受一个方法引用,即 Number::getValuegetValue 方法不需要参数,它返回给定的 Number 实例的 int 值。语法并不复杂:类名 Number 后跟一个双冒号和方法名。回想一下先前的例子 System.out::println,它在 System 类中的 static 属性 out 后面有一个双冒号。

方法引用 Number::getValue 可以用下面的 lambda 表达式替换。参数 n 是流中的 Number 实例中的之一:

mapToInt(n -> n.getValue())

通常,lambda 表达式和方法引用是可互换的:如果像 mapToInt 这样的高阶函数可以采用一种形式作为参数,那么这个函数也可以采用另一种形式。这两个函数式编程结构具有相同的目的 —— 对作为参数传入的数据执行一些自定义操作。在两者之间进行选择通常是为了方便。例如,lambda 可以在没有封装类的情况下编写,而方法则不能。我的习惯是使用 lambda,除非已经有了适当的封装方法。

当前示例末尾的 sum 函数通过结合来自 parallelStream 线程的部分和,以线程安全的方式进行归约。但是,程序员有责任确保在 parallelStream 调用引发的多线程过程中,程序员自己的函数调用(在本例中为 getValue)是线程安全的。

最后一点值得强调。lambda 语法鼓励编写 纯函数 pure function ,即函数的返回值仅取决于传入的参数(如果有);纯函数没有副作用,例如更新一个类中的 static 字段。因此,纯函数是线程安全的,并且如果传递给高阶函数的函数参数(例如 filtermap )是纯函数,则流 API 效果最佳。

对于更细粒度的控制,有另一个流 API 函数,名为 reduce,可用于对 Number 流中的值求和:

Integer sum4AllHarder = listOfNums
    .parallelStream()                           // 多线程
    .map(Number::getValue)                      // 每个 Number 的值
    .reduce(0, (sofar, next) -> sofar + next);  // 求和

此版本的 reduce 函数带有两个参数,第二个参数是一个函数:

  • 第一个参数(在这种情况下为零)是特征值,该值用作求和操作的初始值,并且在求和过程中流结束时用作默认值。
  • 第二个参数是累加器,在本例中,这个 lambda 表达式有两个参数:第一个参数(sofar)是正在运行的和,第二个参数(next)是来自流的下一个值。运行的和以及下一个值相加,然后更新累加器。请记住,由于开始时调用了 parallelStream,因此 mapreduce 函数现在都在多线程上下文中执行。

在到目前为止的示例中,流值被收集,然后被规约,但是,通常情况下,流 API 中的 Collectors 可以累积值,而不需要将它们规约到单个值。正如下一个代码段所示,收集活动可以生成任意丰富的数据结构。该示例使用与前面示例相同的 listOfNums

Map<Number.Parity, List<Number>> numMap = listOfNums
    .parallelStream()
    .collect(Collectors.groupingBy(Number::getParity));

List<Number> evens = numMap.get(Number.Parity.EVEN);
List<Number> odds = numMap.get(Number.Parity.ODD);

第一行中的 numMap 指的是一个 Map,它的键是一个 Number 奇偶校验位(ODDEVEN),其值是一个具有指定奇偶校验位值的 Number 实例的 List。同样,通过 parallelStream 调用进行多线程处理,然后 collect 调用(以线程安全的方式)将部分结果组装到 numMap 引用的 Map 中。然后,在 numMap 上调用 get 方法两次,一次获取 evens,第二次获取 odds

实用函数 dumpList 再次使用来自流 API 的高阶 forEach 函数:

private void dumpList(String msg, List<Number> list) {
    System.out.println("\n" + msg);
    list.stream().forEach(n -> n.dump()); // 或者使用 forEach(Number::dump)
}

这是示例运行中程序输出的一部分:

The sum of the randomly generated values is: 3322
The sum again, using a different method:     3322

Evens:

Value: 72 (parity: even)
Value: 54 (parity: even)
...
Value: 92 (parity: even)

Odds:

Value: 35 (parity: odd)
Value: 37 (parity: odd)
...
Value: 41 (parity: odd)

用于代码简化的函数式结构

函数式结构(如方法引用和 lambda 表达式)非常适合在流 API 中使用。这些构造代表了 Java 中对高阶函数的主要简化。即使在糟糕的过去,Java 也通过 MethodConstructor 类型在技术上支持高阶函数,这些类型的实例可以作为参数传递给其它函数。由于其复杂性,这些类型在生产级 Java 中很少使用。例如,调用 Method 需要对象引用(如果方法是非静态的)或至少一个类标识符(如果方法是静态的)。然后,被调用的 Method 的参数作为对象实例传递给它,如果没有发生多态(那会出现另一种复杂性!),则可能需要显式向下转换。相比之下,lambda 和方法引用很容易作为参数传递给其它函数。

但是,新的函数式结构在流 API 之外具有其它用途。考虑一个 Java GUI 程序,该程序带有一个供用户按下的按钮,例如,按下以获取当前时间。按钮按下的事件处理程序可能编写如下:

JButton updateCurrentTime = new JButton("Update current time");
updateCurrentTime.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        currentTime.setText(new Date().toString());
    }
});

这个简短的代码段很难解释。关注第二行,其中方法 addActionListener 的参数开始如下:

new ActionListener() {

这似乎是错误的,因为 ActionListener 是一个抽象接口,而抽象类型不能通过调用 new 实例化。但是,事实证明,还有其它一些实例被实例化了:一个实现此接口的未命名内部类。如果上面的代码封装在名为 OldJava 的类中,则该未命名的内部类将被编译为 OldJava$1.classactionPerformed 方法在这个未命名的内部类中被重写。

现在考虑使用新的函数式结构进行这个令人耳目一新的更改:

updateCurrentTime.addActionListener(e -> currentTime.setText(new Date().toString()));

lambda 表达式中的参数 e 是一个 ActionEvent 实例,而 lambda 的主体是对按钮上的 setText 的简单调用。

函数式接口和函数组合

到目前为止,使用的 lambda 已经写好了。但是,为了方便起见,我们可以像引用封装方法一样引用 lambda 表达式。以下一系列简短示例说明了这一点。

考虑以下接口定义:

@FunctionalInterface // 可选,通常省略
interface BinaryIntOp {
    abstract int compute(int arg1, int arg2); // abstract 声明可以被删除
}

注释 @FunctionalInterface 适用于声明唯一抽象方法的任何接口;在本例中,这个抽象接口是 compute。一些标准接口,(例如具有唯一声明方法 runRunnable 接口)同样符合这个要求。在此示例中,compute 是已声明的方法。该接口可用作引用声明中的目标类型:

BinaryIntOp div = (arg1, arg2) -> arg1 / arg2;
div.compute(12, 3); // 4

java.util.function 提供各种函数式接口。以下是一些示例。

下面的代码段介绍了参数化的 Predicate 函数式接口。在此示例中,带有参数 StringPredicate<String> 类型可以引用具有 String 参数的 lambda 表达式或诸如 isEmpty 之类的 String 方法。通常情况下,Predicate 是一个返回布尔值的函数。

Predicate<String> pred = String::isEmpty; // String 方法的 predicate 声明
String[] strings = {"one", "two", "", "three", "four"};
Arrays.asList(strings)
   .stream()
   .filter(pred)                  // 过滤掉非空字符串
   .forEach(System.out::println); // 只打印空字符串

在字符串长度为零的情况下,isEmpty Predicate 判定结果为 true。 因此,只有空字符串才能进入管道的 forEach 阶段。

下一段代码将演示如何将简单的 lambda 或方法引用组合成更丰富的 lambda 或方法引用。考虑这一系列对 IntUnaryOperator 类型的引用的赋值,它接受一个整型参数并返回一个整型值:

IntUnaryOperator doubled = n -> n * 2;
IntUnaryOperator tripled = n -> n * 3;
IntUnaryOperator squared = n -> n * n;

IntUnaryOperator 是一个 FunctionalInterface,其唯一声明的方法为 applyAsInt。现在可以单独使用或以各种组合形式使用这三个引用 doubledtripledsquared

int arg = 5;
doubled.applyAsInt(arg); // 10
tripled.applyAsInt(arg); // 15
squared.applyAsInt(arg); // 25

以下是一些函数组合的样例:

int arg = 5;
doubled.compose(squared).applyAsInt(arg); // 5 求 2 次方后乘 2:50
tripled.compose(doubled).applyAsInt(arg); // 5 乘 2 后再乘 3:30
doubled.andThen(squared).applyAsInt(arg); // 5 乘 2 后求 2 次方:100
squared.andThen(tripled).applyAsInt(arg); // 5 求 2 次方后乘 3:75

函数组合可以直接使用 lambda 表达式实现,但是引用使代码更简洁。

构造器引用

构造器引用是另一种函数式编程构造,而这些引用在比 lambda 和方法引用更微妙的上下文中非常有用。再一次重申,代码示例似乎是最好的解释方式。

考虑这个 POJO 类:

public class BedRocker { // 基岩的居民
    private String name;
    public BedRocker(String name) { this.name = name; }
    public String getName() { return this.name; }
    public void dump() { System.out.println(getName()); }
}

该类只有一个构造函数,它需要一个 String 参数。给定一个名字数组,目标是生成一个 BedRocker 元素数组,每个名字代表一个元素。下面是使用了函数式结构的代码段:

String[] names = {"Fred", "Wilma", "Peebles", "Dino", "Baby Puss"};

Stream<BedRocker> bedrockers = Arrays.asList(names).stream().map(BedRocker::new);
BedRocker[] arrayBR = bedrockers.toArray(BedRocker[]::new);

Arrays.asList(arrayBR).stream().forEach(BedRocker::dump);

在较高的层次上,这个代码段将名字转换为 BedRocker 数组元素。具体来说,代码如下所示。Stream 接口(在包 java.util.stream 中)可以被参数化,而在本例中,生成了一个名为 bedrockersBedRocker 流。

Arrays.asList 实用程序再次用于流化一个数组 names,然后将流的每一项传递给 map 函数,该函数的参数现在是构造器引用 BedRocker::new。这个构造器引用通过在每次调用时生成和初始化一个 BedRocker 实例来充当一个对象工厂。在第二行执行之后,名为 bedrockers 的流由五项 BedRocker 组成。

这个例子可以通过关注高阶 map 函数来进一步阐明。在通常情况下,一个映射将一个类型的值(例如,一个 int)转换为另一个相同类型的值(例如,一个整数的后继):

map(n -> n + 1) // 将 n 映射到其后继

然而,在 BedRocker 这个例子中,转换更加戏剧化,因为一个类型的值(代表一个名字的 String)被映射到一个不同类型的值,在这个例子中,就是一个 BedRocker 实例,这个字符串就是它的名字。转换是通过一个构造器调用来完成的,它是由构造器引用来实现的:

map(BedRocker::new) // 将 String 映射到 BedRocker

传递给构造器的值是 names 数组中的其中一项。

此代码示例的第二行还演示了一个你目前已经非常熟悉的转换:先将数组先转换成 List,然后再转换成 Stream

Stream<BedRocker> bedrockers = Arrays.asList(names).stream().map(BedRocker::new);

第三行则是另一种方式 —— 流 bedrockers 通过使用数组构造器引用 BedRocker[]::new 调用 toArray 方法:

BedRocker[ ] arrayBR = bedrockers.toArray(BedRocker[]::new);

该构造器引用不会创建单个 BedRocker 实例,而是创建这些实例的整个数组:该构造器引用现在为 BedRocker[]:new,而不是 BedRocker::new。为了进行确认,将 arrayBR 转换为 List,再次对其进行流式处理,以便可以使用 forEach 来打印 BedRocker 的名字。

Fred
Wilma
Peebles
Dino
Baby Puss

该示例对数据结构的微妙转换仅用几行代码即可完成,从而突出了可以将 lambda,方法引用或构造器引用作为参数的各种高阶函数的功能。

柯里化 Currying

柯里化函数是指减少函数执行任何工作所需的显式参数的数量(通常减少到一个)。(该术语是为了纪念逻辑学家 Haskell Curry。)一般来说,函数的参数越少,调用起来就越容易,也更健壮。(回想一下一些需要半打左右参数的噩梦般的函数!)因此,应将柯里化视为简化函数调用的一种尝试。java.util.function 包中的接口类型适合于柯里化,如以下示例所示。

引用的 IntBinaryOperator 接口类型是为函数接受两个整型参数,并返回一个整型值:

IntBinaryOperator mult2 = (n1, n2) -> n1 * n2;
mult2.applyAsInt(10, 20); // 200
mult2.applyAsInt(10, 30); // 300

引用 mult2 强调了需要两个显式参数,在本例中是 10 和 20。

前面介绍的 IntUnaryOperatorIntBinaryOperator 简单,因为前者只需要一个参数,而后者则需要两个参数。两者均返回整数值。因此,目标是将名为 mult2 的两个参数 IntBinraryOperator 柯里化成一个单一的 IntUnaryOperator 版本 curriedMult2

考虑 IntFunction<R> 类型。此类型的函数采用整型参数,并返回类型为 R 的结果,该结果可以是另一个函数 —— 更准确地说,是 IntBinaryOperator。让一个 lambda 返回另一个 lambda 很简单:

arg1 -> (arg2 -> arg1 * arg2) // 括号可以省略

完整的 lambda 以 arg1 开头,而该 lambda 的主体以及返回的值是另一个以 arg2 开头的 lambda。返回的 lambda 仅接受一个参数(arg2),但返回了两个数字的乘积(arg1arg2)。下面的概述,再加上代码,应该可以更好地进行说明。

以下是如何柯里化 mult2 的概述:

  • 类型为 IntFunction<IntUnaryOperator> 的 lambda 被写入并调用,其整型值为 10。返回的 IntUnaryOperator 缓存了值 10,因此变成了已柯里化版本的 mult2,在本例中为 curriedMult2
  • 然后使用单个显式参数(例如,20)调用 curriedMult2 函数,该参数与缓存的参数(在本例中为 10)相乘以生成返回的乘积。。

这是代码的详细信息:

// 创建一个接受一个参数 n1 并返回一个单参数 n2 -> n1 * n2 的函数,该函数返回一个(n1 * n2 乘积的)整型数。
IntFunction<IntUnaryOperator> curriedMult2Maker = n1 -> (n2 -> n1 * n2);

调用 curriedMult2Maker 生成所需的 IntUnaryOperator 函数:

// 使用 curriedMult2Maker 获取已柯里化版本的 mult2。
// 参数 10 是上面的 lambda 的 n1。
IntUnaryOperator curriedMult2 = curriedMult2Maker2.apply(10);

10 现在缓存在 curriedMult2 函数中,以便 curriedMult2 调用中的显式整型参数乘以 10:

curriedMult2.applyAsInt(20); // 200 = 10 * 20
curriedMult2.applyAsInt(80); // 800 = 10 * 80

缓存的值可以随意更改:

curriedMult2 = curriedMult2Maker.apply(50); // 缓存 50
curriedMult2.applyAsInt(101);               // 5050 = 101 * 50

当然,可以通过这种方式创建多个已柯里化版本的 mult2,每个版本都有一个 IntUnaryOperator

柯里化充分利用了 lambda 的强大功能:可以很容易地编写 lambda 表达式来返回需要的任何类型的值,包括另一个 lambda。

总结

Java 仍然是基于类的面向对象的编程语言。但是,借助流 API 及其支持的函数式构造,Java 向函数式语言(例如 Lisp)迈出了决定性的(同时也是受欢迎的)一步。结果是 Java 更适合处理现代编程中常见的海量数据流。在函数式方向上的这一步还使以在前面的代码示例中突出显示的管道的方式编写清晰简洁的 Java 代码更加容易:

dataStream
   .parallelStream() // 多线程以提高效率
   .filter(...)      // 阶段 1
   .map(...)         // 阶段 2
   .filter(...)      // 阶段 3
   ...
   .collect(...);    // 或者,也可以进行归约:阶段 N

自动多线程,以 parallelparallelStream 调用为例,建立在 Java 的 fork/join 框架上,该框架支持 任务窃取 task stealing 以提高效率。假设 parallelStream 调用后面的线程池由八个线程组成,并且 dataStream 被八种方式分区。某个线程(例如,T1)可能比另一个线程(例如,T7)工作更快,这意味着应该将 T7 的某些任务移到 T1 的工作队列中。这会在运行时自动发生。

在这个简单的多线程世界中,程序员的主要职责是编写线程安全函数,这些函数作为参数传递给在流 API 中占主导地位的高阶函数。尤其是 lambda 鼓励编写纯函数(因此是线程安全的)函数。


via: https://opensource.com/article/20/1/javastream

作者:Marty Kalin 选题:lujun9972 译者:laingke 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

BitlBee 将多个聊天应用集合到一个界面中。在我们的 20 个使用开源提升生产力的系列的第九篇文章中了解如何设置和使用 BitlBee。

去年,我在 19 天里给你介绍了 19 个新(对你而言)的生产力工具。今年,我换了一种方式:使用你在使用或者还没使用的工具,构建一个使你可以在新一年更加高效的环境。

将所有聊天都放到 BitlBee 中

即时消息和聊天已经成为网络世界的主要内容。如果你像我一样,你可能打开五六个不同的应用与你的朋友、同事和其他人交谈。关注所有聊天真的很痛苦。谢天谢地,你可以使用一个应用(好吧,是两个)将这些聊天整个到一个地方。

 title=

BitlBee 是作为服务运行的应用,它可以将标准的 IRC 客户端与大量的消息服务进行桥接。而且,由于它本质上是 IRC 服务器,因此你可以选择很多客户端。

BitlBee 几乎包含在所有 Linux 发行版中。在 Ubuntu 上安装(我选择的 Linux 桌面),类似这样:

sudo apt install bitlbee-libpurple

在其他发行版上,包名可能略有不同,但搜索 “bitlbee” 应该就能看到。

你会注意到我用的 libpurple 版的 BitlBee。这个版本能让我使用 libpurple 即时消息库中提供的所有协议,该库最初是为 Pidgin 开发的。

安装完成后,服务应会自动启动。现在,使用一个 IRC 客户端(图片中为 XChat),我可以连接到端口 6667(标准 IRC 端口)上的服务。

 title=

你将自动连接到控制频道 &bitlbee。此频道对于你是独一无二的,在多用户系统上每个人都有一个自己的。在这里你可以配置该服务。

在控制频道中输入 help,你可以随时获得完整的文档。浏览它,然后使用 register 命令在服务器上注册帐户。

register <mypassword>

现在,你在服务器上所做的任何配置更改(IM 帐户、设置等)都将在输入 save 时保存。每当你连接时,使用 identify <mypassword> 连接到你的帐户并加载这些设置。

 title=

命令 help purple 将显示 libpurple 提供的所有可用协议。例如,我安装了 telegram-purple 包,它增加了连接到 Telegram 的能力。我可以使用 account add 命令将我的电话号码作为帐户添加。

account add telegram +15555555

BitlBee 将显示它已添加帐户。你可以使用 account list 列出你的帐户。因为我只有一个帐户,我可以通过 account 0 on 登录,它会进行 Telegram 登录,列出我所有的朋友和聊天,接下来就能正常聊天了。

但是,对于 Slack 这个最常见的聊天系统之一呢?你可以安装 slack-libpurple 插件,并且对 Slack 执行同样的操作。如果你不愿意编译和安装这些,这可能不适合你。

按照插件页面上的说明操作,安装后重新启动 BitlBee 服务。现在,当你运行 help purple 时,应该会列出 Slack。像其他协议一样添加一个 Slack 帐户。

account add slack [email protected]
account 1 set password my_legcay_API_token
account 1 on

你知道么,你已经连接到 Slack 中,你可以通过 chat add 命令添加你感兴趣的 Slack 频道。比如:

chat add 1 happyparty

将 Slack 频道 happyparty 添加为本地频道 #happyparty。现在可以使用标准 IRC /join 命令访问该频道。这很酷。

BitlBee 和 IRC 客户端帮助我的(大部分)聊天和即时消息保存在一个地方,并减少了我的分心,因为我不再需要查找并切换到任何一个刚刚找我的应用上。


via: https://opensource.com/article/20/1/open-source-chat-tool

作者:Kevin Sonney 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在完成了产品的基础开发以后,接下来需要进行一些周边的工作,这些周边工具将会帮助下一步优化产品。

为什么要加应用统计和 Crash 收集

不少开发者在开发的时候,很少会意识到需要添加应用统计和 Crash 收集。但对于一个合格的应用来说,这些是必须的。

  • 应用统计:应用统计会在后续进行产品迭代的时候给予数据的支持,让能够明确为什么要更新、要更新什么以及为什么这么做。
  • Crash 收集:人无完人,很难开发出一个完美的应用,就随时有可能会出现应用报错的情况。出现报错以后由于用户的水平不同,有效的反馈其实很少。Crash 收集则可以帮助收集必要的 Crash 信息,从而在后续开发的过程中,有针对性的修复 Bug。

应该使用哪些工具?

在应用统计领域,可选项非常多,大部分人使用的是 Google Analytics ,不过由于这个产品的面向用户主要是国内的用户,因此我更倾向选择加载速度更快的产品,最终我选择的是来自腾讯的移动应用分析(MTA),腾讯的移动应用分析中,提供了 HTML5 产品的接入,因此可以完成 TLDR 的统计。

在 Crash 收集方面,大家用的比较普遍的是 Sentry.io ,不过因为 Linux 中国并没有足够多的产品业务需要使用 Sentry 来收集 Crash ,因此,一直使用的是官网的免费使用版本。同样因为网络加载速度的原因,选择使用了国内的竞品 —— FunDebug

接入工具

腾讯移动应用分析

腾讯移动应用分析的接入并不复杂,首先,你需要登陆其官网,创建一个 HTML5 应用。

并在创建完成后,根据你自己的需要配置相应的能力,这里我开启了所有的数据统计,用以支持后续的产品迭代决策。

配置完成后,你会获得具体的统计的代码,接下来就可以进行接入。

一个比较简单的方法是直接将代码复制,并粘贴到 public/index.html 中,从而实现统计。不过,这样嵌入会导致如果需要自定义统计时,会无法通过 ESLint 的规则,因此我选择了第二种方式,使用 Vue 插件的方式接入。

使用 Vue 插件接入时,需要使用 mars-mta 这个包。

先使用 npm 安装依赖,然后在 main.js 中加入相应的统计代码,就可以实现自动的统计。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify';

// 以下为新增代码
import Mars from 'mars-mta'
Vue.use(Mars, {
    open: true, // 开关,若为false,则不会发出上报
    config: {
      sid: '500710182', // 必填,统计用的appid
      cid: '500710183', // 如果开启自定义事件,此项目为必填,否则不填
      autoReport: 1, // 是否开启自动上报(1:init完成则上报一次,0:使用pgv方法才上报)
      senseHash: 1, // hash锚点是否进入url统计
      senseQuery: 1, // url参数是否进入url统计
      performanceMonitor: 1, // 是否开启性能监控
    }
})
// 以上为新增代码

new Vue({
  router,
  vuetify,
  render: h => h(App),
  beforeCreate: async function(){
        const auth = this.$tcb.auth({ persistence: 'local' });
        await auth.signInAnonymously();
  }
}).$mount('#app')

在我的代码中,配置了 sidcid ,这些信息你都需要在腾讯 MTA 的应用管理后台获取。

而下方的配置,则根据你自己的实际需求选择开启即可。

对应的提交:https://github.com/LCTT/tldr.linux.cn/commit/61821aff4bf75fda3e81d96c6cd34a51efd00773

FunDebug

Fundebug 是我之前在开发小程序的时候用过的 Crash 收集应用。这次刚好也用上了。

因为预算的问题,这里我使用的是免费版本,有几个 tab 是看不到的,但是基本的 Debug 也是够用了。

Fundebug 的安装也很简单, 访问 https://www.fundebug.com/ ,注册账号, 并创建一个项目,你会获得一个 API Key,后续你可以使用这个 API Key 来初始化你的项目。

执行如下命令来安装依赖:

npm install fundebug-javascript fundebug-vue --save

并在 main.js 中添加如下代码(将 API-KEY 替换为你自己的 API KEY)并保存,就可以引入 Fundebug 来进行 Crash 收集了。

import * as fundebug from "fundebug-javascript";
import fundebugVue from "fundebug-vue";
fundebug.init({
    apikey: "API-KEY"
})
fundebugVue(fundebug, Vue);

对应的提交:https://github.com/LCTT/tldr.linux.cn/commit/225ca9d38e80eb55defac6383f5b9c228bdab6fe

优化

在开发的过程中经常会出现 Error,这个是难以避免的。在开启了 Crash 收集以后,这些 ERROR 也会被收集到 Fundebug 上,这样会浪费每个月 3000 条的免费额度,因此,需要一个方法在开发环境不启用这些拓展。类似的,也不希望 MTA 统计本地开发的 Page View ,会影响到后续的数据分析。因此,我使用了一些方法来避开这个问题。

if (process.env.NODE_ENV === 'production') {
    Vue.use(Mars, {
      open: true, // 开关,若为false,则不会发出上报
      config: {
        sid: 'xxx', // 必填,统计用的appid
      }
  })
  fundebug.apikey = "xxx"
  fundebugVue(fundebug, Vue);
}

通过将引用统计的代码包裹在环境的判断代码中,可以实现在渲染的时候,只有生产环境才会渲染出相应的统计代码,从而实现了在开发环境不调用 fundebug 和 mta 统计,避免了开发环境的数据干扰。

对应的提交:https://github.com/LCTT/tldr.linux.cn/commit/62f87b51fabd7c25cd905560157a546fd62babf2

总结

在这篇文章中,介绍了两个服务,分别是用于统计的腾讯移动分析 MTA 和用于做 Crash 收集的 fundebug,介绍了应该如何在 Vue 应用中接入这两种服务。此外,还根据实际的需求,优化了两个统计的位置,确保产品在开发环境不会工作,从而避免了影响到我们统计数据的准确性。

这篇文章涉及到的代码你都可以在 https://github.com/LCTT/tldr.linux.cn/blob/master/src/main.js 看到。

当我们还在体验 Linux 5.5 稳定发行版带来更好的硬件支持时,Linux 5.6 已经来了。

说实话,Linux 5.6 比 5.5 更令人兴奋。即使即将发布的 Ubuntu 20.04 LTS 发行版将自带 Linux 5.5,你也需要切实了解一下 Linux 5.6 内核为我们提供了什么。

我将在本文中重点介绍 Linux 5.6 发布版中值得期待的关键更改和功能:

Linux 5.6 功能亮点

当 Linux 5.6 有新消息时,我会努力更新这份功能列表。但现在让我们先看一下当前已知的内容:

1、支持 WireGuard

WireGuard 将被添加到 Linux 5.6,出于各种原因的考虑它可能将取代 OpenVPN

你可以在官网上进一步了解 WireGuard 的优点。当然,如果你使用过它,那你可能已经知道它比 OpenVPN 更好的原因。

同样,Ubuntu 20.04 LTS 将支持 WireGuard

2、支持 USB4

Linux 5.6 也将支持 USB4

如果你不了解 USB 4.0 (USB4),你可以阅读这份文档

根据文档,“USB4 将使 USB 的最大带宽增大一倍并支持 多并发数据和显示协议 multiple simultaneous data and display protocols 。”

另外,虽然我们都知道 USB4 基于 Thunderbolt 接口协议,但它将向后兼容 USB 2.0、USB 3.0 以及 Thunderbolt 3,这将是一个好消息。

3、使用 LZO/LZ4 压缩 F2FS 数据

Linux 5.6 也将支持使用 LZO/LZ4 算法压缩 F2FS 数据。

换句话说,这只是 Linux 文件系统的一种新压缩技术,你可以选择待定的文件扩展技术。

4、解决 32 位系统的 2038 年问题

Unix 和 Linux 将时间值以 32 位有符号整数格式存储,其最大值为 2147483647。时间值如果超过这个数值则将由于整数溢出而存储为负数。

这意味着对于 32 位系统,时间值不能超过 1970 年 1 月 1 日后的 2147483647 秒。也就是说,在 UTC 时间 2038 年 1 月 19 日 03:14:07 时,由于整数溢出,时间将显示为 1901 年 12 月 13 日而不是 2038 年 1 月 19 日。

Linux kernel 5.6 解决了这个问题,因此 32 位系统也可以运行到 2038 年以后。

5、改进硬件支持

很显然,在下一个发布版中,硬件支持也将继续提升。而支持新式无线外设的计划也同样是优先的。

新内核中将增加对 MX Master 3 鼠标以及罗技其他无线产品的支持。

除了罗技的产品外,你还可以期待获得许多不同硬件的支持(包括对 AMD GPU、NVIDIA GPU 和 Intel Tiger Lake 芯片组的支持)。

6、其他更新

此外,Linux 5.6 中除了上述主要的新增功能或支持外,下一个内核版本也将进行其他一些改进:

  • 改进 AMD Zen 的温度/功率报告
  • 修复华硕飞行堡垒系列笔记本中 AMD CPU 过热
  • 开源支持 NVIDIA RTX 2000 图灵系列显卡
  • 内建 FSCRYPT 加密

Phoronix 跟踪了 Linux 5.6 带来的许多技术性更改。因此,如果你好奇 Linux 5.6 所涉及的全部更改,则可以亲自了解一下。

现在你已经了解了 Linux 5.6 发布版带来的新功能,对此有什么看法呢?在下方评论中留下你的看法。


via: https://itsfoss.com/linux-kernel-5-6/

作者:Ankush Das 选题:lujun9972 译者:LazyWolfLin 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出