【问题标题】:Find indexOf a byte array within another byte array在另一个字节数组中查找一个字节数组的 indexOf
【发布时间】:2014-02-15 22:34:06
【问题描述】:

给定一个字节数组,我如何在其中找到一个(较小的)字节数组的位置?

This documentation 看起来很有希望,使用 ArrayUtils,但如果我是正确的,它只会让我在要搜索的数组中找到一个单独的字节。

(我不认为这很重要,但以防万一:有时搜索字节数组将是常规 ASCII 字符,其他时候它将是控制字符或扩展 ASCII 字符。因此使用字符串操作并不总是合适的)

大数组可能在 10 到大约 10000 字节之间,而较小的数组大约为 10。在某些情况下,我会在一次搜索中在较大的数组中找到几个较小的数组。而且我有时会想要找到实例的最后一个索引而不是第一个。

【问题讨论】:

  • 大数组有多大,小数组又小了多少?根据这一点,可能适用不同的方法。
  • 感谢您的提问 - 我已经澄清了我的问题

标签: java search bytearray


【解决方案1】:

最简单的方法是比较每个元素:

public int indexOf(byte[] outerArray, byte[] smallerArray) {
    for(int i = 0; i < outerArray.length - smallerArray.length+1; ++i) {
        boolean found = true;
        for(int j = 0; j < smallerArray.length; ++j) {
           if (outerArray[i+j] != smallerArray[j]) {
               found = false;
               break;
           }
        }
        if (found) return i;
     }
   return -1;  
}  

一些测试:

@Test
public void testIndexOf() {
  byte[] outer = {1, 2, 3, 4};
  assertEquals(0, indexOf(outer, new byte[]{1, 2}));
  assertEquals(1, indexOf(outer, new byte[]{2, 3}));
  assertEquals(2, indexOf(outer, new byte[]{3, 4}));
  assertEquals(-1, indexOf(outer, new byte[]{4, 4}));
  assertEquals(-1, indexOf(outer, new byte[]{4, 5}));
  assertEquals(-1, indexOf(outer, new byte[]{4, 5, 6, 7, 8}));
}

当您更新您的问题时:Java 字符串是 UTF-16 字符串,它们不关心扩展的 ASCII 集,因此您可以使用 string.indexOf()

【讨论】:

  • 不应该是行 ` if (outerArray[i] != innerArray[j]) {` 是 ` if (outerArray[i + j] != innerArray[j]) {`?
  • 哦,我仍然收到一个数组越界消息 - 我认为第一个 for 循环应该是:for(int i = 0; i
【解决方案2】:

Java 字符串由 16 位 chars 组成,而不是由 8 位 bytes 组成。 char 可以容纳 byte,因此您始终可以将字节数组转换为字符串,并使用 indexOf:ASCII 字符、控制字符,甚至零字符都可以正常工作。

这是一个演示:

byte[] big = new byte[] {1,2,3,0,4,5,6,7,0,8,9,0,0,1,2,3,4};
byte[] small = new byte[] {7,0,8,9,0,0,1};
String bigStr = new String(big, StandardCharsets.UTF_8);
String smallStr = new String(small, StandardCharsets.UTF_8);
System.out.println(bigStr.indexOf(smallStr));

This prints 7.

但是,考虑到您的大数组可能高达 10,000 字节,而小数组只有 10 字节,这种解决方案可能不是最有效的,原因有两个:

  • 它需要将大数组复制到两倍大的数组中(容量相同,但使用char 而不是byte)。这会使您的内存需求增加三倍。
  • Java 的字符串搜索算法不是最快的可用算法。如果您实现其中一种高级算法,例如Knuth–Morris–Pratt,您可能会变得足够快。这可能会将执行速度降低多达 10 倍(小字符串的长度),并且需要与小字符串(而不是大字符串)的长度成正比的额外内存。

【讨论】:

  • 值得注意的是,单字节[] 构造函数使用平台默认字符集。这可能是某种 UTF-8,但你不能太确定。使用other constructor that lets you specify it IE new String(bytes, "UTF-8") 可能会更好。
  • 更好:new String(bytes, StandardCharsets.UTF_8)
  • 不要这样做;这不是编码的工作方式。这个答案有两个问题:
  • 首先:并非所有字节序列都是有效的 UTF-8 流,在这些情况下此代码将失败。例如,尝试在字符串{-16, -112, 40, -68} 中查找{-61, 40}:您的代码返回0,因为这两个都是Java 用UTF-8 的默认替换字符替换的无效序列。
  • 其次,即使对于有效的 UTF-8 流,这也不会返回正确的结果:例如,当您解码一个 ASCII 字符序列时,Java 将填充每个字节以获取字符;它不会为每个字符打包两个字节,除非它们是单个 Unicode 字符编码的一部分。这意味着当您搜索的模式在 smallStr 和 largeStr 中以不同方式拆分为多个字符时,代码将出错。 (从概念上讲,你会寻找x, y, z,在包含xy, z 的序列中打包为x, yz)。
【解决方案3】:

这是你要找的吗?

public class KPM {
    /**
     * Search the data byte array for the first occurrence of the byte array pattern within given boundaries.
     * @param data
     * @param start First index in data
     * @param stop Last index in data so that stop-start = length
     * @param pattern What is being searched. '*' can be used as wildcard for "ANY character"
     * @return
     */
    public static int indexOf( byte[] data, int start, int stop, byte[] pattern) {
        if( data == null || pattern == null) return -1;

        int[] failure = computeFailure(pattern);

        int j = 0;

        for( int i = start; i < stop; i++) {
            while (j > 0 && ( pattern[j] != '*' && pattern[j] != data[i])) {
                j = failure[j - 1];
            }
            if (pattern[j] == '*' || pattern[j] == data[i]) {
                j++;
            }
            if (j == pattern.length) {
                return i - pattern.length + 1;
            }
        }
        return -1;
    }

    /**
     * Computes the failure function using a boot-strapping process,
     * where the pattern is matched against itself.
     */
    private static int[] computeFailure(byte[] pattern) {
        int[] failure = new int[pattern.length];

        int j = 0;
        for (int i = 1; i < pattern.length; i++) {
            while (j>0 && pattern[j] != pattern[i]) {
                j = failure[j - 1];
            }
            if (pattern[j] == pattern[i]) {
                j++;
            }
            failure[i] = j;
        }

        return failure;
    }
}

【讨论】:

  • 请注意此实现包含一个奇怪的“通配符”功能。
【解决方案4】:

为了节省您的测试时间:

http://helpdesk.objects.com.au/java/search-a-byte-array-for-a-byte-sequence

如果您将 computeFailure() 设为静态,则会为您提供有效的代码:

public class KPM {
    /**
     * Search the data byte array for the first occurrence 
     * of the byte array pattern.
     */
    public static int indexOf(byte[] data, byte[] pattern) {
    int[] failure = computeFailure(pattern);

    int j = 0;

    for (int i = 0; i < data.length; i++) {
        while (j > 0 && pattern[j] != data[i]) {
            j = failure[j - 1];
        }
        if (pattern[j] == data[i]) { 
            j++; 
        }
        if (j == pattern.length) {
            return i - pattern.length + 1;
        }
    }
    return -1;
    }

    /**
     * Computes the failure function using a boot-strapping process,
     * where the pattern is matched against itself.
     */
    private static int[] computeFailure(byte[] pattern) {
    int[] failure = new int[pattern.length];

    int j = 0;
    for (int i = 1; i < pattern.length; i++) {
        while (j>0 && pattern[j] != pattern[i]) {
            j = failure[j - 1];
        }
        if (pattern[j] == pattern[i]) {
            j++;
        }
        failure[i] = j;
    }

    return failure;
    }
}

既然测试你借用的代码总是明智的,你可以从:

public class Test {
    public static void main(String[] args) {
        do_test1();
    }
    static void do_test1() {
      String[] ss = { "",
                    "\r\n\r\n",
                    "\n\n",
                    "\r\n\r\nthis is a test",
                    "this is a test\r\n\r\n",
                    "this is a test\r\n\r\nthis si a test",
                    "this is a test\r\n\r\nthis si a test\r\n\r\n",
                    "this is a test\n\r\nthis si a test",
                    "this is a test\r\nthis si a test\r\n\r\n",
                    "this is a test"
                };
      for (String s: ss) {
        System.out.println(""+KPM.indexOf(s.getBytes(), "\r\n\r\n".getBytes())+"in ["+s+"]");
      }

    }
}

【讨论】:

    【解决方案5】:

    Google 的 Guava 提供了 Bytes.indexOf(byte[] array, byte[] target)。

    【讨论】:

    • ... 实现为双 for-loop ... 不需要库,对吗?
    • 如果你在类路径中有它并且你知道它在那里你为什么不使用它?
    • ...“使用双 for 循环实现”,他们使用的是 goto。自从我的 BASIC 时代以来就没有见过这个:-o
    • goto 不只是跳转到指令吗?似乎这比外循环结束后的附加逻辑语句或额外变量更快,这也删除了几行代码。 Goto 几乎没有什么好的用途:跳出嵌套循环并在错误时跳转到清理是我见过的有效使用的方法。通常,它们会使代码更易读、更容易调试和更快。明智地使用你的工具!
    • 虽然双循环(显式或其他优化)在此搜索中是不可避免的,但我宁愿看到 Google 模仿 String.indexOf 的实现,因为它稍微优化了一些。相反,他们重新发明了算法,而不是以更好的方式。仍然最好使用这个(假设它已经在类路径上)而不是先编码为字符串。
    【解决方案6】:
    package org.example;
    
    import java.util.List;
    
    import org.riversun.finbin.BinarySearcher;
    
    public class Sample2 {
    
        public static void main(String[] args) throws Exception {
    
            BinarySearcher bs = new BinarySearcher();
    
            // UTF-8 without BOM
            byte[] srcBytes = "Hello world.It's a small world.".getBytes("utf-8");
    
            byte[] searchBytes = "world".getBytes("utf-8");
    
            List<Integer> indexList = bs.searchBytes(srcBytes, searchBytes);
    
            System.out.println("indexList=" + indexList);
        }
     }
    

    所以结果

    indexList=[6, 25]
    

    所以,你可以在 byte[] 中找到 byte[] 的索引

    Github 上的示例:https://github.com/riversun/finbin

    【讨论】:

      【解决方案7】:

      java.lang.String复制几乎完全相同。

      indexOf(char[],int,int,char[]int,int,int)

      static int indexOf(byte[] source, int sourceOffset, int sourceCount, byte[] target, int targetOffset, int targetCount, int fromIndex) {
          if (fromIndex >= sourceCount) {
              return (targetCount == 0 ? sourceCount : -1);
          }
          if (fromIndex < 0) {
              fromIndex = 0;
          }
          if (targetCount == 0) {
              return fromIndex;
          }
      
          byte first = target[targetOffset];
          int max = sourceOffset + (sourceCount - targetCount);
      
          for (int i = sourceOffset + fromIndex; i <= max; i++) {
              /* Look for first character. */
              if (source[i] != first) {
                  while (++i <= max && source[i] != first)
                      ;
              }
      
              /* Found first character, now look at the rest of v2 */
              if (i <= max) {
                  int j = i + 1;
                  int end = j + targetCount - 1;
                  for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++)
                      ;
      
                  if (j == end) {
                      /* Found whole string. */
                      return i - sourceOffset;
                  }
              }
          }
          return -1;
      }
      

      【讨论】:

      • 这个问题已经有了很好的解决方案。为什么要提供复制粘贴代码示例?
      • 我发现它对我来说更好,虽然我可以分享它以防万一寻找相同东西的人想要一个替代品
      • 这是一种算法吗?在我的情况下,我发现它比 KMP、Sunday 或蛮力两个 while 循环快一点。
      • 这是一种所谓的“蛮力”算法。在 JSON 字符串的情况下,KMP (en.wikipedia.org/wiki/…) 预计不会比这更快。例如,如果源是 AAAAAAAAAAAAAAAA,而搜索是 AAAAAAAAAAB,则 KMP 会更快。使用蛮力会有很多不必要的比较。
      【解决方案8】:

      使用Knuth–Morris–Pratt algorithm 是最有效的方式。

      StreamSearcher.java 是它的一个实现,是Twitterelephant-bird 项目的一部分。

      不建议包含这个库,因为它对于只使用一个类来说相当大。

      import java.io.IOException;
      import java.io.InputStream;
      import java.util.Arrays;
      
      /**
       * An efficient stream searching class based on the Knuth-Morris-Pratt algorithm.
       * For more on the algorithm works see: http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm.
       */
      public class StreamSearcher
      {
          private byte[] pattern_;
          private int[] borders_;
      
          // An upper bound on pattern length for searching. Results are undefined for longer patterns.
          @SuppressWarnings("unused")
          public static final int MAX_PATTERN_LENGTH = 1024;
      
          StreamSearcher(byte[] pattern)
          {
              setPattern(pattern);
          }
      
          /**
           * Sets a new pattern for this StreamSearcher to use.
           *
           * @param pattern the pattern the StreamSearcher will look for in future calls to search(...)
           */
          public void setPattern(byte[] pattern)
          {
              pattern_ = Arrays.copyOf(pattern, pattern.length);
              borders_ = new int[pattern_.length + 1];
              preProcess();
          }
      
          /**
           * Searches for the next occurrence of the pattern in the stream, starting from the current stream position. Note
           * that the position of the stream is changed. If a match is found, the stream points to the end of the match -- i.e. the
           * byte AFTER the pattern. Else, the stream is entirely consumed. The latter is because InputStream semantics make it difficult to have
           * another reasonable default, i.e. leave the stream unchanged.
           *
           * @return bytes consumed if found, -1 otherwise.
           */
          long search(InputStream stream) throws IOException
          {
              long bytesRead = 0;
      
              int b;
              int j = 0;
      
              while ((b = stream.read()) != -1)
              {
                  bytesRead++;
      
                  while (j >= 0 && (byte) b != pattern_[j])
                  {
                      j = borders_[j];
                  }
                  // Move to the next character in the pattern.
                  ++j;
      
                  // If we've matched up to the full pattern length, we found it.  Return,
                  // which will automatically save our position in the InputStream at the point immediately
                  // following the pattern match.
                  if (j == pattern_.length)
                  {
                      return bytesRead;
                  }
              }
      
              // No dice, Note that the stream is now completely consumed.
              return -1;
          }
      
          /**
           * Builds up a table of longest "borders" for each prefix of the pattern to find. This table is stored internally
           * and aids in implementation of the Knuth-Moore-Pratt string search.
           * <p>
           * For more information, see: http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm.
           */
          private void preProcess()
          {
              int i = 0;
              int j = -1;
              borders_[i] = j;
              while (i < pattern_.length)
              {
                  while (j >= 0 && pattern_[i] != pattern_[j])
                  {
                      j = borders_[j];
                  }
                  borders_[++i] = ++j;
              }
          }
      }
      

      【讨论】:

        【解决方案9】:

        对于我目前正在开发的一个小型 HTTP 服务器,我想出了以下代码来查找 multipart/form-data 请求中的边界。希望在这里找到更好的解决方案,但我想我会坚持下去。我认为它尽可能高效(非常快并且使用的内存不多)。它将输入字节用作环形缓冲区,一旦与边界不匹配就读取下一个字节,并将第一个完整周期后的数据写入输出流。当然可以按照问题中的要求将其更改为字节数组而不是流。

            private boolean multipartUploadParseOutput(InputStream is, OutputStream os, String boundary)
            {
                try
                {
                    String n = "--"+boundary;
                    byte[] bc = n.getBytes("UTF-8");
                    int s = bc.length;
                    byte[] b = new byte[s];
                    int p = 0;
                    long l = 0;
                    int c;
                    boolean r;
                    while ((c = is.read()) != -1)
                    {
                        b[p] = (byte) c;
                        l += 1;
                        p = (int) (l % s);
                        if (l>p)
                        {
                            r = true;
                            for (int i = 0; i < s; i++)
                            {
                                if (b[(p + i) % s] != bc[i])
                                {
                                    r = false;
                                    break;
                                }
                            }
                            if (r)
                                break;
                            os.write(b[p]);
                        }
                    }
                    os.flush();
                    return true;
                } catch(IOException e) {e.printStackTrace();}
                return false;
            }
        

        【讨论】:

          【解决方案10】:

          此处发布的几个(或全部?)示例未通过一些单元测试,因此我将我的版本与上述测试一起发布在这里。所有的单元测试都基于 Java 的 String.indexOf() 总是给我们正确答案的要求!

          // The Knuth, Morris, and Pratt string searching algorithm remembers information about
          // the past matched characters instead of matching a character with a different pattern
          // character over and over again. It can search for a pattern in O(n) time as it never
          // re-compares a text symbol that has matched a pattern symbol. But, it does use a partial
          // match table to analyze the pattern structure. Construction of a partial match table
          // takes O(m) time. Therefore, the overall time complexity of the KMP algorithm is O(m + n).
          
          public class KMPSearch {
          
              public static int indexOf(byte[] haystack, byte[] needle)
              {
                  // needle is null or empty
                  if (needle == null || needle.length == 0)
                      return 0;
          
                  // haystack is null, or haystack's length is less than that of needle
                  if (haystack == null || needle.length > haystack.length)
                      return -1;
          
                  // pre construct failure array for needle pattern
                  int[] failure = new int[needle.length];
                  int n = needle.length;
                  failure[0] = -1;
                  for (int j = 1; j < n; j++)
                  {
                      int i = failure[j - 1];
                      while ((needle[j] != needle[i + 1]) && i >= 0)
                          i = failure[i];
                      if (needle[j] == needle[i + 1])
                          failure[j] = i + 1;
                      else
                          failure[j] = -1;
                  }
          
                  // find match
                  int i = 0, j = 0;
                  int haystackLen = haystack.length;
                  int needleLen = needle.length;
                  while (i < haystackLen && j < needleLen)
                  {
                      if (haystack[i] == needle[j])
                      {
                          i++;
                          j++;
                      }
                      else if (j == 0)
                          i++;
                      else
                          j = failure[j - 1] + 1;
                  }
                  return ((j == needleLen) ? (i - needleLen) : -1);
              }
          }
          
          
          
          import java.util.Random;
          
          class KMPSearchTest {
              private static Random random = new Random();
              private static String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
          
              @Test
              public void testEmpty() {
                  test("", "");
                  test("", "ab");
              }
          
              @Test
              public void testOneChar() {
                  test("a", "a");
                  test("a", "b");
              }
          
              @Test
              public void testRepeat() {
                  test("aaa", "aaaaa");
                  test("aaa", "abaaba");
                  test("abab", "abacababc");
                  test("abab", "babacaba");
              }
          
              @Test
              public void testPartialRepeat() {
                  test("aaacaaaaac", "aaacacaacaaacaaaacaaaaac");
                  test("ababcababdabababcababdaba", "ababcababdabababcababdaba");
              }
          
              @Test
              public void testRandomly() {
                  for (int i = 0; i < 1000; i++) {
                      String pattern = randomPattern();
                      for (int j = 0; j < 100; j++)
                          test(pattern, randomText(pattern));
                  }
              }
          
              /* Helper functions */
              private static String randomPattern() {
                  StringBuilder sb = new StringBuilder();
                  int steps = random.nextInt(10) + 1;
                  for (int i = 0; i < steps; i++) {
                      if (sb.length() == 0 || random.nextBoolean()) {  // Add literal
                          int len = random.nextInt(5) + 1;
                          for (int j = 0; j < len; j++)
                              sb.append(alphabet.charAt(random.nextInt(alphabet.length())));
                      } else {  // Repeat prefix
                          int len = random.nextInt(sb.length()) + 1;
                          int reps = random.nextInt(3) + 1;
                          if (sb.length() + len * reps > 1000)
                              break;
                          for (int j = 0; j < reps; j++)
                              sb.append(sb.substring(0, len));
                      }
                  }
                  return sb.toString();
              }
          
              private static String randomText(String pattern) {
                  StringBuilder sb = new StringBuilder();
                  int steps = random.nextInt(100);
                  for (int i = 0; i < steps && sb.length() < 10000; i++) {
                      if (random.nextDouble() < 0.7) {  // Add prefix of pattern
                          int len = random.nextInt(pattern.length()) + 1;
                          sb.append(pattern.substring(0, len));
                      } else {  // Add literal
                          int len = random.nextInt(30) + 1;
                          for (int j = 0; j < len; j++)
                              sb.append(alphabet.charAt(random.nextInt(alphabet.length())));
                      }
                  }
                  return sb.toString();
              }
          
              private static void test(String pattern, String text) {
                  try {
                      assertEquals(text.indexOf(pattern), KMPSearch.indexOf(text.getBytes(), pattern.getBytes()));
                  } catch (AssertionError e) {
                      System.out.println("FAILED -> Unable to find '" + pattern + "' in '" + text + "'");
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2019-11-17
            • 2019-05-13
            • 2015-06-09
            • 1970-01-01
            • 1970-01-01
            • 2023-03-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多