簡體   English   中英

編碼/解碼字符串和特殊字符到字節數組

[英]encoding/decoding string and a special character to byte array

我需要將3個字符的字符串(總是字母表)編碼為2個整數的2字節[]數組。 這樣做是為了節省空間和性能的原因。

現在要求發生了一些變化。 String的長度可變。 它的長度為3(如上所示)或長度為4,開頭時將有1個特殊字符。 特殊字符是固定的,即如果我們選擇@它將永遠是@並始終在開頭。 所以我們確定如果String的長度為3,它將只有字母表,如果長度為4,則第一個字符將始終為'@',后跟3個字母

所以我可以使用

charsAsNumbers[0] = (byte) (locationChars[0] - '@');

代替

charsAsNumbers[0] = (byte) (chars[0] - 'A');

我仍然可以將3或4個字符編碼為2字節數組並將其解碼回來嗎? 如果是這樣,怎么樣?

不是直接答案,但這是我將如何進行編碼:

   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的Schema來算術地將每個字符的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();
   }

}

在您的字母表中,您只使用輸出的16個可用位中的15個。 因此,如果字符串長度為4,則可以設置MSB(最高有效位),因為特殊字符是固定的。

另一種選擇是使用轉換表。 只需創建一個包含所有有效字符的String:

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;
}

現在,您可以查找數組中每個方向的值,並按任意順序添加您喜歡的任何字符。

是的,這可能的編碼信息的額外位,同時保持與先前編碼為3倍字符的值。 但是因為你的原始編碼沒有在輸出集中留下漂亮的大量自由數字,所以通過添加額外字符引入的附加字符串集的映射不能不會有點不連續。

因此,我認為很難提出處理這些不連續性的映射函數,而不是既笨拙又慢。 我的結論是基於表的映射是唯一理智的解決方案。

我懶得重新設計你的映射代碼,所以我把它合並到了我的表初始化代碼中; 這也消除了很多翻譯錯誤的機會:)你的encode()方法就是我所說的OldEncoder.encode()

我運行了一個小測試程序來驗證NewEncoder.encode()是否提供了與OldEncoder.encode()相同的值,並且還能夠使用前導的第4個字符對字符串進行編碼。 NewEncoder.encode()不關心字符是什么,它NewEncoder.encode()字符串長度; 對於decode() ,可以使用PREFIX_CHAR定義使用的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一樣將它們折疊起來。


更新:

隨着X-mas的恐怖逼近,我將在路上待一段時間。 我希望你能及時找到這個答案和代碼,以便充分利用它。 為了支持我將在我的小測試程序中投入的努力。 它不會直接檢查內容,而是以所有重要方式打印出轉換結果,並允許您通過眼睛和手來檢查它們。 我擺弄了我的代碼(一旦我得到了基本的想法,就進行了小調整),直到一切看起來都不錯。 您可能希望更加機械和詳盡地進行測試。

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();
   }

}

如果您只是使用java.nio.charset.CharsetEncoder類將字符轉換為字節,您會發現這更容易。 它甚至可以用於ASCII以外的字符。 即使String.getBytes對相同的基本效果也會少得多。

如果“特殊字符”被修復並且您始終知道4字符字符串以此特殊字符開頭,則字符本身不提供有用信息。

如果字符串的長度為3個字符,那么就做你以前做過的事情; 如果它是4個字符,則從第2個字符開始,在String的子字符串上運行舊算法。

我是在想太簡單還是你在想太難?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM