编码/解码字符串和一个特殊字符,以字节数组
题
我有编码3字符串(总是字母)成2个整数的2字节[]阵列的要求。 这是工作要做,以节省空间和性能方面的原因。
现在的要求已经改变了一下。字符串将是可变长度的。它要么是长度为3的(因为它是上文)或将长度为4的,将有在开头1个特殊字符。如果我们选择@它永远是@,总是在开头的特殊字符是固定的,即。因此,我们相信,如果字符串的长度为3,则其仅字母,并且如果长度为4,第一个字符永远是“@”,然后进行3个字母
因此,我可以使用
charsAsNumbers[0] = (byte) (locationChars[0] - '@');
代替
的charsAsNumbers[0] = (byte) (chars[0] - 'A');
我还可以编码3或4个字符为2字节数组,并将它们解码回到?如果是的话,如何?
解决方案
是,它的是可能以编码的信息的额外的位,同时保持先前编码为3个字符值。但是,因为你原来的编码不离开自由号干净的大片在输出集,另外一组介绍字符串的映射通过添加额外的字符不禁有点不连续的。
因此,我认为这将是很难拿出处理这些不连续性而不既笨拙和慢映射函数。我的结论是基于表的映射是唯一明智的解决方案。
我是懒得重新设计的映射代码,所以将其纳入矿井的表初始化代码;这也省去了翻译错误很多机会:)你encode()
方法就是我所说的OldEncoder.encode()
。
我已经运行一个小的测试程序来验证NewEncoder.encode()
想出了为OldEncoder.encode()
相同的价值观,并且是除了能够与领先的第四编码字符的字符串。 NewEncoder.encode()
不关心字符是什么,它的推移字符串的长度;对于decode()
,所使用的字符可使用PREFIX_CHAR
来定义。我也眼珠子检查到前缀字符串的字节数组值不复制任何那些非前缀字符串;最后,该编码的前缀字符串可以确实被转换回同一前缀字符串。
package tequilaguy;
public class NewConverter {
private static final String[] b2s = new String[0x10000];
private static final int[] s2b = new int[0x10000];
static {
createb2s();
creates2b();
}
/**
* Create the "byte to string" conversion table.
*/
private static void createb2s() {
// Fill 17576 elements of the array with b -> s equivalents.
// index is the combined byte value of the old encode fn;
// value is the String (3 chars).
for (char a='A'; a<='Z'; a++) {
for (char b='A'; b<='Z'; b++) {
for (char c='A'; c<='Z'; c++) {
String str = new String(new char[] { a, b, c});
byte[] enc = OldConverter.encode(str);
int index = ((enc[0] & 0xFF) << 8) | (enc[1] & 0xFF);
b2s[index] = str;
// int value = 676 * a + 26 * b + c - ((676 + 26 + 1) * 'A'); // 45695;
// System.out.format("%s : %02X%02X = %04x / %04x %n", str, enc[0], enc[1], index, value);
}
}
}
// Fill 17576 elements of the array with b -> @s equivalents.
// index is the next free (= not null) array index;
// value = the String (@ + 3 chars)
int freep = 0;
for (char a='A'; a<='Z'; a++) {
for (char b='A'; b<='Z'; b++) {
for (char c='A'; c<='Z'; c++) {
String str = "@" + new String(new char[] { a, b, c});
while (b2s[freep] != null) freep++;
b2s[freep] = str;
// int value = 676 * a + 26 * b + c - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
// System.out.format("%s : %02X%02X = %04x / %04x %n", str, 0, 0, freep, value);
}
}
}
}
/**
* Create the "string to byte" conversion table.
* Done by inverting the "byte to string" table.
*/
private static void creates2b() {
for (int b=0; b<0x10000; b++) {
String s = b2s[b];
if (s != null) {
int sval;
if (s.length() == 3) {
sval = 676 * s.charAt(0) + 26 * s.charAt(1) + s.charAt(2) - ((676 + 26 + 1) * 'A');
} else {
sval = 676 * s.charAt(1) + 26 * s.charAt(2) + s.charAt(3) - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
}
s2b[sval] = b;
}
}
}
public static byte[] encode(String str) {
int sval;
if (str.length() == 3) {
sval = 676 * str.charAt(0) + 26 * str.charAt(1) + str.charAt(2) - ((676 + 26 + 1) * 'A');
} else {
sval = 676 * str.charAt(1) + 26 * str.charAt(2) + str.charAt(3) - ((676 + 26 + 1) * 'A') + (26 * 26 * 26);
}
int bval = s2b[sval];
return new byte[] { (byte) (bval >> 8), (byte) (bval & 0xFF) };
}
public static String decode(byte[] b) {
int bval = ((b[0] & 0xFF) << 8) | (b[1] & 0xFF);
return b2s[bval];
}
}
我已经离开几个复杂的常量表达式中的代码,尤其是权力-的-26东西。代码看起来可怕的神秘否则。也可以让那些因为它们不失性能,因为编译器折叠起来像Kleenexes。
<强>更新强>
由于采油方法恐怖,我将是一个在路上。我希望你会发现这个答案和代码的时间好好利用它。为了支持其努力的我会扔在我的小测试程序。它不直接检查的东西,但打印出的转换将导致所有显著的方式,并允许您通过眼睛和手进行检查。我用我的代码拨弄(小的调整一旦我得到的基本理念下),直到一切都显得OK那里。你可能想测试更加机械和详尽。
package tequilaguy;
public class ConverterHarness {
// private static void runOldEncoder() {
// for (char a='A'; a<='Z'; a++) {
// for (char b='A'; b<='Z'; b++) {
// for (char c='A'; c<='Z'; c++) {
// String str = new String(new char[] { a, b, c});
// byte[] enc = OldConverter.encode(str);
// System.out.format("%s : %02X%02X%n", str, enc[0], enc[1]);
// }
// }
// }
// }
private static void testNewConverter() {
for (char a='A'; a<='Z'; a++) {
for (char b='A'; b<='Z'; b++) {
for (char c='A'; c<='Z'; c++) {
String str = new String(new char[] { a, b, c});
byte[] oldEnc = OldConverter.encode(str);
byte[] newEnc = NewConverter.encode(str);
byte[] newEnc2 = NewConverter.encode("@" + str);
System.out.format("%s : %02X%02X %02X%02X %02X%02X %s %s %n",
str, oldEnc[0], oldEnc[1], newEnc[0], newEnc[1], newEnc2[0], newEnc2[1],
NewConverter.decode(newEnc), NewConverter.decode(newEnc2));
}
}
}
}
public static void main(String[] args) {
testNewConverter();
}
}
其他提示
不直接回答,而是这里是我会怎么做编码:
public static byte[] encode(String s) {
int code = s.charAt(0) - 'A' + (32 * (s.charAt(1) - 'A' + 32 * (s.charAt(2) - 'A')));
byte[] encoded = { (byte) ((code >>> 8) & 255), (byte) (code & 255) };
return encoded;
}
第一行使用Horner的模式到每个字符的5个比特算术组装成一个整数。它将可怕失败,如果您的任何输入字符的在上述范围外[A-`]。
第二行从整数的前缘和后字节组装一个2字节数组。
解码可以以类似的方式来完成,并反转的步骤。
<强>更新强>与代码(把我的脚在我的嘴,或类似的东西):
public class TequilaGuy {
public static final char SPECIAL_CHAR = '@';
public static byte[] encode(String s) {
int special = (s.length() == 4) ? 1 : 0;
int code = s.charAt(2 + special) - 'A' + (32 * (s.charAt(1 + special) - 'A' + 32 * (s.charAt(0 + special) - 'A' + 32 * special)));
byte[] encoded = { (byte) ((code >>> 8) & 255), (byte) (code & 255) };
return encoded;
}
public static String decode(byte[] b) {
int code = 256 * ((b[0] < 0) ? (b[0] + 256) : b[0]) + ((b[1] < 0) ? (b[1] + 256) : b[1]);
int special = (code >= 0x8000) ? 1 : 0;
char[] chrs = { SPECIAL_CHAR, '\0', '\0', '\0' };
for (int ptr=3; ptr>0; ptr--) {
chrs[ptr] = (char) ('A' + (code & 31));
code >>>= 5;
}
return (special == 1) ? String.valueOf(chrs) : String.valueOf(chrs, 1, 3);
}
public static void testEncode() {
for (int spcl=0; spcl<2; spcl++) {
for (char c1='A'; c1<='Z'; c1++) {
for (char c2='A'; c2<='Z'; c2++) {
for (char c3='A'; c3<='Z'; c3++) {
String s = ((spcl == 0) ? "" : String.valueOf(SPECIAL_CHAR)) + c1 + c2 + c3;
byte[] cod = encode(s);
String dec = decode(cod);
System.out.format("%4s : %02X%02X : %s\n", s, cod[0], cod[1], dec);
}
}
}
}
}
public static void main(String[] args) {
testEncode();
}
}
在你的字母,只使用15输出的16位可用的。所以,你可以只设置MSB(最显著位)如果字符串的长度是4的,因为特殊字符是固定的。
另一种选择是使用一个转换表。只要创建的所有有效字符的字符串:
String valid = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ";
在这个字符串的字符的索引是在输出的编码。现在创建两个数组:
byte encode[] = new byte[256];
char decode[] = new char[valid.length ()];
for (int i=0; i<valid.length(); i++) {
char c = valid.charAt(i);
encode[c] = i;
decode[i] = c;
}
现在可以查找的值在阵列的每个方向并添加以任何顺序喜欢的任何字符。
如果你只是使用了java.nio.charset.CharsetEncoder
类的字符转换为字节你会发现这是一个容易得多。它甚至会比ASCII字符的其他工作。甚至String.getBytes
会少很多的代码相同的基本效果。
如果“特殊字符”是固定的,你总是知道,一个4字符串开始,这个特殊的字符,然后将焦炭本身没有提供任何有用的信息。
如果字符串的长度为3个字符,然后做你之前做了什么;如果它的4个字符,运行在字符串的子串的旧算法开始于第二字符。
我在想太简单,还是你想得辛苦吗?