Skip to content

Commit fab09a7

Browse files
committed
ByteStrings impl
add optimised lastIndexOf Update ByteString.scala Update ByteString.scala
1 parent c708891 commit fab09a7

File tree

4 files changed

+470
-13
lines changed

4 files changed

+470
-13
lines changed

actor-tests/src/test/scala/org/apache/pekko/util/ByteStringSpec.scala

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,6 @@ class ByteStringSpec extends AnyWordSpec with Matchers with Checkers {
804804
compact.indexOf('e'.toByte) should ===(3)
805805
compact.indexOf('f'.toByte) should ===(4)
806806
compact.indexOf('g'.toByte) should ===(5)
807-
808807
}
809808
"indexOf (specialized) from offset" in {
810809
ByteString.empty.indexOf(5.toByte, -1) should ===(-1)
@@ -857,6 +856,56 @@ class ByteStringSpec extends AnyWordSpec with Matchers with Checkers {
857856
compact.indexOf('g'.toByte, 5) should ===(5)
858857
compact.indexOf('g'.toByte, 6) should ===(-1)
859858
}
859+
"lastIndexOf (specialized)" in {
860+
ByteString.empty.lastIndexOf(5.toByte, -1) should ===(-1)
861+
ByteString.empty.lastIndexOf(5.toByte, 0) should ===(-1)
862+
ByteString.empty.lastIndexOf(5.toByte, 1) should ===(-1)
863+
ByteString.empty.lastIndexOf(5.toByte) should ===(-1)
864+
val byteString1 = ByteString1.fromString("abb")
865+
byteString1.lastIndexOf('d'.toByte) should ===(-1)
866+
byteString1.lastIndexOf('d'.toByte, -1) should ===(-1)
867+
byteString1.lastIndexOf('d'.toByte, 4) should ===(-1)
868+
byteString1.lastIndexOf('d'.toByte, 1) should ===(-1)
869+
byteString1.lastIndexOf('d'.toByte, 0) should ===(-1)
870+
byteString1.lastIndexOf('a'.toByte, -1) should ===(-1)
871+
byteString1.lastIndexOf('a'.toByte) should ===(0)
872+
byteString1.lastIndexOf('a'.toByte, 0) should ===(0)
873+
byteString1.lastIndexOf('a'.toByte, 1) should ===(0)
874+
byteString1.lastIndexOf('b'.toByte) should ===(2)
875+
byteString1.lastIndexOf('b'.toByte, 2) should ===(2)
876+
byteString1.lastIndexOf('b'.toByte, 1) should ===(1)
877+
byteString1.lastIndexOf('b'.toByte, 0) should ===(-1)
878+
879+
val byteStrings = ByteStrings(ByteString1.fromString("abb"), ByteString1.fromString("efg"))
880+
byteStrings.lastIndexOf('e'.toByte) should ===(3)
881+
byteStrings.lastIndexOf('e'.toByte, 6) should ===(3)
882+
byteStrings.lastIndexOf('e'.toByte, 4) should ===(3)
883+
byteStrings.lastIndexOf('e'.toByte, 1) should ===(-1)
884+
byteStrings.lastIndexOf('e'.toByte, 0) should ===(-1)
885+
byteStrings.lastIndexOf('e'.toByte, -1) should ===(-1)
886+
887+
byteStrings.lastIndexOf('b'.toByte) should ===(2)
888+
byteStrings.lastIndexOf('b'.toByte, 6) should ===(2)
889+
byteStrings.lastIndexOf('b'.toByte, 4) should ===(2)
890+
byteStrings.lastIndexOf('b'.toByte, 1) should ===(1)
891+
byteStrings.lastIndexOf('b'.toByte, 0) should ===(-1)
892+
byteStrings.lastIndexOf('b'.toByte, -1) should ===(-1)
893+
894+
val compact = byteStrings.compact
895+
compact.lastIndexOf('e'.toByte) should ===(3)
896+
compact.lastIndexOf('e'.toByte, 6) should ===(3)
897+
compact.lastIndexOf('e'.toByte, 4) should ===(3)
898+
compact.lastIndexOf('e'.toByte, 1) should ===(-1)
899+
compact.lastIndexOf('e'.toByte, 0) should ===(-1)
900+
compact.lastIndexOf('e'.toByte, -1) should ===(-1)
901+
902+
compact.lastIndexOf('b'.toByte) should ===(2)
903+
compact.lastIndexOf('b'.toByte, 6) should ===(2)
904+
compact.lastIndexOf('b'.toByte, 4) should ===(2)
905+
compact.lastIndexOf('b'.toByte, 1) should ===(1)
906+
compact.lastIndexOf('b'.toByte, 0) should ===(-1)
907+
compact.lastIndexOf('b'.toByte, -1) should ===(-1)
908+
}
860909
"copyToArray" in {
861910
val byteString = ByteString(1, 2) ++ ByteString(3) ++ ByteString(4)
862911

actor/src/main/scala-2.12/org/apache/pekko/util/ByteString.scala

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,34 @@ object ByteString {
260260
}
261261
}
262262

263+
override def lastIndexOf[B >: Byte](elem: B): Int = lastIndexOf(elem, length - 1)
264+
override def lastIndexOf[B >: Byte](elem: B, end: Int): Int = {
265+
if (end < 0) -1
266+
else {
267+
var found = -1
268+
var i = math.min(end, length - 1)
269+
while (i >= 0 && found == -1) {
270+
if (bytes(i) == elem) found = i
271+
i -= 1
272+
}
273+
found
274+
}
275+
}
276+
277+
override def lastIndexOf(elem: Byte): Int = lastIndexOf(elem, length - 1)
278+
override def lastIndexOf(elem: Byte, end: Int): Int = {
279+
if (end < 0) -1
280+
else {
281+
var found = -1
282+
var i = math.min(end, length - 1)
283+
while (i >= 0 && found == -1) {
284+
if (bytes(i) == elem) found = i
285+
i -= 1
286+
}
287+
found
288+
}
289+
}
290+
263291
override def slice(from: Int, until: Int): ByteString =
264292
if (from <= 0 && until >= length) this
265293
else if (from >= length || until <= 0 || from >= until) ByteString.empty
@@ -461,6 +489,34 @@ object ByteString {
461489
}
462490
}
463491

492+
override def lastIndexOf[B >: Byte](elem: B): Int = lastIndexOf(elem, length - 1)
493+
override def lastIndexOf[B >: Byte](elem: B, end: Int): Int = {
494+
if (end < 0) -1
495+
else {
496+
var found = -1
497+
var i = math.min(end, length - 1)
498+
while (i >= 0 && found == -1) {
499+
if (bytes(startIndex + i) == elem) found = i
500+
i -= 1
501+
}
502+
found
503+
}
504+
}
505+
506+
override def lastIndexOf(elem: Byte): Int = lastIndexOf(elem, length - 1)
507+
override def lastIndexOf(elem: Byte, end: Int): Int = {
508+
if (end < 0) -1
509+
else {
510+
var found = -1
511+
var i = math.min(end, length - 1)
512+
while (i >= 0 && found == -1) {
513+
if (bytes(startIndex + i) == elem) found = i
514+
i -= 1
515+
}
516+
found
517+
}
518+
}
519+
464520
protected def writeReplace(): AnyRef = new SerializationProxy(this)
465521

466522
override def toArrayUnsafe(): Array[Byte] = {
@@ -707,7 +763,7 @@ object ByteString {
707763
else {
708764
val bs = bytestrings(bsIdx)
709765

710-
if (bs.length <= relativeIndex) {
766+
if (bs.length <= relativeIndex || bs.isEmpty) {
711767
find(bsIdx + 1, relativeIndex - bs.length, bytesPassed + bs.length)
712768
} else {
713769
val subIndexOf = bs.indexOf(elem, relativeIndex)
@@ -734,7 +790,7 @@ object ByteString {
734790
else {
735791
val bs = bytestrings(bsIdx)
736792

737-
if (bs.length <= relativeIndex) {
793+
if (bs.length <= relativeIndex || bs.isEmpty) {
738794
find(bsIdx + 1, relativeIndex - bs.length, bytesPassed + bs.length)
739795
} else {
740796
val subIndexOf = bs.indexOf(elem, relativeIndex)
@@ -749,6 +805,69 @@ object ByteString {
749805
}
750806
}
751807

808+
override def lastIndexOf[B >: Byte](elem: B): Int = lastIndexOf(elem, length - 1)
809+
override def lastIndexOf[B >: Byte](elem: B, end: Int): Int = {
810+
if (end < 0) -1
811+
else {
812+
val byteStringsLast = bytestrings.size - 1
813+
814+
@tailrec
815+
def find(bsIdx: Int, relativeIndex: Int, len: Int): Int = {
816+
if (bsIdx < 0) -1
817+
else {
818+
val bs = bytestrings(bsIdx)
819+
val bsStartIndex = len - bs.length
820+
821+
if (relativeIndex < bsStartIndex || bs.isEmpty) {
822+
if (bsIdx == 0) -1
823+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
824+
} else {
825+
val subIndexOf = bs.lastIndexOf(elem, relativeIndex)
826+
if (subIndexOf < 0) {
827+
if (bsIdx == 0) -1
828+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
829+
} else subIndexOf + bsStartIndex
830+
}
831+
}
832+
}
833+
834+
find(byteStringsLast, math.min(end, length), length)
835+
}
836+
}
837+
838+
override def lastIndexOf(elem: Byte): Int = lastIndexOf(elem, length - 1)
839+
override def lastIndexOf(elem: Byte, end: Int): Int = {
840+
if (end < 0) -1
841+
else {
842+
if (end < 0) -1
843+
else {
844+
val byteStringsLast = bytestrings.size - 1
845+
846+
@tailrec
847+
def find(bsIdx: Int, relativeIndex: Int, len: Int): Int = {
848+
if (bsIdx < 0) -1
849+
else {
850+
val bs = bytestrings(bsIdx)
851+
val bsStartIndex = len - bs.length
852+
853+
if (relativeIndex < bsStartIndex || bs.isEmpty) {
854+
if (bsIdx == 0) -1
855+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
856+
} else {
857+
val subIndexOf = bs.lastIndexOf(elem, relativeIndex)
858+
if (subIndexOf < 0) {
859+
if (bsIdx == 0) -1
860+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
861+
} else subIndexOf + bsStartIndex
862+
}
863+
}
864+
}
865+
866+
find(byteStringsLast, math.min(end, length), length)
867+
}
868+
}
869+
}
870+
752871
protected def writeReplace(): AnyRef = new SerializationProxy(this)
753872
}
754873

@@ -846,7 +965,7 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
846965
// optimized in subclasses
847966
override def indexOf[B >: Byte](elem: B): Int = indexOf[B](elem, 0)
848967

849-
// optimized version of indexOf for bytes, implemented in subclasses
968+
// optimized versions of indexOf and lastIndexOf for bytes, optimized in subclasses
850969
/**
851970
* Finds index of first occurrence of some byte in this ByteString after or at some start index.
852971
*
@@ -866,12 +985,37 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
866985
* Similar to indexOf, but it avoids boxing if the value is already a byte.
867986
*
868987
* @param elem the element value to search for.
869-
* @return the index `>= from` of the first element of this ByteString that is equal (as determined by `==`)
988+
* @return the index of the first element of this ByteString that is equal (as determined by `==`)
870989
* to `elem`, or `-1`, if none exists.
871990
* @since 1.1.0
872991
*/
873992
def indexOf(elem: Byte): Int = indexOf(elem, 0)
874993

994+
/**
995+
* Finds index of last occurrence of some byte in this ByteString before or at some end index.
996+
*
997+
* Similar to lastIndexOf, but it avoids boxing if the value is already a byte.
998+
*
999+
* @param elem the element value to search for.
1000+
* @param end the end index
1001+
* @return the index `<= end` of the last element of this ByteString that is equal (as determined by `==`)
1002+
* to `elem`, or `-1`, if none exists.
1003+
* @since 2.0.0
1004+
*/
1005+
def lastIndexOf(elem: Byte, end: Int): Int = lastIndexOf[Byte](elem, end)
1006+
1007+
/**
1008+
* Finds index of last occurrence of some byte in this ByteString.
1009+
*
1010+
* Similar to lastIndexOf, but it avoids boxing if the value is already a byte.
1011+
*
1012+
* @param elem the element value to search for.
1013+
* @return the index of the last element of this ByteString that is equal (as determined by `==`)
1014+
* to `elem`, or `-1`, if none exists.
1015+
* @since 2.0.0
1016+
*/
1017+
def lastIndexOf(elem: Byte): Int = lastIndexOf(elem, length - 1)
1018+
8751019
override def grouped(size: Int): Iterator[ByteString] = {
8761020
if (size <= 0) {
8771021
throw new IllegalArgumentException(s"size=$size must be positive")

0 commit comments

Comments
 (0)