【问题标题】:Shorten an already short string in Java在Java中缩短一个已经很短的字符串
【发布时间】:2011-11-15 09:33:13
【问题描述】:

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

字符串是主机名:端口组合,可能类似于“my-domain.se:2121”或“123.211.80.4:2122”。

我知道对于这么短的字符串,由于所需的开销和缺乏重复,定期压缩几乎是不可能的,但我知道如何做到这一点。

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

  1. 使用某种自定义编码将字符串编码为字节数组
  2. 将字节数组解码为 UTF-8 或 ASCII 字符串(该字符串显然没有任何意义)。

然后逆过程得到原字符串。

所以我的问题:

  1. 这可行吗?
  2. 有没有更好的办法?
  3. 怎么样?

【问题讨论】:

  • 您遗漏了第四个问题:为什么?
  • 您将您的应用程序限制为仅支持 Latin-1 字符?您希望存储多少这些值?对我来说,这听起来像是为了节省少量空间而付出的巨大努力。磁盘很便宜,开发/维护时间非常昂贵。
  • 这与磁盘空间无关。我需要尽可能短,因为可以在键盘、电话或口语上手动输入生成的字符串。
  • @Gustav,看,这就是为什么你应该总是提到原因。如果要复制 tinyurl,请构建自己的(短)字符串并将它们映射到数据库中的 url 地址。您可以从一个字母字符串开始,然后逐步向上,对于一个中低流量的网站,您不太可能超过三个字符。
  • @Blindy 我认为实际上不需要它。但你是对的。消息将在两个应用程序之间传输,因此它们无权访问任何类型的共享地图。

标签: java string encoding compression


【解决方案1】:

您可以将字符串编码为基数 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.

我将解码作为一项练习。 ;)

【讨论】:

  • 这正是我想要的。解码部分真的很挣扎,但我最终应该让它工作。
  • 将每对字节读取为 char ch。第一个索引是ch % 40,第二个是ch / 40 % 40,最后一个是ch / 40 / 40。使用chars 将索引转换为字符。笔记;我已经交换了代码中的顺序以使其更易于解码。 0 表示您已到达终点。
  • 终于搞定了。它绝对漂亮:) 再次感谢您!
【解决方案2】:

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

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

【讨论】:

  • 如您所见,它不仅限于 IP 地址。我需要支持我提到的所有角色的东西。如果我按照我的想法去做,我不确定从哪里开始 =/
【解决方案3】:

在你的情况下,我会为你的用例使用专门的算法。认识到你可以存储字符串以外的东西。因此,对于 IPv4 地址:端口,您将有一个捕获 6 个字节的类——4 个用于地址,2 个用于端口。另一种用于字母数字主机名的类型。端口将始终存储在两个字节中。例如,主机名部分本身也可以专门支持.com。所以一个示例层次结构可能是:

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


public interface HostPort extends CharSequence { }


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

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

【讨论】:

  • 不错的主意...唯一的问题是我需要将使用的编码方法与字符串一起发送。但这只需要一个额外的字节,并且由于 IP:port 将更加常见,字符串通常会短很多......
  • “发送使用的编码方式”是什么意思?你的意思是你需要通过一些基于文本的协议发送压缩表单?因为否则,您可以以二进制形式传输它;您将为每个子类型使用一个字节,并且每个子类型都知道如何以压缩形式进行序列化。
  • 是的,压缩后的表单将作为文本(甚至可能是语音)发送。但我的意思是“消息”将需要一个额外的字符来指示它是否包含 IPv4 地址或主机名。
【解决方案4】:

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

【讨论】:

  • 这可能是我最终要做的。也许我会跳过主机名压缩。但如果我也能做到这一点,那就太好了。
  • 我正在使用这个并使用Peter Lawrey's 压缩主机名(如果使用的话)进行字符串编码。
【解决方案5】:

您可以使用CDC Display code 对它们进行编码。这种编码在过去比特稀缺且程序员很紧张时使用。

【讨论】:

  • 看起来 CDC 实际上包含了我需要的所有字符!我会寻找这个的 Java 实现。
  • 没有成功。改用 Peter Lawrey 的 solution,因为它可以节省更多空间。
【解决方案6】:

您的建议类似于 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);

它会将您的字符串缩短几个字符。这显然行不通,因为你最终得到的不是你开始的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2019-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多