概述

使用 Base64 编码来对 UUID(Universally Unique Identifiers) 存储在一些特定的场合被广泛的使用。使用 Base64 对比直接使用 UUID 进行存储来说能够更多的节约空间。

 

Java 中将 UUID 存储为 Base64 字符串_数组

本文对这方面的相关内容和问题进行探讨。

在这里,使用 Base64 来对 UUID 进行存储,涉及到一些类型的转换的。Base64 是编码算法,在实际使用的时候我们更多会用到 Byte 数组的方式来进行编码的。这样我们就比较明确在对其进行 Base64 转换之前,我们应该要先干什么了。

使用 byte[] 和 Base64.Encoder

Base64.Encoder 就能够提供 byte[] 的 Base64 编码了,我们先使用这个最简单的方式来进行处理。

编码

首先我们需要给出的 UUID 位中创建出我们需要的 byte 数组。

我们先获得 UUID 的 most significant bits 和 least significant bits,然后放入我们 byte 数组中的 0-7 和 8-15 的位置。

程序代码如下:

 

private byte[] convertToByteArray(UUID uuid) {
        byte[] result = new byte[16];

        long mostSignificantBits = uuid.getMostSignificantBits();
        fillByteArray(0, 8, result, mostSignificantBits);

        long leastSignificantBits = uuid.getLeastSignificantBits();
        fillByteArray(8, 16, result, leastSignificantBits);

        return result;
    }

上面的代码中还有一个 fillByteArray 方法,这个方法,这个方法将会把我们的 bit 存如 byte array 数组中,同时还会移动 8 位。

方法的代码如下:

 

void fillByteArray(int start, int end, byte[] result, long bits) {
    for (int i = start; i < end; i++) {
        int shift = i * 8;
        result[i] = (byte) ((int) (255L & bits >> shift));
    }
}

当我们获得 byte 数组后,我们就可以调用 JDK 的 Base64.Encoder 方法来直接进行编码了成一个 Base64 加密字符串了。

完整的测试代码如下:

 

UUID originalUUID = UUID.fromString("cc5f93f7-8cf1-4a51-83c6-e740313a0c6c");

@Test
void givenEncodedString_whenDecodingUsingBase64Decoder_thenGiveExpectedUUID() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw==";
    byte[] uuidBytes = convertToByteArray(originalUUID);
    String encodedUUID = Base64.getEncoder().encodeToString(uuidBytes);
    assertEquals(expectedEncodedString, encodedUUID);
}

解码

把我们获得的 UUID Base64 字符串进行解码,我们可以使用完全相反的方法:

 

@Test
public void givenEncodedString_whenDecodingUsingBase64Decoder_thenGiveExpectedUUID() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw==";
    byte[] decodedBytes = Base64.getDecoder().decode(expectedEncodedString);
    UUID uuid = convertToUUID(decodedBytes);
}

首先把 Base64 字符串解码成 Byte 数组,然后调用我们的转换方法,把我们获得 byte 数组转换成为 UUID 对象。

 

UUID convertToUUID(byte[] src) {
    long mostSignificantBits = convertBytesToLong(src, 0);
    long leastSignificantBits = convertBytesToLong(src, 8);

    return new UUID(mostSignificantBits, leastSignificantBits);
}

在上面的方法中,我们分别对 UUID 中需要使用的 most significant bits 和 less significant bits 分别进行转换,然后再组合在一起。

转换的方法如下:

 

long convertBytesToLong(byte[] uuidBytes, int start) {
    long result = 0;

    for(int i = 0; i < 8; i++) {
        int shift = i * 8;
        long bits = (255L & (long)uuidBytes[i + start]) << shift;
        long mask = 255L << shift;
        result = result & ~mask | bits;
    }

    return result;
}

通过上面的测试代码,可以看到代码的转换都顺利完成了。

使用 ByteBuffer 和 Base64.getUrlEncoder()

如果我们还使用 JDK 的 API 的话,我们还可以把上面的代码进行一些简化。

编码

通过使用 ByteBuffer,我们可以使用非常简单的下面 2 行代码把 UUID 的 bit 转换为 buffer wrapping 数组。

 

ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
byteBuffer.putLong(originalUUID.getMostSignificantBits());
byteBuffer.putLong(originalUUID.getLeastSignificantBits());

当程序执行完上面的 2 行代码后,我们将会获得 一个 ByteBuffer 对象,这个对象中存储的是 UUID 转换过来的数据。

针对编码,我们就可以使用 Base64.getUrlEncoder() 方法,这个方法的参数我们可以使用 ByteBuffer 转换成 array 就可以了,因为 ByteBuffer 转换成 Array 是返回 Array 的。

 

String encodedUUID = Base64.getUrlEncoder().encodeToString(byteBuffer.array());

针对上面代码的修改,我们只需要下面简单的几行代码就可以完成 UUID 到 Base64 的转换。

 

@Test
public void givenUUID_whenEncodingUsingByteBufferAndBase64UrlEncoder_thenGiveExpectedEncodedString() {
    String expectedEncodedString = "zF-T94zxSlGDxudAMToMbA==";
    ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
    byteBuffer.putLong(originalUUID.getMostSignificantBits());
    byteBuffer.putLong(originalUUID.getLeastSignificantBits());
    String encodedUUID = Base64.getUrlEncoder().encodeToString(byteBuffer.array());
    assertEquals(expectedEncodedString, encodedUUID);
}

解码

解码就使用 Base64.UrlDecoder() 即可,Base64.UrlDecoder() 的解码结果为 byte 数组,所以我们还需要用 ByteBuffer.wrap 把解码后的数组包装成 ByteBuffer 对象。

 

@Test
void givenEncodedString_whenDecodingUsingByteBufferAndBase64UrlDecoder_thenGiveExpectedUUID() {
    String expectedEncodedString = "zF-T94zxSlGDxudAMToMbA==";
    byte[] decodedBytes = Base64.getUrlDecoder().decode(expectedEncodedString);
    ByteBuffer byteBuffer = ByteBuffer.wrap(decodedBytes);
    long mostSignificantBits = byteBuffer.getLong();
    long leastSignificantBits = byteBuffer.getLong();
    UUID uuid = new UUID(mostSignificantBits, leastSignificantBits);
    assertEquals(originalUUID, uuid);
}

缩短编码后字符串

在我们完成上面的编码后,我们会看到字符串的最后还有 2 个等号 “==” 。为了进一步节约我们的存储空间,我们可以把这 2 个等号从字符串中删除。

我们可以配置编码函数,告诉编码函数不要在字符串的末尾添加 2 个等号。

 

String encodedUUID = 
  Base64.getUrlEncoder().withoutPadding().encodeToString(byteBuffer.array());

assertEquals(expectedEncodedString, encodedUUID);

针对解码的方法来说,我们并不需要对方法进行任何改变,因为现在的解码方法已经能够正确识别 Base64 字符串的末尾是不是有 2 个等号,并且可都兼容。

  1. 使用 Apache Commons Conversion Utils 和Codec Utils 工具类
    在这部分,我们使用 Apache Commons Conversion Utils 的工具类来先把 UUID 对象转换为 UUID byte 数组,然后使用 Apache Commons Codec Utils 工具类来把进行 Base64 的字符串处理。

依赖

为了完成上面 2 个步骤,我们需要分别使用 Apache Commons Lang library 和 commons-codec 类库

在我们的 pom.xml 项目文件中,分别添加上面 2 个类库的依赖。

通常你的项目基本上都会包含进来的,如果没有的话再添加。

 

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.16.0</version>
</dependency>

编码

上面的编码就非常简单了,直接使用 Conversion.uuidToByteArray 方法,把要编码的 UUID 对象传进来,然后直接 Base64,使用 URL 安全的方法。

 

@Test
void givenUUID_whenEncodingUsingApacheUtils_thenGiveExpectedEncodedString() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw";
    byte[] bytes = Conversion.uuidToByteArray(originalUUID, new byte[16], 0, 16);
    String encodedUUID = encodeBase64URLSafeString(bytes);
    assertEquals(expectedEncodedString, encodedUUID);
}

通过上面的代码,我们可以看到结果是 Base 64 已经把最后的 2 个等号删掉了。

解码

针对解码来说,我们使用的是 JDK 的 Base64.decodeBase64() 方法,然后调用 Conversion.byteArrayToUuid() 把解码后的 byte 数组转换为 UUID 对象。

 

@Test
void givenEncodedString_whenDecodingUsingApacheUtils_thenGiveExpectedUUID() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw";
    byte[] decodedBytes = decodeBase64(expectedEncodedString);
    UUID uuid = Conversion.byteArrayToUuid(decodedBytes, 0);
    assertEquals(originalUUID, uuid);
}

结论

UUID 是广泛使用的 ID 识别标识,我们通过对 Base64 的转换来让 UUID 能够以更小的数据量来进行存储。

转换的关键就在于 UUID 先要转换为 byte 数组,然后 Base64 是对 byte[] 数组进行编码的。

 

https://www.isharkfly.com/t/java-uuid-base64/15685