Сохранение UUID в виде строки base64
Вопрос
Я экспериментировал с использованием 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
Решение
Вы можете безопасно удалить отступ «==" в этом приложении.Если бы вам пришлось декодировать текст в формате Base64 обратно в байты, большинство библиотек ожидали бы, что он будет там, но поскольку вы просто используете полученную строку в качестве ключа, это не проблема.
Мне нравится Base-64, потому что его ограниченный набор символов меньше похож на тарабарщину, но есть и База-85.Он использует больше символов и кодирует 4 байта как 5 символов, поэтому вы можете сократить текст до 20 символов.
Другие советы
Я тоже пытался сделать что-то подобное.Я работаю с приложением 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();
}
У меня есть приложение, где я делаю почти именно это.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, вероятно, будет совершенно другой, поскольку у вас, скорее всего, будет массив необработанных байтов, а не действительно большое целое число.
Вот мой код: он использует 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));
}
Вы не говорите, какую СУБД вы используете, но кажется, что RAW будет лучшим подходом, если вы заботитесь об экономии места.Вам просто нужно не забыть конвертировать все запросы, иначе вы рискуете сильно упасть в производительности.
Но я должен спросить:там, где вы живете, байты действительно такие дорогие?
Вот пример с 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 и не содержит дополнений.
Ниже показано, что я использую для 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;
}
}