【问题标题】:Removing trailing nulls from byte array in C#从 C# 中的字节数组中删除尾随空值
【发布时间】:2010-09-19 09:48:44
【问题描述】:

好的,我正在将 dat 文件读入字节数组。出于某种原因,生成这些文件的人在文件末尾放置了大约半兆的无用空字节。有谁知道快速修剪这些的方法吗?

最初的想法是从数组的末尾开始并向后迭代,直到找到除 null 之外的其他内容,然后将所有内容复制到该点,但我想知道是否没有更好的方法。

回答一些问题: 您确定 0 字节肯定在文件中,而不是文件读取代码中存在错误吗?是的,我很确定。

你能确定修剪所有尾随的 0 吗?是的。

文件的其余部分可以有任何 0 吗?是的,其他地方可以有0,所以,不,我不能从头开始,在第一个0停止。

【问题讨论】:

  • 尾随的空值可能来自将整个缓冲区写入文件,而不仅仅是缓冲区的已使用部分。我只是使用 MemoryStream.GetBuffer() 而不是 ToArray()。前者返回整个缓冲区,而后者返回一个仅包含缓冲区已使用部分的数组。 docs.microsoft.com/en-us/dotnet/api/…
  • @moreurgentjest 很有趣。对我有很大帮助有点晚了,但绝对是一个好点

标签: c# bytearray


【解决方案1】:

我同意乔恩的观点。关键是您必须“触摸”从最后一个字节到第一个非零字节的每个字节。像这样的:

byte[] foo;
// populate foo
int i = foo.Length - 1;
while(foo[i] == 0)
    --i;
// now foo[i] is the last non-zero byte
byte[] bar = new byte[i+1];
Array.Copy(foo, bar, i+1);

我很确定这与您能够做到的一样高效。

【讨论】:

  • 仅当您必须复制数据时:) 另一种选择是将其视为更广泛类型的数组,例如整数或长。这可能需要不安全的代码,如果数组的末尾有奇数字节(续)
  • 但在“查找”部分可能会更有效。我当然不会开始尝试,直到我证明这是瓶颈:)
  • 您可能希望在 while 中添加一个最小检查,或者如果数组只有 0 个字节,您最终会尝试读取索引 -1。
【解决方案2】:

鉴于现在回答的额外问题,听起来您从根本上做对了。特别是,您必须从最后一个 0 开始触摸文件的每个字节,以检查它是否只有 0。

现在,您是否必须复制所有内容取决于您当时对数据的处理方式。

  • 您或许可以记住索引并将其与数据或文件名一起保存。
  • 您可以将数据复制到新的字节数组中
  • 如果你想“修复”文件,你可以调用FileStream.SetLength来截断文件

“您必须读取截断点和文件末尾之间的每个字节”是关键部分。

【讨论】:

    【解决方案3】:

    @Factor 神秘主义者,

    我认为有一种最短的方法:

    var data = new byte[] { 0x01, 0x02, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 };
    var new_data = data.TakeWhile((v, index) => data.Skip(index).Any(w => w != 0x00)).ToArray();
    

    【讨论】:

    • 有趣。有没有人有任何基准来看看这与“原始”方法相比如何?不过,这不是我会使用 LINQ 的东西。
    • 刚刚针对@Coderer 的解决方案进行了测试,速度大约慢了 9 倍
    【解决方案4】:

    这个怎么样:

    [Test]
    public void Test()
    {
       var chars = new [] {'a', 'b', '\0', 'c', '\0', '\0'};
    
       File.WriteAllBytes("test.dat", Encoding.ASCII.GetBytes(chars));
    
       var content = File.ReadAllText("test.dat");
    
       Assert.AreEqual(6, content.Length); // includes the null bytes at the end
    
       content = content.Trim('\0');
    
       Assert.AreEqual(4, content.Length); // no more null bytes at the end
                                           // but still has the one in the middle
    }
    

    【讨论】:

    • 将其视为文本似乎有风险 - 而且您刚刚将文件 IO 增加了三倍。
    • 哦,CPU等也显着增加(编码/解码需要时间,即使是ASCII)
    • 编码只是为了测试......编写示例文件。不过,将文件视为文本当然可能是个问题。
    • 这甚至不是一个 byte 数组。这是一个 char 数组。您意识到您可以从中创建一个字符串,然后在不写入任何文件的情况下将空字符剪掉,对吧? Char[] trimmed = new String(chars).Trim('\0').ToCharArray(); 而且这种编码会弄乱值大于 0x80 的字符,因此大小甚至可能都不匹配。
    【解决方案5】:

    假设 0=null,这可能是您最好的选择...作为一个小调整,您可能希望在最终复制有用数据时使用 Buffer.BlockCopy..

    【讨论】:

      【解决方案6】:

      测试一下:

          private byte[] trimByte(byte[] input)
          {
              if (input.Length > 1)
              {
                  int byteCounter = input.Length - 1;
                  while (input[byteCounter] == 0x00)
                  {
                      byteCounter--;
                  }
                  byte[] rv = new byte[(byteCounter + 1)];
                  for (int byteCounter1 = 0; byteCounter1 < (byteCounter + 1); byteCounter1++)
                  {
                      rv[byteCounter1] = input[byteCounter1];
                  }
                  return rv;
              }
      

      【讨论】:

      • 嗯,有 Array.Copy() 用于此类批量复制操作,所以这可能更有效,但对于其余部分,您有正确的想法。
      【解决方案7】:

      总有一个 LINQ 答案

      byte[] data = new byte[] { 0x01, 0x02, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 };
      bool data_found = false;
      byte[] new_data = data.Reverse().SkipWhile(point =>
      {
        if (data_found) return false;
        if (point == 0x00) return true; else { data_found = true; return false; }
      }).Reverse().ToArray();
      

      【讨论】:

      • 我在单独的答案中发布了一个较短的 LINQ 替代方案。希望大家喜欢。
      • 如果这是一个大缓冲区,那么简单地向后使用索引器会更有效率。 Reverse() 是一种缓冲操作,有性能开销。
      【解决方案8】:

      您可以只计算数组末尾的零个数,然后在以后迭代数组时使用它而不是 .Length。你可以随心所欲地封装它。要点是您实际上并不需要将其复制到新结构中。如果它们很大,那可能是值得的。

      【讨论】:

        【解决方案9】:

        如果文件中的空字节可以是有效值,你知道文件中的最后一个字节不能为空吗?如果是这样,则向后迭代并查找第一个非空条目可能是最好的,如果不是,则无法判断文件的实际结尾在哪里。

        如果您对数据格式了解更多,例如不能有长于两个字节的空字节序列(或一些类似的约束)。然后,您实际上可以对“过渡点”进行二进制搜索。这应该比线性搜索快得多(假设您可以读取整个文件)。

        基本思想(使用我之前关于没有连续空字节的假设)是:

        var data = (byte array of file data...);
        var index = data.length / 2;
        var jmpsize = data.length/2;
        while(true)
        {
            jmpsize /= 2;//integer division
            if( jmpsize == 0) break;
            byte b1 = data[index];
            byte b2 = data[index + 1];
            if(b1 == 0 && b2 == 0) //too close to the end, go left
                index -=jmpsize;
            else
                index += jmpsize;
        }
        
        if(index == data.length - 1) return data.length;
        byte b1 = data[index];
        byte b2 = data[index + 1];
        if(b2 == 0)
        {
            if(b1 == 0) return index;
            else return index + 1;
        }
        else return index + 2;
        

        【讨论】:

          【解决方案10】:

          当文件很大(比我的 RAM 大得多)时,我用它来删除尾随的空值:

          static void RemoveTrailingNulls(string inputFilename, string outputFilename)
          {
              int bufferSize = 100 * 1024 * 1024;
              long totalTrailingNulls = 0;
              byte[] emptyArray = new byte[bufferSize];
          
              using (var inputFile = File.OpenRead(inputFilename))
              using (var inputFileReversed = new ReverseStream(inputFile))
              {
                  var buffer = new byte[bufferSize];
          
                  while (true)
                  {
                      var start = DateTime.Now;
          
                      var bytesRead = inputFileReversed.Read(buffer, 0, buffer.Length);
          
                      if (bytesRead == emptyArray.Length && Enumerable.SequenceEqual(emptyArray, buffer))
                      {
                          totalTrailingNulls += buffer.Length;
                      }
                      else
                      {
                          var nulls = buffer.Take(bytesRead).TakeWhile(b => b == 0).Count();
                          totalTrailingNulls += nulls;
          
                          if (nulls < bytesRead)
                          {
                              //found the last non-null byte
                              break;
                          }
                      }
          
                      var duration = DateTime.Now - start;
                      var mbPerSec = (bytesRead / (1024 * 1024D)) / duration.TotalSeconds;
                      Console.WriteLine($"{mbPerSec:N2} MB/seconds");
                  }
          
                  var lastNonNull = inputFile.Length - totalTrailingNulls;
          
                  using (var outputFile = File.Open(outputFilename, FileMode.Create, FileAccess.Write))
                  {
                      inputFile.Seek(0, SeekOrigin.Begin);
                      inputFile.CopyTo(outputFile, lastNonNull, bufferSize);
                  }
              }
          }
          

          它使用 ReverseStream 类,可以在 here 找到。

          还有这个扩展方法:

          public static class Extensions
          {
              public static long CopyTo(this Stream input, Stream output, long count, int bufferSize)
              {
                  byte[] buffer = new byte[bufferSize];
                  long totalRead = 0;
                  while (true)
                  {
                      if (count == 0) break;
          
                      int read = input.Read(buffer, 0, (int)Math.Min(bufferSize, count));
          
                      if (read == 0) break;
                      totalRead += read;
          
                      output.Write(buffer, 0, read);
                      count -= read;
                  }
          
                  return totalRead;
              }
          }
          

          【讨论】:

            【解决方案11】:

            在我的情况下,LINQ 方法从未完成 ^))) 使用字节数组很慢!

            各位,你们为什么不用 Array.Copy() 方法呢?

                /// <summary>
                /// Gets array of bytes from memory stream.
                /// </summary>
                /// <param name="stream">Memory stream.</param>
                public static byte[] GetAllBytes(this MemoryStream stream)
                {
                    byte[] result = new byte[stream.Length];
                    Array.Copy(stream.GetBuffer(), result, stream.Length);
            
                    return result;
                }
            

            【讨论】:

            • stream.GetArray() 在这种情况下会更好,因为它不会返回整个内存缓冲区,只返回已写入缓冲区的数据。
            • ...应该是 stream.ToArray()。我的错。但没有回答问题。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-08-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-26
            • 2015-02-15
            相关资源
            最近更新 更多