生成Base58格式的UUID(Hibernate Base64格式的UUID续)

一、背景

前一篇博文(Base64压缩UUID长度替换Hibernate原有UUID生成器)已经阐述了如何用Base64格式来生成UUID,达到减小UUID的字符串长度的目的。前段时间无意中了解到了Base58,分析后发现比Base64更适合做为UUID主键的编码方式。

二、Base58简介

Base58采用的字符集合为“123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ”,从这不难看出,Base58是纯数字与字母组成而且去掉了容易引起视觉混淆的字符(0:数字零,O:大写O,I:大写i,l:小写L)。9个数字+49个字母=58个。由于没有特殊字符所以在采用鼠标双击或移动设备选择时可以自动识别全选。

Base58本身就是URLSafe。Base64的URFSafe模式虽然已经对URL支持的比较好,但UUID中还是包含“-或_”。

目前流行的比特币,采用的就是Base58Check编码,是在Base58基础上又增加了安全效验机制。

三、Base58编码器程序

由于Base58最近才兴起,Java与Apache Commons中并不包含编码器。我从网上找一个。

package org.noahx.uuid.utils;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;

/**
 * Created with IntelliJ IDEA.
 * User: noah
 * Date: 8/2/13
 * Time: 10:36 AM
 * To change this template use File | Settings | File Templates.
 */
public class Base58 {

    public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
    private static final int[] INDEXES = new int[128];

    static {
        for (int i = 0; i < INDEXES.length; i++) {
            INDEXES[i] = -1;
        }
        for (int i = 0; i < ALPHABET.length; i++) {
            INDEXES[ALPHABET[i]] = i;
        }
    }

    /**
     * Encodes the given bytes in base58. No checksum is appended.
     */
    public static String encode(byte[] input) {
        if (input.length == 0) {
            return "";
        }
        input = copyOfRange(input, 0, input.length);
        // Count leading zeroes.
        int zeroCount = 0;
        while (zeroCount < input.length && input[zeroCount] == 0) {
            ++zeroCount;
        }
        // The actual encoding.
        byte[] temp = new byte[input.length * 2];
        int j = temp.length;

        int startAt = zeroCount;
        while (startAt < input.length) {
            byte mod = divmod58(input, startAt);
            if (input[startAt] == 0) {
                ++startAt;
            }
            temp[--j] = (byte) ALPHABET[mod];
        }

        // Strip extra '1' if there are some after decoding.
        while (j < temp.length && temp[j] == ALPHABET[0]) {
            ++j;
        }
        // Add as many leading '1' as there were leading zeros.
        while (--zeroCount >= 0) {
            temp[--j] = (byte) ALPHABET[0];
        }

        byte[] output = copyOfRange(temp, j, temp.length);
        try {
            return new String(output, "US-ASCII");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);  // Cannot happen.
        }
    }

    public static byte[] decode(String input) throws IllegalArgumentException {
        if (input.length() == 0) {
            return new byte[0];
        }
        byte[] input58 = new byte[input.length()];
        // Transform the String to a base58 byte sequence
        for (int i = 0; i < input.length(); ++i) {
            char c = input.charAt(i);

            int digit58 = -1;
            if (c >= 0 && c < 128) {
                digit58 = INDEXES[c];
            }
            if (digit58 < 0) {
                throw new IllegalArgumentException("Illegal character " + c + " at " + i);
            }

            input58[i] = (byte) digit58;
        }
        // Count leading zeroes
        int zeroCount = 0;
        while (zeroCount < input58.length && input58[zeroCount] == 0) {
            ++zeroCount;
        }
        // The encoding
        byte[] temp = new byte[input.length()];
        int j = temp.length;

        int startAt = zeroCount;
        while (startAt < input58.length) {
            byte mod = divmod256(input58, startAt);
            if (input58[startAt] == 0) {
                ++startAt;
            }

            temp[--j] = mod;
        }
        // Do no add extra leading zeroes, move j to first non null byte.
        while (j < temp.length && temp[j] == 0) {
            ++j;
        }

        return copyOfRange(temp, j - zeroCount, temp.length);
    }

    public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException {
        return new BigInteger(1, decode(input));
    }

    //
    // number -> number / 58, returns number % 58
    //
    private static byte divmod58(byte[] number, int startAt) {
        int remainder = 0;
        for (int i = startAt; i < number.length; i++) {
            int digit256 = (int) number[i] & 0xFF;
            int temp = remainder * 256 + digit256;

            number[i] = (byte) (temp / 58);

            remainder = temp % 58;
        }

        return (byte) remainder;
    }

    //
    // number -> number / 256, returns number % 256
    //
    private static byte divmod256(byte[] number58, int startAt) {
        int remainder = 0;
        for (int i = startAt; i < number58.length; i++) {
            int digit58 = (int) number58[i] & 0xFF;
            int temp = remainder * 58 + digit58;

            number58[i] = (byte) (temp / 256);

            remainder = temp % 256;
        }

        return (byte) remainder;
    }

    private static byte[] copyOfRange(byte[] source, int from, int to) {
        byte[] range = new byte[to - from];
        System.arraycopy(source, from, range, 0, range.length);

        return range;
    }


}

四、UUID生成程序

这个生成UUID程序包含了Base64(URLSafe)与Base58两种编码。

package org.noahx.uuid.util;

import org.apache.commons.codec.binary.Base64;

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

public abstract class UuidUtils {

    public static String uuid() {
        UUID uuid = UUID.randomUUID();
        return uuid.toString();
    }

    public static String base64Uuid() {
        UUID uuid = UUID.randomUUID();
        return base64Uuid(uuid);
    }

    protected static String base64Uuid(UUID uuid) {

        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());

        return Base64.encodeBase64URLSafeString(bb.array());
    }

    public static String encodeBase64Uuid(String uuidString) {
        UUID uuid = UUID.fromString(uuidString);
        return base64Uuid(uuid);
    }

    public static String decodeBase64Uuid(String compressedUuid) {

        byte[] byUuid = Base64.decodeBase64(compressedUuid);

        ByteBuffer bb = ByteBuffer.wrap(byUuid);
        UUID uuid = new UUID(bb.getLong(), bb.getLong());
        return uuid.toString();
    }

    public static String base58Uuid() {
        UUID uuid = UUID.randomUUID();
        return base58Uuid(uuid);
    }

    protected static String base58Uuid(UUID uuid) {

        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());

        return Base58.encode(bb.array());
    }

    public static String encodeBase58Uuid(String uuidString) {
        UUID uuid = UUID.fromString(uuidString);
        return base58Uuid(uuid);
    }

    public static String decodeBase58Uuid(String base58uuid) {
        byte[] byUuid = Base58.decode(base58uuid);
        ByteBuffer bb = ByteBuffer.wrap(byUuid);
        UUID uuid = new UUID(bb.getLong(), bb.getLong());
        return uuid.toString();
    }
}

五、生成UUID的效果

1、Base64的效果

M0ISICCxQi6sP-KIq3kFOw
11YozyYYTvKmuUXpRDvoJA
KlZnS-MuT2m3d-the2chxg
8J3SC10AQzqZr6Im8V2xYA
ES1UiFTGTHqn6ADU5YW0aw
1usa208oT1q7FitKbQHH5Q
53aDQZxKTGyqmKCzDnBwYQ
SVVjViEoQXayWB9_JknKqQ
fP6znJIAT1uGMN9HW5o8cw
YR-2-kKmSOubhGr2LpFCgQ

可以看到有-与_字符。大家可以双击上面包含-的UUID,得到只选中部分的效果。

2、Base58的效果

MqJqC2rtZLkuHys6ed2Eai
QrS5w2t5etpRY3zTR1BAEJ
Qd6wcFFVz2ZSQb3voGGj8P
75bJdWMcEh6NhT51D5Uyju
2L7kTgsktxMBKLkfAo2iWC
UX2Twhbt1kstRziqc7iwCR
9tZNKCeR93taLHU6PVy8hN
HSn6JMibca4nG9URWokpwg
8eL4SNz2a4puEW8fD4njsG
GThFxPsdVUoZMfmKoEHwQX

没有混淆字符一眼看上去更清晰更可读。

六、总结

Base58与Base64(URLSafe)一样也只需21或22个字符就可以标示128位的UUID数据。基本一样的长度,看上去更舒服,当然以后就采用Base58来生成UUID。配合Hibernate的UUID生成器(见前一博文),感觉很好。

你可能感兴趣的:(Hibernate,base64,uuid,主键,base58)