HashIds.java

标签: hashids java | 发表时间:2019-05-22 09:35 | 作者:w727ang
出处:https://www.iteye.com
package org.hashids;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Hashids designed for Generating short hashes from numbers (like YouTube and Bitly), obfuscate
 * database IDs, use them as forgotten password hashes, invitation codes, store shard numbers.
 * <p>
 * This is implementation of http://hashids.org v1.0.0 version.
 *
 * This implementation is immutable, thread-safe, no lock is necessary.
 *
 * @author <a href="mailto:[email protected]">fanweixiao</a>
 * @author <a href="mailto:[email protected]">Tercio Gaudencio Filho</a>
 * @since 0.3.3
 */
public class Hashids {
  /**
   * Max number that can be encoded with Hashids.
   */
  public static final long MAX_NUMBER = 9007199254740992L;

  private static final String DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
  private static final String DEFAULT_SEPS = "cfhistuCFHISTU";
  private static final String DEFAULT_SALT = "";

  private static final int DEFAULT_MIN_HASH_LENGTH = 0;
  private static final int MIN_ALPHABET_LENGTH = 16;
  private static final double SEP_DIV = 3.5;
  private static final int GUARD_DIV = 12;

  private final String salt;
  private final int minHashLength;
  private final String alphabet;
  private final String seps;
  private final String guards;

  public Hashids() {
    this(DEFAULT_SALT);
  }

  public Hashids(String salt) {
    this(salt, 0);
  }

  public Hashids(String salt, int minHashLength) {
    this(salt, minHashLength, DEFAULT_ALPHABET);
  }

  public Hashids(String salt, int minHashLength, String alphabet) {
    this.salt = salt != null ? salt : DEFAULT_SALT;
    this.minHashLength = minHashLength > 0 ? minHashLength : DEFAULT_MIN_HASH_LENGTH;

    final StringBuilder uniqueAlphabet = new StringBuilder();
    for (int i = 0; i < alphabet.length(); i++) {
      if (uniqueAlphabet.indexOf(String.valueOf(alphabet.charAt(i))) == -1) {
        uniqueAlphabet.append(alphabet.charAt(i));
      }
    }

    alphabet = uniqueAlphabet.toString();

    if (alphabet.length() < MIN_ALPHABET_LENGTH) {
      throw new IllegalArgumentException(
          "alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters");
    }

    if (alphabet.contains(" ")) {
      throw new IllegalArgumentException("alphabet cannot contains spaces");
    }

    // seps should contain only characters present in alphabet;
    // alphabet should not contains seps
    String seps = DEFAULT_SEPS;
    for (int i = 0; i < seps.length(); i++) {
      final int j = alphabet.indexOf(seps.charAt(i));
      if (j == -1) {
        seps = seps.substring(0, i) + " " + seps.substring(i + 1);
      } else {
        alphabet = alphabet.substring(0, j) + " " + alphabet.substring(j + 1);
      }
    }

    alphabet = alphabet.replaceAll("\\s+", "");
    seps = seps.replaceAll("\\s+", "");
    seps = Hashids.consistentShuffle(seps, this.salt);

    if ((seps.isEmpty()) || (((float) alphabet.length() / seps.length()) > SEP_DIV)) {
      int seps_len = (int) Math.ceil(alphabet.length() / SEP_DIV);

      if (seps_len == 1) {
        seps_len++;
      }

      if (seps_len > seps.length()) {
        final int diff = seps_len - seps.length();
        seps += alphabet.substring(0, diff);
        alphabet = alphabet.substring(diff);
      } else {
        seps = seps.substring(0, seps_len);
      }
    }

    alphabet = Hashids.consistentShuffle(alphabet, this.salt);
    // use double to round up
    final int guardCount = (int) Math.ceil((double) alphabet.length() / GUARD_DIV);

    String guards;
    if (alphabet.length() < 3) {
      guards = seps.substring(0, guardCount);
      seps = seps.substring(guardCount);
    } else {
      guards = alphabet.substring(0, guardCount);
      alphabet = alphabet.substring(guardCount);
    }
    this.guards = guards;
    this.alphabet = alphabet;
    this.seps = seps;
  }

  /**
   * Encode numbers to string
   *
   * @param numbers
   *          the numbers to encode
   * @return the encoded string
   */
  public String encode(long... numbers) {
    if (numbers.length == 0) {
      return "";
    }

    for (final long number : numbers) {
      if (number < 0) {
        return "";
      }
      if (number > MAX_NUMBER) {
        throw new IllegalArgumentException("number can not be greater than " + MAX_NUMBER + "L");
      }
    }
    return this._encode(numbers);
  }

  /**
   * Decode string to numbers
   *
   * @param hash
   *          the encoded string
   * @return decoded numbers
   */
  public long[] decode(String hash) {
    if (hash.isEmpty()) {
      return new long[0];
    }
    
    String validChars = this.alphabet + this.guards + this.seps;
    for (int i = 0; i < hash.length(); i++) {
      if(validChars.indexOf(hash.charAt(i)) == -1) {
        return new long[0];
      }
    }

    return this._decode(hash, this.alphabet);
  }

  /**
   * Encode hexa to string
   *
   * @param hexa
   *          the hexa to encode
   * @return the encoded string
   */
  public String encodeHex(String hexa) {
    if (!hexa.matches("^[0-9a-fA-F]+$")) {
      return "";
    }

    final List<Long> matched = new ArrayList<Long>();
    final Matcher matcher = Pattern.compile("[\\w\\W]{1,12}").matcher(hexa);

    while (matcher.find()) {
      matched.add(Long.parseLong("1" + matcher.group(), 16));
    }

    // conversion
    final long[] result = new long[matched.size()];
    for (int i = 0; i < matched.size(); i++) {
      result[i] = matched.get(i);
    }

    return this.encode(result);
  }

  /**
   * Decode string to numbers
   *
   * @param hash
   *          the encoded string
   * @return decoded numbers
   */
  public String decodeHex(String hash) {
    final StringBuilder result = new StringBuilder();
    final long[] numbers = this.decode(hash);

    for (final long number : numbers) {
      result.append(Long.toHexString(number).substring(1));
    }

    return result.toString();
  }

  public static int checkedCast(long value) {
    final int result = (int) value;
    if (result != value) {
      // don't use checkArgument here, to avoid boxing
      throw new IllegalArgumentException("Out of range: " + value);
    }
    return result;
  }

  /* Private methods */

  private String _encode(long... numbers) {
    long numberHashInt = 0;
    for (int i = 0; i < numbers.length; i++) {
      numberHashInt += (numbers[i] % (i + 100));
    }
    String alphabet = this.alphabet;
    final char ret = alphabet.charAt((int) (numberHashInt % alphabet.length()));

    long num;
    long sepsIndex, guardIndex;
    String buffer;
    final StringBuilder ret_strB = new StringBuilder(this.minHashLength);
    ret_strB.append(ret);
    char guard;

    for (int i = 0; i < numbers.length; i++) {
      num = numbers[i];
      buffer = ret + this.salt + alphabet;

      alphabet = Hashids.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
      final String last = Hashids.hash(num, alphabet);

      ret_strB.append(last);

      if (i + 1 < numbers.length) {
        if (last.length() > 0) {
          num %= (last.charAt(0) + i);
          sepsIndex = (int) (num % this.seps.length());
        } else {
          sepsIndex = 0;
        }
        ret_strB.append(this.seps.charAt((int) sepsIndex));
      }
    }

    String ret_str = ret_strB.toString();
    if (ret_str.length() < this.minHashLength) {
      guardIndex = (numberHashInt + (ret_str.charAt(0))) % this.guards.length();
      guard = this.guards.charAt((int) guardIndex);

      ret_str = guard + ret_str;

      if (ret_str.length() < this.minHashLength) {
        guardIndex = (numberHashInt + (ret_str.charAt(2))) % this.guards.length();
        guard = this.guards.charAt((int) guardIndex);

        ret_str += guard;
      }
    }

    final int halfLen = alphabet.length() / 2;
    while (ret_str.length() < this.minHashLength) {
      alphabet = Hashids.consistentShuffle(alphabet, alphabet);
      ret_str = alphabet.substring(halfLen) + ret_str + alphabet.substring(0, halfLen);
      final int excess = ret_str.length() - this.minHashLength;
      if (excess > 0) {
        final int start_pos = excess / 2;
        ret_str = ret_str.substring(start_pos, start_pos + this.minHashLength);
      }
    }

    return ret_str;
  }

  private long[] _decode(String hash, String alphabet) {
    final ArrayList<Long> ret = new ArrayList<Long>();

    int i = 0;
    final String regexp = "[" + this.guards + "]";
    String hashBreakdown = hash.replaceAll(regexp, " ");
    String[] hashArray = hashBreakdown.split(" ");

    if (hashArray.length == 3 || hashArray.length == 2) {
      i = 1;
    }

    if (hashArray.length > 0) {
      hashBreakdown = hashArray[i];
      if (!hashBreakdown.isEmpty()) {
        final char lottery = hashBreakdown.charAt(0);

        hashBreakdown = hashBreakdown.substring(1);
        hashBreakdown = hashBreakdown.replaceAll("[" + this.seps + "]", " ");
        hashArray = hashBreakdown.split(" ");

        String subHash, buffer;
        for (final String aHashArray : hashArray) {
          subHash = aHashArray;
          buffer = lottery + this.salt + alphabet;
          alphabet = Hashids.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
          ret.add(Hashids.unhash(subHash, alphabet));
        }
      }
    }

    // transform from List<Long> to long[]
    long[] arr = new long[ret.size()];
    for (int k = 0; k < arr.length; k++) {
      arr[k] = ret.get(k);
    }

    if (!this.encode(arr).equals(hash)) {
      arr = new long[0];
    }

    return arr;
  }

  private static String consistentShuffle(String alphabet, String salt) {
    if (salt.length() <= 0) {
      return alphabet;
    }

    int asc_val, j;
    final char[] tmpArr = alphabet.toCharArray();
    for (int i = tmpArr.length - 1, v = 0, p = 0; i > 0; i--, v++) {
      v %= salt.length();
      asc_val = salt.charAt(v);
      p += asc_val;
      j = (asc_val + v + p) % i;
      final char tmp = tmpArr[j];
      tmpArr[j] = tmpArr[i];
      tmpArr[i] = tmp;
    }

    return new String(tmpArr);
  }

  private static String hash(long input, String alphabet) {
    String hash = "";
    final int alphabetLen = alphabet.length();

    do {
      final int index = (int) (input % alphabetLen);
      if (index >= 0 && index < alphabet.length()) {
        hash = alphabet.charAt(index) + hash;
      }
      input /= alphabetLen;
    } while (input > 0);

    return hash;
  }

  private static Long unhash(String input, String alphabet) {
    long number = 0, pos;

    for (int i = 0; i < input.length(); i++) {
      pos = alphabet.indexOf(input.charAt(i));
      number = number * alphabet.length() + pos;
    }

    return number;
  }

  /**
   * Get Hashid algorithm version.
   *
   * @return Hashids algorithm version implemented.
   */
  public String getVersion() {
    return "1.0.0";
  }
}

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [hashids java] 推荐:

Java中的锁(Locks in Java)

- - 并发编程网 - ifeve.com
原文链接 作者:Jakob Jenkov 译者:申章 校对:丁一. 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂. 因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字( 译者注:这说的是Java 5之前的情况).

Java PaaS 对决

- 呆瓜 - IBM developerWorks 中国 : 文档库
本文为 Java 开发人员比较了三种主要的 Platform as a Service (PaaS) 产品:Google App Engine for Java、Amazon Elastic Beanstalk 和 CloudBees RUN@Cloud. 它分析了每种服务独特的技术方法、优点以及缺点,而且还讨论了常见的解决方法.

Java浮点数

- d0ngd0ng - 译言-电脑/网络/数码科技
Thomas Wang, 2000年3月. Java浮点数的定义大体上遵守了二进制浮点运算标准(即IEEE 754标准). IEEE 754标准提供了浮点数无穷,负无穷,负零和非数字(Not a number,简称NaN)的定义. 在Java开发方面,这些东西经常被多数程序员混淆. 在本文中,我们将讨论计算这些特殊的浮点数相关的结果.

Qt——转战Java?

- - 博客 - 伯乐在线
编者按:事实上,在跨平台开发方面,Qt仍是最好的工具之一,无可厚非,但Qt目前没有得到任何主流移动操作系统的正式支持. 诺基亚的未来计划,定位非常模糊,这也是令很多第三方开发者感到失望,因此将导致诺基亚屡遭失败的原因. Qt的主要开发者之一Mirko Boehm在博客上强烈讽刺Nokia裁了Qt部门的决定,称其为“绝望之举”,而非“策略变更”.

java 验证码

- - ITeye博客
// 创建字体,字体的大小应该根据图片的高度来定. // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到. // randomCode用于保存随机产生的验证码,以便用户登录后进行验证. // 随机产生codeCount数字的验证码. // 得到随机产生的验证码数字. // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同.

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

java面试题

- - Java - 编程语言 - ITeye博客
 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面. 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节. 抽象包括两个方面,一是过程抽象,二是数据抽象.  继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法. 对象的一个新类可以从现有的类中派生,这个过程称为类继承.

Java使用memcached

- - 互联网 - ITeye博客
首先到 http://danga.com/memcached下载memcached的windows版本和java客户端jar包,目前最新版本是memcached-1.2.1-win32.zip和java_memcached-release_1.6.zip,分别解压后即可. 然后是安装运行memcached服务器,我们将memcached-1.2.1-win32.zip解压后,进入其目录,然后运行如下命令:c:>;memcached.exe -d install
c:>memcached.exe -l 127.0.0.1 -m 32 -d start.

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.

java 缩略图

- - 开源软件 - ITeye博客
文章说明:根据用户上传的图片按等比例生成相应的的缩略图,两小例笔记. 第一、java-image-scaling 开源小工具生成图片缩略图. Maven地址: http://mvnrepository.com/artifact/com.mortennobel/java-image-scaling/.