1. 程式人生 >實用技巧 >其它 >【一起讀原始碼】1. Java 中元組 Tuple

【一起讀原始碼】1. Java 中元組 Tuple


1.1 問題描述

使用 Java 做資料分析、機器學習的時候,常常需要對批量的資料進行處理,如果需要處理的資料的維度不超過10時,可以考慮使用 org.javatuples 提供的 Tuple 類工具。支援 1 - 10 的多維資料處理,支援資料泛型。除了常見的 Integer / String / Long / Double 還可以是自定義的資料型別。

1.2 總體分析

如圖所示,總共 13 個類,13個介面。

【一起讀原始碼】1. Java 中元組 Tuple


先看介面非常簡單,附帶自己的泛型 X,然後提供一個自己的方法 X getValue0() … X getValue9() … getValue … getKey … getLabel.

所有的類的父類為抽象類 Tuple,注意該方法並沒有包含任何泛型,而是實現了常見的三個介面:

  • Iterable
  • Serializable
  • Comparable

接下來要出場的子類就根據實際情況實現多個介面,返回對應的值。具體內容如下表所示:

類名引數數目
Unit1
Pair2
KeyValue2
LabelValue2
Triplet3
Quartet4
Quintet5
Sextet6
Septet7
Octet8
Ennead9
Decade10

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 組合儲存。

  1. 按行儲存:儲存後結果是 n 行,每一行是 一個 Tuple 物件,每個 Tuple 物件共包含 m 個屬性。也就是說,先打包成行,再儲存為一個 List。
  2. 按列儲存:儲存後結果是 m 列,然後把每一列對應的 List 物件儲存為 Tuple。也就是說,先基於列進行打包,然後每一列對應一個 List 物件,然後使用 Tuple 進行合併。

這個應該要根據實際情況而定,一般情況下第二種情況可能更加多一些,基於屬性進行打包,方便每次讀取單個屬性時返回一個 list。

1.7 總結

作為讀原始碼的開端,特別選了一個簡單的、也挺好用的。希望能夠幫到大家~~

Smileyan
2022.7.28 19:56