кодирование / декодирование строки и специального символа в массив байтов

StackOverflow https://stackoverflow.com/questions/1873971

Вопрос

У меня было требование кодировать 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(), и, кроме того, способен кодировать строки с начальным 4-м символом. 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.В противном случае код выглядит ужасно загадочным.Вы можете оставить их такими, какие они есть, без потери производительности, поскольку компилятор сворачивает их, как салфетки.


Обновить:

По мере приближения рождественского ужаса я какое-то время буду в пути.Я надеюсь, что вы найдете этот ответ и код вовремя, чтобы с пользой им воспользоваться.В поддержку этих усилий я приведу свою небольшую тестовую программу.Он не проверяет данные напрямую, но выводит результаты конверсий всеми значимыми способами и позволяет вам проверять их на глаз и от руки.Я повозился со своим кодом (небольшие изменения, как только у меня появилась основная идея), пока там все не стало выглядеть нормально.Возможно, вы захотите протестировать более механически и исчерпывающе.

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

В первой строке используется схема Хорнера для арифметического объединения 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 символа, запустите старый алгоритм для подстроки строки, начиная со 2-го символа.

Я думаю слишком просто или ты думаешь слишком усердно?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top