UUID를 base64 문자열로 저장


80

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 위치가 필요하다는 것입니다.
Stijn Sanders

이전 질문은 본질적으로 동일 해 보입니다. stackoverflow.com/questions/772325/…
erickson

귀하의 코드가 asByteBuffer의 두 번째 for 루프에서 올바른지 확신하지 못합니다. i를 7에서 빼지 만 8에서 16으로 반복하여 음수로 이동합니다. IIRC <<<가 둘러싸지만 여전히 정확하지 않은 것 같습니다.
Jon Tirsen

: 나는이 질문에 같은 단지 바이트 배열에 두 갈망을 변환의 ByteBuffer를 사용하기 쉬운 것 같아요 stackoverflow.com/questions/6881659/...
존 Tirsen

답변:


31

이 응용 프로그램에서 패딩 "=="을 안전하게 삭제할 수 있습니다. base-64 텍스트를 다시 바이트로 디코딩하는 경우 일부 라이브러리는 해당 텍스트가있을 것으로 예상하지만 결과 문자열을 키로 사용하기 때문에 문제가되지 않습니다.

인코딩 문자가 URL에 안전 할 수 있고 횡설수설처럼 보이지 않기 때문에 Base-64를 사용합니다. 하지만 Base-85도 있습니다. 더 많은 기호와 코드를 4 바이트를 5 자로 사용하므로 텍스트를 20 자까지 줄일 수 있습니다.


17
BAse85는 2 자만 저장합니다. 또한 Base85는 URL에서 사용하기에 안전하지 않으며 UUID의 주요 용도 중 하나는 데이터베이스의 엔티티 식별자이며 URL에서 끝납니다.
Dennis

@erickson Base85로 변환 할 코드를 공유해 주시겠습니까? 나는 시도했지만 신뢰할 수있는 모든 Base85 자바 라이브러리를 가져올 수 없습니다
마니

@Manish base-85에는 여러 가지 변형이 있지만 각각 구현하려면 "스 니펫"이상의 코드가 필요합니다. 그런 종류의 답변은이 사이트에 맞지 않습니다. 시도한 도서관에서 어떤 종류의 문제를 발견 했습니까? 핵심 Java를 지원하고 인코딩 된 값에 대해 약 7 % 더 많은 공간이 필요하므로 base-64를 정말 권장합니다.
erickson

@erickson이지만 base64는 uuid를 20 자 길이로 줄이는 내 목적을 해결하지 못합니다.
Manish

@ Manish 봐요. 요구 사항이 따옴표, 퍼센트 기호 ( %) 또는 백 슬래시 (`\`) 와 같은 특수 문자를 금지 합니까? 식별자를 인코딩하고 디코딩해야합니까? (즉, 기존 UUID로 다시 변환 할 수 있습니까, 아니면 단축 할 수 있습니까?)
erickson

62

저도 비슷한 일을하려고했습니다. 나는 (Java 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8의 표준 UUID lib로 생성되는) 형식의 UUID를 사용하는 Java 응용 프로그램으로 작업하고 있습니다. 제 경우에는이 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();
}

1
이 댓글을 보지 못해서 죄송합니다. 예, Apache commons-codec을 사용하고 있습니다. import org.apache.commons.codec.binary.Base64;
swill

크기 39 % 감소. 좋은.
Stu Thompson

6
당신은 자바 8. 이후에 내장 사용할 수 Base64.getUrlEncoder().encodeToString(bb.array())Base64.getUrlDecoder().decode(id)
Wpigott

Base64 클래스를 인스턴스화하지 않도록 선택할 수 있습니다. encodeBase64URLSafeString (b []) 및 decodeBase64 (str) 메서드는 정적입니다. 그렇지 않습니까?
Kumar Mani

9

내 코드는 다음과 같습니다. 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));
}

8

거의 정확히이 작업을 수행하는 응용 프로그램이 있습니다. 22 자로 인코딩 된 UUID. 잘 작동합니다. 그러나 내가 이렇게하는 주된 이유는 ID가 웹 앱의 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가 URI 경로 구성 요소에 나타나면 이스케이프해야하는 문자를 사용하기 때문에 base64 인코딩과 정확히 동일하지 않습니다. Java 구현은 실제로 큰 정수 대신 원시 바이트 배열을 가질 가능성이 높기 때문에 상당히 다를 수 있습니다.


3

어떤 DBMS를 사용하고 있는지 말하지 않지만 공간 절약에 관심이 있다면 RAW가 가장 좋은 방법 인 것 같습니다. 모든 쿼리에 대해 변환하는 것을 기억하면됩니다. 그렇지 않으면 성능이 크게 저하 될 위험이 있습니다.

그러나 나는 물어야한다 : 당신이 사는 곳에서 바이트가 정말로 그렇게 비싸냐?


예, 그렇게 생각합니다 ... 가능한 한 많은 공간을 절약하면서 사람이 읽을 수 있도록하고 싶습니다.
mainstringargs 2009

좋아, 왜 그렇게 생각하니? 10 억 개의 행을 저장하고 있습니까? 80 억 바이트를 절약 할 수 있습니다. 실제로 DBMS는 인코딩을 위해 추가 공간을 예약 할 수 있으므로 절약 할 수 있습니다. 그리고 고정 크기 CHAR 대신 VARCHAR을 사용하면 실제 길이를 저장하는 데 필요한 공간을 잃게됩니다.
kdgregory

... 그리고 그 "저축"은 CHAR (32)를 사용하는 경우에만 가능합니다. RAW를 사용하면 실제로 공간을 절약 할 수 있습니다.
kdgregory

8
합리적인 DBMS를 사용하면 16 바이트가 필요한 기본 형식으로 UUID를 저장할 수 있습니다. 합리적인 db 도구는 쿼리 결과에서이를 표준 형식 (예 : "cdaed56d-8712-414d-b346-01905d0026fe")으로 변환합니다. 사람들은 오랫동안 이것을 해왔습니다. 바퀴를 다시 발명 할 필요가 없습니다.
Robert Lewis

1
그는 QR 코드에 UUID를 포함 시키려고 할 수 있습니다. 이는 더 쉽게 스캔 할 수있는 QR 코드를 생성하기 위해 압축이 유용하다는 것을 의미합니다.
nym

3

다음은 java.util.Base64JDK8에 도입 된 예입니다 .

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에 안전하며 패딩이 없습니다.


3

이것은 정확히 당신이 요구 한 것은 아니지만 (Base64가 아님) 유연성이 추가 되었기 때문에 살펴볼 가치가 있습니다. UUID ( https : // github .com / tonsky / compact-uuids ).

몇 가지 하이라이트 :

  • 30 % 더 작은 문자열을 생성합니다 (26 자 대 기존 36 자).
  • 전체 UUID 범위 (128 비트) 지원
  • 인코딩 안전 (ASCII에서 읽을 수있는 문자 만 사용)
  • URL / 파일 이름 안전
  • 소문자 / 대문자 안전
  • 모호한 문자 방지 (i / I / l / L / 1 / O / o / 0)
  • 인코딩 된 26 자 문자열의 알파벳순 정렬은 기본 UUID 정렬 순서와 일치합니다.

이것은 다소 좋은 속성입니다. 저는이 인코딩을 데이터베이스 키와 사용자가 볼 수있는 식별자 모두에 내 응용 프로그램에서 사용해 왔으며 매우 잘 작동합니다.


가장 효과적인 형식이 16 진 바이트 인 경우 데이터베이스 키에 사용하는 이유는 무엇입니까?
kravemir

편의상. UUID를 문자열 형식으로 사용하는 것은 분명합니다. 모든 소프트웨어가이를 처리 할 수 ​​있습니다. 바이너리 형식의 키로 사용하는 것은 상당한 개발 및 유지 관리 비용을 발생시키는 최적화입니다. 노력할 가치가 없다고 결정했습니다.
Jan Rychter

1

아래는 UUID (Comb 스타일)에 사용하는 것입니다. 여기에는 uuid 문자열 또는 uuid 유형을 base64로 변환하는 코드가 포함되어 있습니다. 64 비트 단위로 수행하므로 등호를 다루지 않습니다.

자바

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;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.