缩短Java中已经很短的字符串

时间:2011-09-12 14:11:12

标签: java string encoding compression

我正在寻找一种尽可能缩短已经很短的字符串的方法。

字符串是主机名:端口组合,可能看起来像“ my-domain.se:2121 ”或“ 123.211.80.4:2122 ”。

我知道由于需要的开销和缺乏重复,常规压缩对于字符串的问题几乎是不可能的,但我知道如何做到这一点。

因为字母表限制为39个字符( [a-z] [0-9] - :。),所以每个字符都可以容纳6位。与ASCII相比,这可以减少高达25%的长度。所以我的建议是这样的:

  1. 使用某种自定义编码将字符串编码为字节数组
  2. 将字节数组解码为UTF-8或ASCII字符串(这个字符串显然没有任何意义)。
  3. 然后反转该过程以获取原始字符串。

    所以我的问题:

    1. 这可行吗?
    2. 有更好的方法吗?
    3. 如何?

6 个答案:

答案 0 :(得分:3)

您可以将字符串编码为基数40,它比基数64更紧凑。这将为您提供12个这样的标记为64位长。第40个标记可以是字符串标记的结尾,为您提供长度(因为它不再是整个字节数)

如果您使用算术编码,它可能会小得多,但您需要每个令牌的频率表。 (使用一长串可能的例子)

class Encoder {
  public static final int BASE = 40;
  StringBuilder chars = new StringBuilder(BASE);
  byte[] index = new byte[256];

  {
    chars.append('\0');
    for (char ch = 'a'; ch <= 'z'; ch++) chars.append(ch);
    for (char ch = '0'; ch <= '9'; ch++) chars.append(ch);
    chars.append("-:.");
    Arrays.fill(index, (byte) -1);
    for (byte i = 0; i < chars.length(); i++)
      index[chars.charAt(i)] = i;
  }

  public byte[] encode(String address) {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      DataOutputStream dos = new DataOutputStream(baos);
      for (int i = 0; i < address.length(); i += 3) {
        switch (Math.min(3, address.length() - i)) {
          case 1: // last one.
            byte b = index[address.charAt(i)];
            dos.writeByte(b);
            break;

          case 2:
            char ch = (char) ((index[address.charAt(i+1)]) * 40 + index[address.charAt(i)]);
            dos.writeChar(ch);
            break;

          case 3:
            char ch2 = (char) ((index[address.charAt(i+2)] * 40 + index[address.charAt(i + 1)]) * 40 + index[address.charAt(i)]);
            dos.writeChar(ch2);
            break;
        }
      }
      return baos.toByteArray();
    } catch (IOException e) {
      throw new AssertionError(e);
    }
  }

  public static void main(String[] args) {
    Encoder encoder = new Encoder();
    for (String s : "twitter.com:2122,123.211.80.4:2122,my-domain.se:2121,www.stackoverflow.com:80".split(",")) {
      System.out.println(s + " (" + s.length() + " chars) encoded is " + encoder.encode(s).length + " bytes.");
    }
  }
}

打印

twitter.com:2122 (16 chars) encoded is 11 bytes.
123.211.80.4:2122 (17 chars) encoded is 12 bytes.
my-domain.se:2121 (17 chars) encoded is 12 bytes.
www.stackoverflow.com:80 (24 chars) encoded is 16 bytes.

我将解码作为练习。 ;)

答案 1 :(得分:2)

首先,IP地址设计为4个字节,端口号设置为2. ascii表示仅供人类阅读,因此对其进行压缩没有意义。

您压缩域名字符串的想法是可行的。

答案 2 :(得分:1)

在你的情况下,我会使用专门的算法作为你的用例。认识到您可以存储除字符串之外的其他内容。因此,对于IPv4地址:端口,您将拥有一个捕获6个字节的类 - 4个用于地址,2个用于端口。另一种用于apha数字主机名的类型。端口总是以两个字节存储。例如,主机名部分本身也可以对.com提供专门支持。因此,样本层次结构可能是:

    HostPort
       |
  +----+--------+
  |             |
IPv4        HostnamePort
                |
           DotComHostnamePort


public interface HostPort extends CharSequence { }


public HostPorts {
  public static HostPort parse(String hostPort) {
    ...
  }
}

在这种情况下,DotComHostnamePort允许您从主机名中删除.com并保存4个字符/字节,具体取决于您是以punyform还是以UTF16格式存储主机名。

答案 3 :(得分:1)

前两个字节可以包含端口号。如果始终使用此固定长度的端口号,则不需要包含分隔符:。而是使用一个位来指示IP地址是否跟随(请参阅Karl Bielefeldt's解决方案)或主机名。

答案 4 :(得分:1)

您可以使用CDC Display code对其进行编码。这种编码在过去的时候被用掉了,当时有些东西很少,程序员很紧张。

答案 5 :(得分:0)

你所建议的类似于base 64编码/解码,并且在查看其中一些实现时可能会有一些里程(base 64编码使用6位)。

如果您使用Apaches base 64库

作为入门者
String x = new String(Base64.decodeBase64("my-domain.se:2121".getBytes()));
String y = new String(Base64.encodeBase64(x.getBytes()));
System.out.println("x = " + x);
System.out.println("y = " + y);

它将通过几个字符缩短你的字符串。这显然不起作用,因为你最终得到的不是你开始的。