【问题标题】:Storing UUID as base64 String将 UUID 存储为 base64 字符串
【发布时间】:2010-10-20 20:14:58
【问题描述】:

我一直在尝试使用 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;
    }

}

输出:

UUID String: cdaed56d-8712-414d-b346-01905d0026fe
Number of Bytes: 36

UUID Byte Array: -51 -82 -43 109 -121 18 65 77 -77 70 1 -112 93 0 38 -2 
Number of Bytes: 16

UUID Base64 String: za7VbYcSQU2zRgGQXQAm/g==
Number of Bytes: 24

UUID Base64 String Trimmed: za7VbYcSQU2zRgGQXQAm/g
Number of Bytes: 22

Back to UUID Byte Array: -51 -82 -43 109 -121 18 65 77 -77 70 1 -112 93 0 38 -2 0 38 
Number of Bytes: 18

Fixed UUID Byte Array: -51 -82 -43 109 -121 18 65 77 -77 70 1 -112 93 0 38 -2 
Number of Bytes: 16

UUID String: cdaed56d-8712-414d-b346-01905d0026fe
Number of Bytes: 36

Equal to Start UUID? true

【问题讨论】:

  • 查看它的一种方法是 UUID 是 128 个随机位,因此每个 base64 项 6 位是 128/6=21.3,所以你是对的,你需要 22 个 base64 位置存储相同的数据。
  • 您之前的问题似乎基本相同:stackoverflow.com/questions/772325/…
  • 我不确定您的代码在 asByteBuffer 的第二个 for 循环中是否正确,您将 i 从 7 中减去,但 i 从 8 迭代到 16,这意味着它将移动一个负数。 IIRC
  • 我认为使用 ByteBuffer 将两个 long 转换为字节数组更容易,就像这个问题一样:stackoverflow.com/questions/6881659/…

标签: java sql bytearray base64 uuid


【解决方案1】:

我也在尝试做类似的事情。我正在使用一个 Java 应用程序,它使用 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8 形式的 UUID(使用 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();
}

【讨论】:

  • 对不起,我没有注意到这条评论。是的,我正在使用 Apache commons-codec。 import org.apache.commons.codec.binary.Base64;
  • 尺寸减少 39%。不错。
  • 您可以使用自 Java 8 以来内置的。Base64.getUrlEncoder().encodeToString(bb.array())Base64.getUrlDecoder().decode(id)
  • 你可以选择不实例化 Base64 类,方法 encodeBase64URLSafeString(b[]) 和 decodeBase64(str) 是静态的,不是吗?
【解决方案2】:

您可以在此应用程序中安全地删除填充“==”。如果您要将 base-64 文本解码回字节,一些库会期望它存在,但由于您只是使用生成的字符串作为键,所以这不是问题。

我会使用 Base-64,因为它的编码字符可以是 URL 安全的,而且看起来不像乱码。但也有Base-85。它使用更多的符号并将 4 个字节编码为 5 个字符,因此您可以将文本减少到 20 个字符。

【讨论】:

  • BAse85 只保存 2 个字符。另外,在 URL 中使用 Base85 并不安全,UUID 的一个主要用途是数据库中的实体标识符,然后在 URLS 中结束。
  • @erickson 能否分享一些代码 sn-p 以转换为 Base85。我试过但无法获得任何可靠的 Base85 java 库
  • @Manish base-85 有几种变体,但每一种都需要不止一个“sn-p”的代码来实现;这种答案真的不适合这个网站。您在尝试过的库中发现了什么样的问题?我真的会推荐 base-64,因为它在核心 Java 中具有支持,并且编码值只需要多花费大约 7% 的空间。
  • @erickson 但 base64 并不能解决我将 uuid 减少到 20 个字符长度的目的。
  • @Manish 我明白了。您的要求是否禁止任何特殊字符,例如引号、百分号 (%) 或反斜杠 (`\`)?您是否必须对标识符进行编码和解码? (也就是说,您希望能够转换回传统的 UUID,还是只是缩短它们?)
【解决方案3】:

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

private static Base64 BASE64 = new Base64(true);
public static String generateKey(){
    UUID uuid = UUID.randomUUID();
    byte[] uuidArray = KeyGenerator.toByteArray(uuid);
    byte[] encodedArray = BASE64.encode(uuidArray);
    String returnValue = new String(encodedArray);
    returnValue = StringUtils.removeEnd(returnValue, "\r\n");
    return returnValue;
}
public static UUID convertKey(String key){
    UUID returnValue = null;
    if(StringUtils.isNotBlank(key)){
        // Convert base64 string to a byte array
        byte[] decodedArray = BASE64.decode(key);
        returnValue = KeyGenerator.fromByteArray(decodedArray);
    }
    return returnValue;
}
private static byte[] toByteArray(UUID uuid) {
    byte[] byteArray = new byte[(Long.SIZE / Byte.SIZE) * 2];
    ByteBuffer buffer = ByteBuffer.wrap(byteArray);
    LongBuffer longBuffer = buffer.asLongBuffer();
    longBuffer.put(new long[] { uuid.getMostSignificantBits(), uuid.getLeastSignificantBits() });
    return byteArray;
}
private static UUID fromByteArray(byte[] bytes) {
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    LongBuffer longBuffer = buffer.asLongBuffer();
    return new UUID(longBuffer.get(0), longBuffer.get(1));
}

【讨论】:

  • 你为什么说这段代码会产生 url 安全的 uuid?据我了解,url 安全 uuid 不能包含“+”和“/”。但是在您的代码中,我看不到这些符号已被替换。你能解释一下吗?
  • comons-codec Base64 类有一个 urlSafe 构造函数参数,我将其设置为 true(如果为 true,此编码器将发出 - 和 _ 而不是通常的 + 和 / 字符)。 (commons.apache.org/proper/commons-codec/apidocs/org/apache/…)
  • 非常感谢您的解释。
【解决方案4】:

我有一个应用程序,我几乎就是这样做的。 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 实现可能会完全不同,因为您更有可能拥有一个原始字节数组而不是一个非常大的整数。

【讨论】:

    【解决方案5】:

    下面是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 安全且无填充的。

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 是的,我想是的......我想尽可能多地节省空间,同时让它仍然可供人类阅读。
      • 好吧,你为什么这么认为?你要存储十亿行吗?您将节省 80 亿字节,这并不多。实际上,您将节省更少,因为您的 DBMS 可能会为编码保留额外的空间。如果您使用 VARCHAR 而不是固定大小的 CHAR,您将失去保存实际长度所需的空间。
      • ...而且“节省”仅在您使用 CHAR(32) 时。如果您使用 RAW,您实际上会节省空间。
      • 任何合理的 DBMS 都允许您以本机格式存储 UUID,这需要 16 个字节。任何合理的数据库工具都会在查询结果中将这些转换为标准格式(例如“cdaed56d-8712-414d-b346-01905d0026fe”)。人们已经这样做了很长时间。无需重新发明轮子。
      • 他可能试图在 QR 码中包含 UUID,这意味着压缩对于创建更易于扫描的 QR 码很有用。
      【解决方案7】:

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

      一些亮点:

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

      这些都是相当不错的属性。我一直在我的应用程序中将这种编码用于数据库键和用户可见标识符,并且效果很好。

      【讨论】:

      • 如果最有效的格式是 16 个二进制字节,为什么还要将它用于数据库键?
      • 为了方便。使用字符串形式的 UUID 是显而易见的:每个软件都能够处理它。将其用作二进制形式的密钥是一种优化,会产生大量的开发和维护成本。我认为这不值得。
      【解决方案8】:

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

      JAVA

      import java.util.Calendar;
      import java.util.UUID;
      import org.apache.commons.codec.binary.Base64;
      
      public class UUIDUtil{
          public static UUID combUUID(){
              private UUID srcUUID = UUID.randomUUID();
              private java.sql.Timestamp ts = new java.sql.Timestamp(Calendar.getInstance().getTime().getTime());
      
              long upper16OfLowerUUID = this.zeroLower48BitsOfLong( srcUUID.getLeastSignificantBits() );
              long lower48Time = UUIDUtil.zeroUpper16BitsOfLong( ts );
              long lowerLongForNewUUID = upper16OfLowerUUID | lower48Time;
              return new UUID( srcUUID.getMostSignificantBits(), lowerLongForNewUUID );
          }   
          public static base64URLSafeOfUUIDObject( UUID uuid ){
              byte[] bytes = ByteBuffer.allocate(16).putLong(0, uuid.getLeastSignificantBits()).putLong(8, uuid.getMostSignificantBits()).array();
              return Base64.encodeBase64URLSafeString( bytes );
          }
          public static base64URLSafeOfUUIDString( String uuidString ){
          UUID uuid = UUID.fromString( uuidString );
              return UUIDUtil.base64URLSafeOfUUIDObject( uuid );
          }
          private static long zeroLower48BitsOfLong( long longVar ){
              long upper16BitMask =  -281474976710656L;
              return longVar & upper16BitMask;
          }
          private static void zeroUpper16BitsOfLong( long longVar ){
              long lower48BitMask =  281474976710656L-1L;
              return longVar & lower48BitMask;
          }
      }
      

      【讨论】:

        【解决方案9】:

        编解码器Base64CodecBase64UrlCodec 可以有效地将UUID 编码为base-64 和base-64-url。

        // Returns a base-64 string
        // input:: 01234567-89AB-4DEF-A123-456789ABCDEF
        // output: ASNFZ4mrTe+hI0VniavN7w
        String string = Base64Codec.INSTANCE.encode(uuid);
        
        // Returns a base-64-url string
        // input:: 01234567-89AB-4DEF-A123-456789ABCDEF
        // output: ASNFZ4mrTe-hI0VniavN7w
        String string = Base64UrlCodec.INSTANCE.encode(uuid);
        

        在同一packageuuid-creator 中有其他编码的编解码器。

        【讨论】:

          【解决方案10】:

          很惊讶没有人提到来自 commons-lang3 的 uuidToByteArray(…)

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

          然后代码会是

          import org.apache.commons.lang3.Conversion;
          import java.util.*;
          
          
          public static byte[] asByteArray(UUID uuid) {
              return Conversion.uuidToByteArray(uuid, new byte[16], 0, 16);
          }
          

          【讨论】:

            猜你喜欢
            • 2011-09-10
            • 1970-01-01
            • 2021-03-13
            • 2016-08-02
            • 2013-03-05
            • 1970-01-01
            • 2023-03-07
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多