This is an automated email from the ASF dual-hosted git repository.

fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new e62592c4b4 specialized version of indexOfSlice (#2306)
e62592c4b4 is described below

commit e62592c4b49dafdf1ab20f941be8a89b17bdb6c1
Author: PJ Fanning <[email protected]>
AuthorDate: Sat Oct 18 09:24:56 2025 +0100

    specialized version of indexOfSlice (#2306)
    
    * try specialized version of indexOf
    
    * specialised ByteStrings indexOfSlice
    
    * Update ByteString.scala
    
    Update ByteString.scala
    
    Update ByteString.scala
    
    Update ByteString.scala
    
    * remove ByteStrings indexOfSlice
    
    * Update ByteString.scala
    
    add test
    
    add test
    
    Update ByteString.scala
    
    * Update actor/src/main/scala/org/apache/pekko/util/ByteString.scala
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update actor/src/main/scala/org/apache/pekko/util/ByteString.scala
    
    Co-authored-by: Copilot <[email protected]>
    
    * Apply suggestion from @Copilot
    
    Co-authored-by: Copilot <[email protected]>
    
    * Update ByteStringSpec.scala
    
    * when slice is len=1, use indexOf instead
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 .../org/apache/pekko/util/ByteStringSpec.scala     |  45 +++++++++
 .../scala/org/apache/pekko/util/ByteString.scala   | 109 ++++++++++++++++++++-
 2 files changed, 152 insertions(+), 2 deletions(-)

diff --git 
a/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala 
b/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
index 35d96f80be..81fe9cc93b 100644
--- a/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
+++ b/actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala
@@ -1041,16 +1041,61 @@ class ByteStringSpec extends AnyWordSpec with Matchers 
with Checkers {
       val slice0 = ByteString1.fromString("xyz")
       val slice1 = ByteString1.fromString("xyzabc")
       val notSlice = ByteString1.fromString("12345")
+      val pByte = ByteString1.fromString("p")
       val byteStringLong = ByteString1.fromString("abcdefghijklmnopqrstuvwxyz")
       val byteStrings = ByteStrings(byteStringLong, byteStringLong)
       byteStringLong.indexOfSlice(slice0) should ===(23)
       byteStringLong.indexOfSlice(slice1) should ===(-1)
       byteStringLong.indexOfSlice(notSlice) should ===(-1)
+      byteStringLong.indexOfSlice(pByte) should ===(15)
 
       byteStrings.indexOfSlice(slice0) should ===(23)
       byteStrings.indexOfSlice(slice1) should ===(23)
       byteStrings.indexOfSlice(notSlice) should ===(-1)
+      byteStrings.indexOfSlice(pByte) should ===(15)
+      byteStrings.indexOfSlice(pByte, 16) should ===(41)
+
+      val byteStringXxyz = ByteString1.fromString("xxyz")
+      byteStringXxyz.indexOfSlice(slice0) should ===(1)
+      byteStringXxyz.indexOfSlice(slice0, 2) should ===(-1)
     }
+    "indexOfSlice (specialized)" in {
+      val slice0 = "xyz".getBytes(StandardCharsets.UTF_8)
+      val slice1 = "xyzabc".getBytes(StandardCharsets.UTF_8)
+      val notSlice = "12345".getBytes(StandardCharsets.UTF_8)
+      val pByte = Array('p'.toByte)
+      val byteStringLong = ByteString1.fromString("abcdefghijklmnopqrstuvwxyz")
+      val byteStrings = ByteStrings(byteStringLong, byteStringLong)
+      byteStringLong.indexOfSlice(slice0) should ===(23)
+      byteStringLong.indexOfSlice(slice1) should ===(-1)
+      byteStringLong.indexOfSlice(notSlice) should ===(-1)
+      byteStringLong.indexOfSlice(pByte) should ===(15)
+
+      byteStrings.indexOfSlice(slice0) should ===(23)
+      byteStrings.indexOfSlice(slice1) should ===(23)
+      byteStrings.indexOfSlice(notSlice) should ===(-1)
+      byteStrings.indexOfSlice(pByte) should ===(15)
+      byteStrings.indexOfSlice(pByte, 16) should ===(41)
+
+      val byteStringXxyz = ByteString1.fromString("xxyz")
+      byteStringXxyz.indexOfSlice(slice0) should ===(1)
+      byteStringXxyz.indexOfSlice(slice0, 2) should ===(-1)
+    }
+    "startsWith (specialized)" in {
+      val slice0 = "abc".getBytes(StandardCharsets.UTF_8)
+      val slice1 = "xyz".getBytes(StandardCharsets.UTF_8)
+      val notSlice = "12345".getBytes(StandardCharsets.UTF_8)
+      val byteStringLong = ByteString1.fromString("abcdefghijklmnopqrstuvwxyz")
+      val byteStrings = ByteStrings(byteStringLong, byteStringLong)
+      byteStringLong.startsWith(slice0) should ===(true)
+      byteStringLong.startsWith(slice1, 23) should ===(true)
+      byteStringLong.startsWith(notSlice) should ===(false)
+
+      byteStrings.startsWith(slice0) should ===(true)
+      byteStrings.startsWith(slice1, 23) should ===(true)
+      byteStrings.startsWith(notSlice) should ===(false)
+    }
+
   }
 
   "A ByteString" must {
diff --git a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala 
b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
index 849067dc2a..952e3fe060 100644
--- a/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/ByteString.scala
@@ -1034,7 +1034,7 @@ sealed abstract class ByteString
   /**
    * Finds index of first occurrence of some byte in this ByteString after or 
at some start index.
    *
-   * Similar to indexOf, but it avoids boxing if the value is already a byte.
+   * Similar to Seq's indexOf, but it avoids boxing if the value is already a 
byte.
    *
    *  @param   elem   the element value to search for.
    *  @param   from   the start index
@@ -1047,7 +1047,7 @@ sealed abstract class ByteString
   /**
    * Finds index of first occurrence of some byte in this ByteString.
    *
-   * Similar to indexOf, but it avoids boxing if the value is already a byte.
+   * Similar to Seq's indexOf, but it avoids boxing if the value is already a 
byte.
    *
    *  @param   elem   the element value to search for.
    *  @return  the index `>= from` of the first element of this ByteString 
that is equal (as determined by `==`)
@@ -1056,6 +1056,78 @@ sealed abstract class ByteString
    */
   def indexOf(elem: Byte): Int = indexOf(elem, 0)
 
+  override def indexOfSlice[B >: Byte](slice: scala.collection.Seq[B], from: 
Int): Int = {
+    // this is only called if the first byte matches, so we can skip that check
+    def check(startPos: Int): Boolean = {
+      var i = startPos + 1
+      var j = 1
+      // Bounds are guaranteed by the indexOf call limiting the search range, 
so startPos + slice.length <= length.
+      while (j < slice.length) {
+        if (apply(i) != slice(j)) return false
+        i += 1
+        j += 1
+      }
+      true
+    }
+    val headByte = slice.head.asInstanceOf[Byte]
+    @tailrec def rec(from: Int): Int = {
+      val startPos = indexOf(headByte, from, length - slice.length + 1)
+      if (startPos == -1) -1
+      else if (check(startPos)) startPos
+      else rec(startPos + 1)
+    }
+    val sliceLength = slice.length
+    if (sliceLength == 0) 0
+    else if (sliceLength == 1) indexOf(headByte, from)
+    else rec(math.max(0, from))
+  }
+
+  /**
+   * Finds index of first occurrence of some slice in this ByteString.
+   *
+   * @param   slice   the slice to search for.
+   * @param   from    the start index
+   * @return  the index greater than or equal to `from` of the first element 
of this
+   *          ByteString that starts a slice equal (as determined by `==`)
+   *          to `slice`, or `-1`, if none exists.
+   * @since 2.0.0
+   */
+  def indexOfSlice(slice: Array[Byte], from: Int): Int = {
+    // this is only called if the first byte matches, so we can skip that check
+    def check(startPos: Int): Boolean = {
+      var i = startPos + 1
+      var j = 1
+      // let's trust the calling code has ensured that we have enough bytes in 
this ByteString
+      while (j < slice.length) {
+        if (apply(i) != slice(j)) return false
+        i += 1
+        j += 1
+      }
+      true
+    }
+    @tailrec def rec(from: Int): Int = {
+      val startPos = indexOf(slice.head, from, length - slice.length + 1)
+      if (startPos == -1) -1
+      else if (check(startPos)) startPos
+      else rec(startPos + 1)
+    }
+    val sliceLength = slice.length
+    if (sliceLength == 0) 0
+    else if (sliceLength == 1) indexOf(slice.head, from)
+    else rec(math.max(0, from))
+  }
+
+  /**
+   * Finds index of first occurrence of some slice in this ByteString.
+   *
+   * @param   slice   the slice to search for.
+   * @return  the index of the first element of this
+   *          ByteString that starts a slice equal (as determined by `==`)
+   *          to `slice`, or `-1`, if none exists.
+   * @since 2.0.0
+   */
+  def indexOfSlice(slice: Array[Byte]): Int = indexOfSlice(slice, 0)
+
   override def contains[B >: Byte](elem: B): Boolean = indexOf(elem, 0) != -1
 
   /**
@@ -1065,6 +1137,39 @@ sealed abstract class ByteString
    */
   def contains(elem: Byte): Boolean = indexOf(elem, 0) != -1
 
+  /**
+   * Tests whether this ByteString starts with the given slice.
+   *
+   * @param bytes the slice to test
+   * @param offset the offset to start testing from
+   * @return true if this ByteString starts with the given slice
+   * @since 2.0.0
+   */
+  def startsWith(bytes: Array[Byte], offset: Int): Boolean = {
+    if (length - offset < bytes.length) false
+    else {
+      var i = offset
+      var j = 0
+      while (j < bytes.length) {
+        // we know that byteString is at least as long as bytes,
+        // so no need to check i < length
+        if (apply(i) != bytes(j)) return false
+        i += 1
+        j += 1
+      }
+      true
+    }
+  }
+
+  /**
+   * Tests whether this ByteString starts with the given slice.
+   *
+   * @param bytes the slice to test
+   * @return true if this ByteString starts with the given slice
+   * @since 2.0.0
+   */
+  def startsWith(bytes: Array[Byte]): Boolean = startsWith(bytes, 0)
+
   override def grouped(size: Int): Iterator[ByteString] = {
     if (size <= 0) {
       throw new IllegalArgumentException(s"size=$size must be positive")


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to