2009-03-15 17 views
8

(En .NET) Tengo datos binarios arbitrarios almacenados en un byte [] (una imagen, por ejemplo). Ahora, necesito almacenar esos datos en una cadena (un campo "Comentario" de una API heredada). ¿Hay una técnica estándar para empaquetando esta información binaria en una cadena ? Al "empaquetar" quiero decir que para cualquier conjunto de datos razonablemente grande y aleatorio, bytes.Length/2 es aproximadamente lo mismo que packed.Length; porque dos bytes son más o menos un solo carácter.¿Existe una técnica estándar para empaquetar datos binarios en una cadena UTF-16?

Los dos "obvias" respuestas no cumplen todos los criterios:

string base64 = System.Convert.ToBase64String(bytes) 

no hace un uso muy eficiente de la cadena de puesto que utiliza solamente 64 caracteres de aproximadamente 60.000 disponibles (mi el almacenamiento es un System.String). Ir con

string utf16 = System.Text.Encoding.Unicode.GetString(bytes) 

hace un mejor uso de la cadena de , pero no va a trabajar para los datos que contiene caracteres Unicode no válidos (decir los pares suplentes pegaban mucho). This MSDN article muestra esta técnica exacta (pobre).

Veamos un ejemplo sencillo:

byte[] bytes = new byte[] { 0x41, 0x00, 0x31, 0x00}; 
string utf16 = System.Text.Encoding.Unicode.GetString(bytes); 
byte[] utf16_bytes = System.Text.Encoding.Unicode.GetBytes(utf16); 

En este caso bytes y utf16_bytes son los mismos, ya que el original bytes fuera una cadena UTF-16. Hacer este mismo procedimiento con la codificación base64 da 16 miembros base64_bytes matriz.

Ahora, repita el procedimiento con inválida de datos UTF-16:

byte[] bytes = new byte[] { 0x41, 0x00, 0x00, 0xD8}; 

Encontrará que utf16_bytes no coinciden con los datos originales.

He escrito un código que usa U + FFFD como escape antes de caracteres Unicode no válidos; funciona, pero me gustaría saber si existe una técnica más estándar que algo que simplemente cociné por mi cuenta. Sin mencionar, no me gusta capturar ing DecoderFallbackException como la forma de detectar caracteres no válidos.

Supongo que podría llamar esto una codificación "base BMP" o "base UTF-16" (utilizando todos los caracteres en el plano multilingüe básico Unicode). Sí, idealmente seguiría Shawn Steele's advice y pasaría por byte [].


voy a ir con la sugerencia de Peter Housel como la respuesta "correcta", porque él es el único que estuvo cerca de lo que sugiere una "técnica estándar".


Editar base16klooks aún mejor. Jim Beveridge tiene un implementation.

+0

Qué codificación Unicode exactamente? .NET usa UTF-8 de manera predeterminada, lo cual no es adecuado para este tipo de uso debido a las secuencias de escape o como se llame. –

+0

Lo que quiero decir es que escaparse de cada par de bytes en UTF-8 es mucho más derrochador que el enfoque de "6 bits por 8" de Base64. –

+0

@DrJokepu - del/2, ** presumiblemente ** UTF-16 –

Respuesta

4

Me encontré con Base16k después de leer su pregunta. No es estrictamente un estándar, pero parece funcionar bien y fue bastante fácil de implementar en C#.

+0

¡Bingo! Esto parece ser casi EXACTAMENTE lo que estaba buscando; dependiendo de tu definición de "estándar". –

1

Puede tratar los datos binarios como UTF-8b. La codificación UTF-8b supone que los bytes son secuencias UTF-8 multibyte, pero tiene una codificación de reserva para las cosas que no lo son.

+0

UTF-8b parece muy interesante. ¿Pero los sustitutos bajos no apareados no darán como resultado una cadena UTF-16 malformada? –

+0

Exactamente. Pero a la mayoría de las cosas no les importará, y pasarán el UTF-16 inválido. –

+0

Digamos que esta "API heredada" supone razonablemente que está obteniendo una cadena real, así que quién sabe qué hará con ella. Si las cadenas malformadas UTF-16 están bien, podría simplemente "convertir" el byte original [] en una cadena. Por supuesto, es probable que los sustitutos sin parear sean considerados una "infracción menor". –

0

Me entretuve con matrices de caracteres directas, y su caso de falla funciona con mi implementación. El código se ha probado bien: haz tus pruebas primero.

Puede acelerar esto usando un código no seguro. Pero estoy seguro de que UnicodeEncoding es tan lento (si no más lento).

/// <summary> 
/// Represents an encoding that packs bytes tightly into a string. 
/// </summary> 
public class ByteEncoding : Encoding 
{ 
    /// <summary> 
    /// Gets the Byte Encoding instance. 
    /// </summary> 
    public static readonly Encoding Encoding = new ByteEncoding(); 

    private ByteEncoding() 
    { 
    } 

    public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) 
    { 
     for (int i = 0; i < chars.Length; i++) 
     { 
      // Work out some indicies. 
      int j = i * 2; 
      int k = byteIndex + j; 

      // Get the bytes. 
      byte[] packedBytes = BitConverter.GetBytes((short) chars[charIndex + i]); 

      // Unpack them. 
      bytes[k] = packedBytes[0]; 
      bytes[k + 1] = packedBytes[1]; 
     } 

     return chars.Length * 2; 
    } 

    public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) 
    { 
     for (int i = 0; i < byteCount; i += 2) 
     { 
      // Work out some indicies. 
      int j = i/2; 
      int k = byteIndex + i; 

      // Make sure we don't read too many bytes. 
      byte byteB = 0; 
      if (i + 1 < byteCount) 
      { 
       byteB = bytes[k + 1]; 
      } 

      // Add it to the array. 
      chars[charIndex + j] = (char) BitConverter.ToInt16(new byte[] { bytes[k], byteB }, 0); 
     } 

     return (byteCount/2) + (byteCount % 2); // Round up. 
    } 

    public override int GetByteCount(char[] chars, int index, int count) 
    { 
     return count * 2; 
    } 

    public override int GetCharCount(byte[] bytes, int index, int count) 
    { 
     return (count/2) + (count % 2); 
    } 

    public override int GetMaxByteCount(int charCount) 
    { 
     return charCount * 2; 
    } 

    public override int GetMaxCharCount(int byteCount) 
    { 
     return (byteCount/2) + (byteCount % 2); 
    } 
} 

Aquí hay un código de prueba:

static void Main(string[] args) 
    { 
     byte[] original = new byte[256]; 

     // Note that we can't tell on the decode side how 
     // long the array was if the original length is 
     // an odd number. This will result in an 
     // inconclusive result. 
     for (int i = 0; i < original.Length; i++) 
      original[i] = (byte) Math.Abs(i - 1); 

     string packed = ByteEncoding.Encoding.GetString(original); 
     byte[] unpacked = ByteEncoding.Encoding.GetBytes(packed); 

     bool pass = true; 

     if (original.Length != unpacked.Length) 
     { 
      Console.WriteLine("Inconclusive: Lengths differ."); 
      pass = false; 
     } 

     int min = Math.Min(original.Length, unpacked.Length); 
     for (int i = 0; i < min; i++) 
     { 
      if (original[i] != unpacked[i]) 
      { 
       Console.WriteLine("Fail: Invalid at a position {0}.", i); 
       pass = false; 
      } 
     } 

     Console.WriteLine(pass ? "All Passed" : "Failure Present"); 

     Console.ReadLine(); 
    } 

funciona de la prueba, pero que van a tener que probarlo con su función API.

0

Hay otra forma de evitar esta limitación: aunque no estoy seguro de qué tan bien funcionaría.

En primer lugar, tendrá que averiguar qué tipo de cadena espera la llamada API, y cuál es la estructura de esta cadena. Si tomo un ejemplo simple, consideremos la cadena .Net:

  • Int32 _length;
  • byte [] _data;
  • byte _terminator = 0;

Añadir una sobrecarga a su llamada a la API, así:

[DllImport("legacy.dll")] 
private static extern void MyLegacyFunction(byte[] data); 

[DllImport("legacy.dll")] 
private static extern void MyLegacyFunction(string comment); 

Luego, cuando es necesario llamar a la versión de bytes que puede hacer lo siguiente:

public static void TheLegacyWisperer(byte[] data) 
    { 
     byte[] realData = new byte[data.Length + 4 /* _length */ + 1 /* _terminator */ ]; 
     byte[] lengthBytes = BitConverter.GetBytes(data.Length); 
     Array.Copy(lengthBytes, realData, 4); 
     Array.Copy(data, 0, realData, 4, data.Length); 
     // realData[end] is equal to 0 in any case. 
     MyLegacyFunction(realData); 
    } 
10

¿Puedo sugerir ¿ usa base64? Puede que no sea la forma más eficiente de hacerlo en el modo de almacenamiento, pero tiene sus ventajas:

  1. Se acabó su preocupación por el código.
  2. Tendrás menos problemas de compatibilidad con otros jugadores, si es que hay alguno.
  3. Si la cadena codificada alguna vez se considera ASCII durante la conversión, exportación, importación, copia de seguridad, restauración, lo que sea, tampoco tendrá ningún problema.
  4. En caso de que alguna vez se caiga muerto o termine bajo un autobús o algo así, cualquier programador que alguna vez tenga en sus manos el campo de comentarios sabrá instantáneamente que es base64 y no asumirá que está todo encriptado o algo así.
+0

El punto n. ° 4 es exactamente el motivo por el que estoy buscando "una técnica más estándar que algo que simplemente cociné por mi cuenta". Usar solo 6 bits con base64 parece una pérdida cuando SÉ que la cadena es UTF-16 y tiene casi 16 bits disponibles ("casi" debido a las secuencias de bits que no son caracteres válidos). –

+0

Cierto, puede sonar como un desperdicio, pero también lo es cualquier otro archivo guardado como UTF-16 porque la mayoría de nosotros no usa todo el espacio de caracteres de todos modos. Además, si esa aplicación heredada es inteligente, se guardaría como UTF-8 en una base de datos o en un archivo, en cuyo caso tendría efectos adversos si planea usar todo Unicode. –

+0

Quizás la cadena UTF-16 siempre permanezca en la memoria; por lo tanto, es (algo) importante usar el espacio de manera eficiente. La memoria sigue siendo relativamente cara en comparación con el disco, especialmente con el espacio de direcciones más limitado en la máquina de 32 bits. –

3

En primer lugar, recuerde que Unicode no significa 16 bits. El hecho de que System.String use UTF-16 internamente no está ni aquí ni allá. Los caracteres Unicode son abstractos: solo obtienen representaciones de bits mediante codificaciones.

Usted dice "mi almacenamiento es un System.String" - si ese es el caso, no puede hablar de bits y bytes, solo caracteres Unicode. System.String ciertamente tiene su propia codificación interna, pero (en teoría) podría ser diferente.

Por cierto, si usted cree que la representación interna de System.String es demasiado ineficiente en cuanto a la memoria para los datos codificados en Base64, ¿por qué no se preocupa también por las cadenas latinas/occidentales?

Si desea almacenar datos binarios en System.String, necesita una asignación entre colecciones de bits y caracteres.

Opción A: hay uno prefabricado en forma de codificación Base64. Como ha señalado, esto codifica seis bits de datos por carácter.

Opción B: si desea empacar más bits por carácter, deberá crear una matriz (o codificación) de 128, 256, 512, etc. caracteres Unicode, y el paquete 7, 8, 9, etc. bits de datos por personaje. Esos personajes deben ser caracteres Unicode reales.

Para responder a su pregunta simplemente, sí, hay un estándar, es la codificación Base64.

¿Es esto un problema real? ¿Tiene datos de rendimiento para respaldar su idea de no usar Base64?

+0

Es uso de memoria. En el ejemplo anterior, base64 usa 4 veces más memoria que UTF-16: 16 bytes frente a 4 bytes. La aplicación heredada con la que estoy trabajando ya siente presión de memoria en los sistemas de 32 bits, por lo que me gustaría encontrar una manera "fácil" y "estándar" de ser más eficiente. –

+0

Base64 utiliza 4/3 (1,333 ~) veces el tamaño de la memoria como fuente de datos binarios. Si almacena el base64 como una cadena UTF-8, el uso de memoria será mucho menor que almacenarlo como una cadena UTF-16. –

1

He aquí una versión de C# C++ de Jim Beveridge implementation:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Text.RegularExpressions; 
using System.Linq; 


// 
// Base16k.cpp : Variant of base64 used to efficiently encode binary into Unicode UTF16 strings. Based on work by 
// Markus Scherer at https://sites.google.com/site/markusicu/unicode/base16k 
// 
// This code is hereby placed in the Public Domain. 
// Jim Beveridge, November 29, 2011. 
// 
// C# port of http://qualapps.blogspot.com/2011/11/base64-for-unicode-utf16.html 
// This code is hereby placed in the Public Domain. 
// J. Daniel Smith, February 23, 2015 
// 

namespace JDanielSmith 
{ 
    public static partial class Convert 
    { 
     /// <summary> 
     /// Encode a binary array into a Base16k string for Unicode. 
     /// </summary> 
     public static string ToBase16kString(byte[] inArray) 
     { 
      int len = inArray.Length; 

      var sb = new StringBuilder(len*6/5); 
      sb.Append(len); 

      int code = 0; 

      for (int i=0; i<len; ++i) 
      { 
       byte byteValue = inArray[i]; 
       switch (i%7) 
       { 
       case 0: 
        code = byteValue<<6; 
        break; 

       case 1: 
        code |= byteValue>>2; 
        code += 0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code = (byteValue&3)<<12; 
        break; 

       case 2: 
        code |= byteValue<<4; 
        break; 

       case 3: 
        code |= byteValue>>4; 
        code+=0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code = (byteValue&0xf)<<10; 
        break; 

       case 4: 
        code |= byteValue<<2; 
        break; 

       case 5: 
        code|=byteValue>>6; 
        code+=0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code=(byteValue&0x3f)<<8; 
        break; 

       case 6: 
        code|=byteValue; 
        code+=0x5000; 
        sb.Append(System.Convert.ToChar(code)); 
        code=0; 
        break; 
       } 
      } 

      // emit a character for remaining bits 
      if (len%7 != 0) { 
       code += 0x5000; 
       sb.Append(System.Convert.ToChar(code)); 
      } 

      return sb.ToString(); 
     } 

     /// <summary> 
     /// Decode a Base16k string for Unicode into a binary array. 
     /// </summary> 
     public static byte[] FromBase16kString(string s) 
     { 
      // read the length 
      var r = new Regex(@"^\d+", RegexOptions.None, matchTimeout: TimeSpan.FromMilliseconds(100)); 
      Match m = r.Match(s); 
      if (!m.Success) 
       return null; 

      int length; 
      if (!Int32.TryParse(m.Value, out length)) 
       return null; 

      var buf = new List<byte>(length); 

      int pos=0; // position in s 
      while ((pos < s.Length) && (s[pos] >= '0' && s[pos] <= '9')) 
       ++pos; 

      // decode characters to bytes 
      int i = 0; // byte position modulo 7 (0..6 wrapping around) 
      int code=0; 
      byte byteValue=0; 

      while (length-- > 0) 
      { 
       if (((1<<i)&0x2b)!=0) 
       { 
        // fetch another Han character at i=0, 1, 3, 5 
        if(pos >= s.Length) 
        { 
         // Too few Han characters representing binary data. 
         System.Diagnostics.Debug.Assert(pos < s.Length); 
         return null; 
        } 

        code=s[pos++]-0x5000; 
       } 

       switch (i%7) 
       { 
       case 0: 
        byteValue = System.Convert.ToByte(code>>6); 
        buf.Add(byteValue); 
        byteValue = System.Convert.ToByte((code&0x3f)<<2); 
        break; 

       case 1: 
        byteValue |= System.Convert.ToByte(code>>12); 
        buf.Add(byteValue); 
        break; 

       case 2: 
        byteValue = System.Convert.ToByte((code>>4)&0xff); 
        buf.Add(byteValue); 
        byteValue = System.Convert.ToByte((code&0xf)<<4); 
        break; 

       case 3: 
        byteValue |= System.Convert.ToByte(code>>10); 
        buf.Add(byteValue); 
        break; 

       case 4: 
        byteValue = System.Convert.ToByte((code>>2)&0xff); 
        buf.Add(byteValue); 
        byteValue = System.Convert.ToByte((code&3)<<6); 
        break; 

       case 5: 
        byteValue |= System.Convert.ToByte(code>>8); 
        buf.Add(byteValue); 
        break; 

       case 6: 
        byteValue = System.Convert.ToByte(code&0xff); 
        buf.Add(byteValue); 
        break; 
       } 

       // advance to the next byte position 
       if(++i==7) 
        i=0; 
      } 

      return buf.ToArray(); 
     } 
    } 
} 

namespace Base16kCS 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var drand = new Random(); 

      // Create 500 different binary objects, then encode and decode them. 
      // The first 16 objects will have length 0,1,2 ... 16 to test boundary conditions. 
      for (int loop = 0; loop < 500; ++loop) 
      { 
       Console.WriteLine("{0}", loop); 

       int dw = drand.Next(128000); 
       var org = new List<byte>(dw); 
       for (int i = 0; i < dw; ++i) 
        org.Add(Convert.ToByte(drand.Next(256))); 

       if (loop < 16) 
        org = org.Take(loop).ToList(); 

       string wstr = JDanielSmith.Convert.ToBase16kString(org.ToArray()); 

       byte[] bin = JDanielSmith.Convert.FromBase16kString(wstr); 

       System.Diagnostics.Debug.Assert(org.SequenceEqual(bin)); 
      } 
     } 
    } 
} 
Cuestiones relacionadas