一起读源码1. Java 中元组 Tuple
Posted smile-yan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起读源码1. Java 中元组 Tuple相关的知识,希望对你有一定的参考价值。
1.1 问题描述
使用 Java 做数据分析、机器学习的时候,常常需要对批量的数据进行处理,如果需要处理的数据的维度不超过10时,可以考虑使用 org.javatuples 提供的 Tuple 类工具。支持 1 - 10 的多维数据处理,支持数据泛型。除了常见的 Integer / String / Long / Double 还可以是自定义的数据类型。
1.2 总体分析
如图所示,总共 13 个类,13个接口。
先看接口非常简单,附带自己的泛型 X,然后提供一个自己的方法 X getValue0() … X getValue9() … getValue … getKey … getLabel.
所有的类的父类为抽象类 Tuple,注意该方法并没有包含任何泛型,而是实现了常见的三个接口:
- Iterable
- Serializable
- Comparable
接下来要出场的子类就根据实际情况实现多个接口,返回对应的值。具体内容如下表所示:
类名 | 参数数目 |
---|---|
Unit | 1 |
Pair | 2 |
KeyValue | 2 |
LabelValue | 2 |
Triplet | 3 |
Quartet | 4 |
Quintet | 5 |
Sextet | 6 |
Septet | 7 |
Octet | 8 |
Ennead | 9 |
Decade | 10 |
1.3 父类 Tuple
Tuple 是一个抽象类,具有两个成员变量,如下:
private final Object[] valueArray;
private final List<Object> valueList;
私有变量外部不能直接访问,这两个变量的主要作用应该是用于 Iterable 遍历。
接着看 Tuple 对这个接口的实现:
public final Iterator<Object> iterator()
return this.valueList.iterator();
比较容易理解,把 Tuple 的 iterator 转移给它内部变量完成。
同样地看一下 contains 方法,这个同样容易理解:如果待查变量为 null 则直接返回 false,否则逐个遍历看看是不是相等。
public final boolean contains(final Object value)
for (final Object val : this.valueList)
if (val == null)
if (value == null)
return true;
else
if (val.equals(value))
return true;
return false;
接下来看一下其中的 indexOf 方法,也就是找到数据对应的索引:
public final int indexOf(final Object value)
int i = 0;
for (final Object val : this.valueList)
if (val == null)
if (value == null)
return i;
else
if (val.equals(value))
return i;
i++;
return -1;
这个我认为不合理的地方就是最后的返回 -1 ,应该定义一个常量,NOT_FOUNT = -1 更加合适。其他的没有什么可以解释的。
类似地看一下 lastIndexOf ,这个只是倒过来遍历一次。
到目前为止还不知道成员变量 valueArray 的作用,看下源码发现,主要是用于Tuple转换为 Array 和 getValue 时使用(遍历链表时间复杂度为 O ( N ) O(N) O(N) 而此时根据索引读数据时间复杂度为 O ( 1 ) O(1) O(1))。
public final Object[] toArray()
return this.valueArray.clone();
public final Object getValue(final int pos)
if (pos >= getSize())
throw new IllegalArgumentException(
"Cannot retrieve position " + pos + " in " + this.getClass().getSimpleName() +
". Positions for this class start with 0 and end with " + (getSize() - 1));
return this.valueArray[pos];
1.4 子类以 Pair 为例
构造函数只提供了一个 ,即全参数构造函数
public Pair(
final A value0,
final B value1)
super(value0, value1);
this.val0 = value0;
this.val1 = value1;
内部变量 final A value1 和 final B value2 是 final 类型的,因此如果希望更新它们的值不能直接 set 而是需要重新 new Pair,当然在 Pair 类中已经提供了。
public <X> Pair<X,B> setAt0(final X value)
return new Pair<X,B>(
value, this.val1);
public <X> Pair<A,X> setAt1(final X value)
return new Pair<A,X>(
this.val0, value);
而其他的方法比较有意思的是从 Pair 到 Triplet 等等更高维度的,都有提供这些方法。方法同样是 return new Triplet … 这类的。
另外还有经常使用的 with 方法创建Pair对象,比如 Pair.with(value1, value2);
对应的源码实现如下:
public static <A,B> Pair<A,B> with(final A value0, final B value1)
return new Pair<A,B>(value0,value1);
这些都是非常容易理解的内容,也可以借此学习一下 java 的泛型。
1.5 面向对象思维
所有这些类继承于一个父类 Tuple,该父类为抽象类,实现 Iterable 与 Comparable 接口
public abstract class Tuple implements Iterable<Object>, Serializable, Comparable<Tuple>
// 两个基本的数据结构
private final Object[] valueArray;
private final List<Object> valueList;
... 包括迭代器、遍历方法等
接下来的12个类均继承于 Tuple 类,并根据参数多少对相应的方法做了修改。
public final class Unit<A> extends Tuple implements IValue0<A>
而 该接口也比较简单:
public interface IValue0<X>
public X getValue0();
类似地,其他接口只是更改了方法的名字,以便于不同的类的使用。比如 Quartet 类有四个对象,则需要实现四个接口,分别实现 getValue0、getValue1、getValue2、getValue3 四个接口。
非常简单,但是也非常好用。
1.6 灵活应用
对于一个具有 m 个属性, 共 n 行的数据集,现在需要使用 List 和 Tuple 组合存储。
- 按行存储:存储后结果是 n 行,每一行是 一个 Tuple 对象,每个 Tuple 对象共包含 m 个属性。也就是说,先打包成行,再存储为一个 List。
- 按列存储:存储后结果是 m 列,然后把每一列对应的 List 对象存储为 Tuple。也就是说,先基于列进行打包,然后每一列对应一个 List 对象,然后使用 Tuple 进行合并。
这个应该要根据实际情况而定,一般情况下第二种情况可能更加多一些,基于属性进行打包,方便每次读取单个属性时返回一个 list。
1.7 总结
作为读源码的开端,特别选了一个简单的、也挺好用的。希望能够帮到大家~~
Smileyan
2022.7.28 19:56
以上是关于一起读源码1. Java 中元组 Tuple的主要内容,如果未能解决你的问题,请参考以下文章