Зберігання UUID як рядка base64


80

Я експериментував із використанням UUID як ключів бази даних. Я хочу зайняти якомога меншу кількість байтів, зберігаючи при цьому читання уявлення UUID.

Я думаю, що я зменшив його до 22 байт, використовуючи base64 і видаливши деякий кінцевий "==", який здається непотрібним для зберігання для моїх цілей. Чи є недоліки такого підходу?

В основному мій тестовий код робить купу перетворень, щоб зменшити 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 випадкових бітів, отже, 6 біт для елемента base64 - це 128/6 = 21,3, тож ви праві, що вам потрібно 22 позиції base64 для зберігання тих самих даних.
Stijn Sanders

Ви попереднє запитання , здається , по суті , один і той же: stackoverflow.com/questions/772325 / ...
Erickson

Я не впевнений, що ваш код правильний у другому циклі for asByteBuffer ви віднімаєте i з 7, але i ітерації від 8 до 16, що означає, що він зміститься на від'ємне число. IIRC <<< обертається навколо, але все ще здається неправильним.
Джон Тірсен,

Я думаю , що легше просто використовувати ByteBuffer для перетворення двох довгих позицій в масив байтів , як в цьому питанні: stackoverflow.com/questions/6881659 / ...
Jon Tirsen

Відповіді:


31

Ви можете сміливо скинути відступ "==" у цій програмі. Якби вам було декодувати текст base-64 назад у байти, деякі бібліотеки очікували б, що він там буде, але оскільки ви просто використовуєте отриманий рядок як ключ, це не проблема.

Я б використовував Base-64, оскільки його символи кодування можуть бути безпечними за URL-адресами, і це менш схоже на тупіть. Але є також Base-85 . Він використовує більше символів та коди 4 байти як 5 символів, щоб ви могли зменшити текст до 20 символів.


17
BAse85 зберігає лише 2 символи. Крім того, Base85 не безпечно використовувати в URL-адресах, і одним з основних видів використання UUID є ідентифікатори сутності в базах даних, які потім потрапляють в URLS.
Денніс

@erickson, будь ласка, поділіться фрагментом коду для перетворення на Base85. Я спробував, але не зміг отримати надійну бібліотеку Java Base85
Manish

@Manish Існує кілька варіантів base-85, але кожен з них потребує більше, ніж “фрагмент” коду для реалізації; така відповідь насправді не вміщується на цьому сайті. Які проблеми ви виявили в бібліотеках, які ви пробували? Я справді рекомендую base-64, оскільки він підтримує основну Java і коштує лише приблизно на 7% більше місця для закодованих значень.
erickson 02

@erickson, але base64 не вирішує моєї мети зменшити uuid до 20 символів.
Manish

@Manish я бачу. Чи забороняють ваші вимоги будь-які спеціальні символи, такі як лапки, знак відсотка ( %) або коса коса риса (`\`)? Чи потрібно кодувати та декодувати ідентифікатор? (Тобто, ви хочете мати можливість перетворити назад на звичайний UUID, або просто скоротити їх?)
erickson

62

Я також намагався зробити щось подібне. Я працюю з додатком Java, який використовує UUID-і форми 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8(які генеруються зі стандартною бібліотекою 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. import org.apache.commons.codec.binary.Base64;
swill

Зменшення розміру на 39%. Приємно.
Стю Томпсон,

6
Ви можете використовувати вбудовану, починаючи з Java 8. Base64.getUrlEncoder().encodeToString(bb.array())таBase64.getUrlDecoder().decode(id)
Wpigott

Ви можете не створювати екземпляри класу Base64, методи encodeBase64URLSafeString (b []) та decodeBase64 (str) є статичними, чи не так?
Кумар Мані

9

Ось мій код, він використовує org.apache.commons.codec.binary.Base64 для створення унікальних рядків, що захищають URL-адреси, довжиною 22 символи (і які мають таку ж унікальність, як UUID).

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, закодований символом. Це чудово працює. Однак головна причина, що я роблю це таким чином, полягає в тому, що ідентифікатори відображаються в URI веб-програми, а 36 символів насправді досить великі для того, що відображається в URI. 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, швидше за все, буде зовсім іншою, оскільки ви, швидше за все, матимете масив необроблених байт, а не справді велике ціле число.


3

Ви не кажете, яку СУБД ви використовуєте, але, схоже, RAW буде найкращим підходом, якщо ви турбуєтеся про економію місця. Вам просто потрібно пам’ятати про конвертацію для всіх запитів, інакше ви ризикуєте величезним зниженням продуктивності.

Але я повинен запитати: чи справді байти такі дорогі там, де ви живете?


Так, я думаю, що так ... Я хочу заощадити якомога більше місця, зберігаючи при цьому його читабельність.
mainstringargs

Добре, чому ти так думаєш? Ви зберігаєте мільярд рядків? Ви заощадите 8 мільярдів байт, що не так вже й багато. Насправді ви заощадите менше, оскільки ваша СУБД може зарезервувати додатковий простір для кодування. І якщо ви використовуєте VARCHAR замість фіксованого розміру CHAR, ви втратите місце, необхідне для збереження фактичної довжини.
kdgregory

... і ця "економія" є лише в тому випадку, якщо ви використовуєте CHAR (32). Якщо ви використовуєте RAW, ви фактично заощадите простір.
kdgregory

8
Будь-яка розумна СУБД дозволяє зберігати UUID у власному форматі, який вимагає 16 байт. Будь-які розумні інструменти db перетворять їх у стандартний формат (наприклад, "cdaed56d-8712-414d-b346-01905d0026fe") у результатах запиту. Люди цим займаються вже давно. Не потрібно повторно винаходити колесо.
Роберт Льюїс,

1
Він міг би намагатися включити UUID в QR-код, що означало б, що стиснення корисне для того, щоб створити QR-код, який легше сканувати.
ним

3

Ось приклад із java.util.Base64представленим у JDK8:

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);
  }
}

UUID, закодований в Base64, захищений URL-адресою і без відступів.


3

Це не зовсім те, про що ви просили (це не Base64), але на це варто подивитися через додаткову гнучкість: існує бібліотека Clojure, яка реалізує компактне 26-символьне URL-безпечне представлення 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 двійкових байтів?
кравемір

Для зручності. Використання UUID в рядковій формі очевидно: кожна частина програмного забезпечення здатна мати з ним справу. Використання його як ключа в двійковій формі є оптимізацією, яка спричинить значні витрати на розробку та обслуговування. Я вирішив, що це не вартує зусиль.
Ян Рихтер,

1

Нижче наведено те, що я використовую для UUID (стиль гребінця). Він включає код для перетворення рядка 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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.