Java 将UUID存储为base64字符串

Java 将UUID存储为base64字符串,java,sql,bytearray,base64,uuid,Java,Sql,Bytearray,Base64,Uuid,我一直在尝试使用UUID作为数据库密钥。我希望占用尽可能少的字节,同时仍然保持UUID表示的可读性 我想我已经使用base64将其压缩到了22个字节,并删除了一些尾部的“==”,这些似乎不需要存储。这种方法有什么缺陷吗 基本上,我的测试代码会进行一系列转换,将UUID转换为22字节的字符串,然后将其转换回UUID import java.io.IOException; import java.util.UUID; public class UUIDTest { public stat

我一直在尝试使用UUID作为数据库密钥。我希望占用尽可能少的字节,同时仍然保持UUID表示的可读性

我想我已经使用base64将其压缩到了22个字节,并删除了一些尾部的“==”,这些似乎不需要存储。这种方法有什么缺陷吗

基本上,我的测试代码会进行一系列转换,将UUID转换为22字节的字符串,然后将其转换回UUID

import java.io.IOException;
import java.util.UUID;

public class UUIDTest {

    public static void main(String[] args){
        UUID uuid = UUID.randomUUID();
        System.out.println("UUID String: " + uuid.toString());
        System.out.println("Number of Bytes: " + uuid.toString().getBytes().length);
        System.out.println();

        byte[] uuidArr = asByteArray(uuid);
        System.out.print("UUID Byte Array: ");
        for(byte b: uuidArr){
            System.out.print(b +" ");
        }
        System.out.println();
        System.out.println("Number of Bytes: " + uuidArr.length);
        System.out.println();


        try {
            // Convert a byte array to base64 string
            String s = new sun.misc.BASE64Encoder().encode(uuidArr);
            System.out.println("UUID Base64 String: " +s);
            System.out.println("Number of Bytes: " + s.getBytes().length);
            System.out.println();


            String trimmed = s.split("=")[0];
            System.out.println("UUID Base64 String Trimmed: " +trimmed);
            System.out.println("Number of Bytes: " + trimmed.getBytes().length);
            System.out.println();

            // Convert base64 string to a byte array
            byte[] backArr = new sun.misc.BASE64Decoder().decodeBuffer(trimmed);
            System.out.print("Back to UUID Byte Array: ");
            for(byte b: backArr){
                System.out.print(b +" ");
            }
            System.out.println();
            System.out.println("Number of Bytes: " + backArr.length);

            byte[] fixedArr = new byte[16];
            for(int i= 0; i<16; i++){
                fixedArr[i] = backArr[i];
            }
            System.out.println();
            System.out.print("Fixed UUID Byte Array: ");
            for(byte b: fixedArr){
                System.out.print(b +" ");
            }
            System.out.println();
            System.out.println("Number of Bytes: " + fixedArr.length);

            System.out.println();
            UUID newUUID = toUUID(fixedArr);
            System.out.println("UUID String: " + newUUID.toString());
            System.out.println("Number of Bytes: " + newUUID.toString().getBytes().length);
            System.out.println();

            System.out.println("Equal to Start UUID? "+newUUID.equals(uuid));
            if(!newUUID.equals(uuid)){
                System.exit(0);
            }


        } catch (IOException e) {
        }

    }


    public static byte[] asByteArray(UUID uuid) {

        long msb = uuid.getMostSignificantBits();
        long lsb = uuid.getLeastSignificantBits();
        byte[] buffer = new byte[16];

        for (int i = 0; i < 8; i++) {
            buffer[i] = (byte) (msb >>> 8 * (7 - i));
        }
        for (int i = 8; i < 16; i++) {
            buffer[i] = (byte) (lsb >>> 8 * (7 - i));
        }

        return buffer;

    }

    public static UUID toUUID(byte[] byteArray) {

        long msb = 0;
        long lsb = 0;
        for (int i = 0; i < 8; i++)
            msb = (msb << 8) | (byteArray[i] & 0xff);
        for (int i = 8; i < 16; i++)
            lsb = (lsb << 8) | (byteArray[i] & 0xff);
        UUID result = new UUID(msb, lsb);

        return result;
    }

}

您没有说您正在使用什么DBMS,但是如果您关心节省空间,RAW似乎是最好的方法。您只需要记住对所有查询进行转换,否则您将面临性能大幅下降的风险


但是我不得不问:在你居住的地方,字节真的那么贵吗?

你可以安全地删除这个应用程序中的填充“==”。如果要将base-64文本解码回字节,有些库会希望它在那里,但因为您只是将结果字符串用作键,所以这不是问题


我会使用Base-64,因为它的编码字符可以是URL安全的,而且看起来不像是胡言乱语。但也有一些问题。它使用了更多的符号和代码,将4个字节作为5个字符,因此您可以将文本减少到20个字符。

我有一个应用程序,我几乎就是这样做的。22字符编码的UUID。它很好用。然而,我这样做的主要原因是ID在web应用程序的URI中公开,对于URI中出现的某些内容,36个字符确实相当大。22个字符仍然有点长,但我们勉强凑合

下面是用于此的Ruby代码:

  # Make an array of 64 URL-safe characters
  CHARS64 = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ["-", "_"]
  # Return a 22 byte URL-safe string, encoded six bits at a time using 64 characters
  def to_s22
    integer = self.to_i # UUID as a raw integer
    rval = ""
    22.times do
      c = (integer & 0x3F)
      rval += CHARS64[c]
      integer = integer >> 6
    end
    return rval.reverse
  end

这与base64编码不完全相同,因为base64使用的字符在URI路径组件中出现时必须转义。Java实现可能会有很大的不同,因为您更可能拥有一个原始字节数组,而不是一个非常大的整数。

下面是我用于UUID(梳状样式)的内容。它包括用于将uuid字符串或uuid类型转换为base64的代码。我每64位执行一次,所以我不处理任何等号:

JAVA
我也在尝试做类似的事情。我正在使用一个Java应用程序,它使用的UUID格式为
6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8
(使用Java中的标准UUID库生成)。在我的例子中,我需要能够将这个UUID减少到30个字符或更少。我使用Base64,这些是我的便利函数。希望他们能对某些人有所帮助,因为解决方案对我来说还不是很明显

用法:

String uuid_str = "6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8";
String uuid_as_64 = uuidToBase64(uuid_str);
System.out.println("as base64: "+uuid_as_64);
System.out.println("as uuid: "+uuidFromBase64(uuid_as_64));
as base64: b8tRS7h4TJ2Vt43Dp85v2A
as uuid  : 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8
import org.apache.commons.codec.binary.Base64;

private static String uuidToBase64(String str) {
    Base64 base64 = new Base64();
    UUID uuid = UUID.fromString(str);
    ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
    bb.putLong(uuid.getMostSignificantBits());
    bb.putLong(uuid.getLeastSignificantBits());
    return base64.encodeBase64URLSafeString(bb.array());
}
private static String uuidFromBase64(String str) {
    Base64 base64 = new Base64(); 
    byte[] bytes = base64.decodeBase64(str);
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    UUID uuid = new UUID(bb.getLong(), bb.getLong());
    return uuid.toString();
}
输出:

String uuid_str = "6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8";
String uuid_as_64 = uuidToBase64(uuid_str);
System.out.println("as base64: "+uuid_as_64);
System.out.println("as uuid: "+uuidFromBase64(uuid_as_64));
as base64: b8tRS7h4TJ2Vt43Dp85v2A
as uuid  : 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8
import org.apache.commons.codec.binary.Base64;

private static String uuidToBase64(String str) {
    Base64 base64 = new Base64();
    UUID uuid = UUID.fromString(str);
    ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
    bb.putLong(uuid.getMostSignificantBits());
    bb.putLong(uuid.getLeastSignificantBits());
    return base64.encodeBase64URLSafeString(bb.array());
}
private static String uuidFromBase64(String str) {
    Base64 base64 = new Base64(); 
    byte[] bytes = base64.decodeBase64(str);
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    UUID uuid = new UUID(bb.getLong(), bb.getLong());
    return uuid.toString();
}
功能:

String uuid_str = "6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8";
String uuid_as_64 = uuidToBase64(uuid_str);
System.out.println("as base64: "+uuid_as_64);
System.out.println("as uuid: "+uuidFromBase64(uuid_as_64));
as base64: b8tRS7h4TJ2Vt43Dp85v2A
as uuid  : 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8
import org.apache.commons.codec.binary.Base64;

private static String uuidToBase64(String str) {
    Base64 base64 = new Base64();
    UUID uuid = UUID.fromString(str);
    ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
    bb.putLong(uuid.getMostSignificantBits());
    bb.putLong(uuid.getLeastSignificantBits());
    return base64.encodeBase64URLSafeString(bb.array());
}
private static String uuidFromBase64(String str) {
    Base64 base64 = new Base64(); 
    byte[] bytes = base64.decodeBase64(str);
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    UUID uuid = new UUID(bb.getLong(), bb.getLong());
    return uuid.toString();
}

这是我的代码,它使用org.apache.commons.codec.binary.Base64生成url安全的唯一字符串,长度为22个字符(并且具有与UUID相同的唯一性)


下面是JDK8中引入的
java.util.Base64
示例:

import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.UUID;

public class Uuid64 {

  private static final Encoder BASE64_URL_ENCODER = Base64.getUrlEncoder().withoutPadding();

  public static void main(String[] args) {
    // String uuidStr = UUID.randomUUID().toString();
    String uuidStr = "eb55c9cc-1fc1-43da-9adb-d9c66bb259ad";
    String uuid64 = uuidHexToUuid64(uuidStr);
    System.out.println(uuid64); //=> 61XJzB_BQ9qa29nGa7JZrQ
    System.out.println(uuid64.length()); //=> 22
    String uuidHex = uuid64ToUuidHex(uuid64);
    System.out.println(uuidHex); //=> eb55c9cc-1fc1-43da-9adb-d9c66bb259ad
  }

  public static String uuidHexToUuid64(String uuidStr) {
    UUID uuid = UUID.fromString(uuidStr);
    byte[] bytes = uuidToBytes(uuid);
    return BASE64_URL_ENCODER.encodeToString(bytes);
  }

  public static String uuid64ToUuidHex(String uuid64) {
    byte[] decoded = Base64.getUrlDecoder().decode(uuid64);
    UUID uuid = uuidFromBytes(decoded);
    return uuid.toString();
  }

  public static byte[] uuidToBytes(UUID uuid) {
    ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
    bb.putLong(uuid.getMostSignificantBits());
    bb.putLong(uuid.getLeastSignificantBits());
    return bb.array();
  }

  public static UUID uuidFromBytes(byte[] decoded) {
    ByteBuffer bb = ByteBuffer.wrap(decoded);
    long mostSigBits = bb.getLong();
    long leastSigBits = bb.getLong();
    return new UUID(mostSigBits, leastSigBits);
  }
}

Base64中编码的UUID是URL安全的,没有填充。

这并不是您想要的(它不是Base64),但值得一看,因为它增加了灵活性:有一个Clojure库,它实现了UUID的紧凑的26字符URL安全表示()

一些亮点:

  • 生成的字符串小30%(26个字符比传统的36个字符)
  • 支持全UUID范围(128位)
  • 编码安全(仅使用ASCII中的可读字符)
  • URL/文件名安全
  • 小写/大写安全
  • 避免歧义字符(i/i/l/l/1/O/O/0)
  • 编码的26个字符字符串的字母排序与默认UUID排序顺序匹配

这些都是相当不错的物业。在我的应用程序中,对于数据库键和用户可见标识符,我都使用了这种编码,而且效果非常好。

没有人在commons-lang3中提到uuidToByteArray(…)

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

一种方法是UUID是128个随机位,因此每个base64项6位,是128/6=21.3,因此,您需要22个base64位置来存储相同的数据,这是正确的。您之前的问题基本上是相同的:我不确定您的代码在asByteBuffer的第二个for循环中是否正确。您从7中减去I,但我从8迭代到16,这意味着它将以负数移动。IIRC我认为使用ByteBuffer将两个long转换为一个字节数组更容易,就像这个问题中所说的:是的,我认为是这样。。。我想在保持可读性的同时尽可能节省空间。好吧,你为什么这么认为?您正在存储十亿行吗?您将节省80亿字节,这并不多。实际上,您将节省更少的空间,因为您的DBMS可能会为编码保留额外的空间。如果使用VARCHAR而不是固定大小的CHAR,那么将丢失保存实际长度所需的空间。。。。只有使用字符(32)时,“节省”才有效。如果您使用RAW,实际上可以节省空间。任何合理的DBMS都允许您以本机格式存储UUID,这需要16个字节。任何合理的数据库工具都会在查询结果中将其转换为标准格式(如“cdaed56d-8712-414d-b346-01905d0026fe”)。人们这样做已经很长时间了。没有必要重新发明轮子。他可以尝试在二维码中包含UUID,这意味着压缩对于创建更容易扫描的二维码非常有用。BAse85只保存2个字符。另外,在URL中使用Base85是不安全的,UUID的一个主要用途是数据库中的实体标识符,这些标识符最终会出现在URL中。@erickson您能分享一些代码片段来转换为Base85吗。我试过了,但找不到可靠的Base85 javalibrary@Manishbase-85有几种变体,但每种变体都需要一段以上的代码才能实现;这种回答在这个网站上实在不合适。您在尝试过的图书馆中发现了哪些问题?我真的会推荐base-64,因为它有