Skip to content

Commit b5a4ec5

Browse files
committed
ByteStrings impl
add optimised lastIndexOf Update ByteString.scala Update ByteString.scala Update ByteString.scala
1 parent bda5b0c commit b5a4ec5

File tree

2 files changed

+188
-2
lines changed

2 files changed

+188
-2
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
@@ -852,7 +852,6 @@ class ByteStringSpec extends AnyWordSpec with Matchers with Checkers {
852852
compact.indexOf('e'.toByte) should ===(3)
853853
compact.indexOf('f'.toByte) should ===(4)
854854
compact.indexOf('g'.toByte) should ===(5)
855-
856855
}
857856
"indexOf (specialized) from offset" in {
858857
ByteString.empty.indexOf(5.toByte, -1) should ===(-1)
@@ -1001,6 +1000,56 @@ class ByteStringSpec extends AnyWordSpec with Matchers with Checkers {
10011000
byteStringLong.indexOf('z', 2, 24) should ===(-1)
10021001
byteStringLong.indexOf('a', 2, 24) should ===(-1)
10031002
}
1003+
"lastIndexOf (specialized)" in {
1004+
ByteString.empty.lastIndexOf(5.toByte, -1) should ===(-1)
1005+
ByteString.empty.lastIndexOf(5.toByte, 0) should ===(-1)
1006+
ByteString.empty.lastIndexOf(5.toByte, 1) should ===(-1)
1007+
ByteString.empty.lastIndexOf(5.toByte) should ===(-1)
1008+
val byteString1 = ByteString1.fromString("abb")
1009+
byteString1.lastIndexOf('d'.toByte) should ===(-1)
1010+
byteString1.lastIndexOf('d'.toByte, -1) should ===(-1)
1011+
byteString1.lastIndexOf('d'.toByte, 4) should ===(-1)
1012+
byteString1.lastIndexOf('d'.toByte, 1) should ===(-1)
1013+
byteString1.lastIndexOf('d'.toByte, 0) should ===(-1)
1014+
byteString1.lastIndexOf('a'.toByte, -1) should ===(-1)
1015+
byteString1.lastIndexOf('a'.toByte) should ===(0)
1016+
byteString1.lastIndexOf('a'.toByte, 0) should ===(0)
1017+
byteString1.lastIndexOf('a'.toByte, 1) should ===(0)
1018+
byteString1.lastIndexOf('b'.toByte) should ===(2)
1019+
byteString1.lastIndexOf('b'.toByte, 2) should ===(2)
1020+
byteString1.lastIndexOf('b'.toByte, 1) should ===(1)
1021+
byteString1.lastIndexOf('b'.toByte, 0) should ===(-1)
1022+
1023+
val byteStrings = ByteStrings(ByteString1.fromString("abb"), ByteString1.fromString("efg"))
1024+
byteStrings.lastIndexOf('e'.toByte) should ===(3)
1025+
byteStrings.lastIndexOf('e'.toByte, 6) should ===(3)
1026+
byteStrings.lastIndexOf('e'.toByte, 4) should ===(3)
1027+
byteStrings.lastIndexOf('e'.toByte, 1) should ===(-1)
1028+
byteStrings.lastIndexOf('e'.toByte, 0) should ===(-1)
1029+
byteStrings.lastIndexOf('e'.toByte, -1) should ===(-1)
1030+
1031+
byteStrings.lastIndexOf('b'.toByte) should ===(2)
1032+
byteStrings.lastIndexOf('b'.toByte, 6) should ===(2)
1033+
byteStrings.lastIndexOf('b'.toByte, 4) should ===(2)
1034+
byteStrings.lastIndexOf('b'.toByte, 1) should ===(1)
1035+
byteStrings.lastIndexOf('b'.toByte, 0) should ===(-1)
1036+
byteStrings.lastIndexOf('b'.toByte, -1) should ===(-1)
1037+
1038+
val compact = byteStrings.compact
1039+
compact.lastIndexOf('e'.toByte) should ===(3)
1040+
compact.lastIndexOf('e'.toByte, 6) should ===(3)
1041+
compact.lastIndexOf('e'.toByte, 4) should ===(3)
1042+
compact.lastIndexOf('e'.toByte, 1) should ===(-1)
1043+
compact.lastIndexOf('e'.toByte, 0) should ===(-1)
1044+
compact.lastIndexOf('e'.toByte, -1) should ===(-1)
1045+
1046+
compact.lastIndexOf('b'.toByte) should ===(2)
1047+
compact.lastIndexOf('b'.toByte, 6) should ===(2)
1048+
compact.lastIndexOf('b'.toByte, 4) should ===(2)
1049+
compact.lastIndexOf('b'.toByte, 1) should ===(1)
1050+
compact.lastIndexOf('b'.toByte, 0) should ===(-1)
1051+
compact.lastIndexOf('b'.toByte, -1) should ===(-1)
1052+
}
10041053
"copyToArray" in {
10051054
val byteString = ByteString(1, 2) ++ ByteString(3) ++ ByteString(4)
10061055

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

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,32 @@ object ByteString {
314314
else -1
315315
}
316316

317+
override def lastIndexOf[B >: Byte](elem: B, end: Int): Int = {
318+
if (end < 0) -1
319+
else {
320+
var found = -1
321+
var i = math.min(end, length - 1)
322+
while (i >= 0 && found == -1) {
323+
if (bytes(i) == elem) found = i
324+
i -= 1
325+
}
326+
found
327+
}
328+
}
329+
330+
override def lastIndexOf(elem: Byte, end: Int): Int = {
331+
if (end < 0) -1
332+
else {
333+
var found = -1
334+
var i = math.min(end, length - 1)
335+
while (i >= 0 && found == -1) {
336+
if (bytes(i) == elem) found = i
337+
i -= 1
338+
}
339+
found
340+
}
341+
}
342+
317343
override def slice(from: Int, until: Int): ByteString =
318344
if (from <= 0 && until >= length) this
319345
else if (from >= length || until <= 0 || from >= until) ByteString.empty
@@ -554,7 +580,6 @@ object ByteString {
554580
i += 1
555581
}
556582
-1
557-
558583
}
559584

560585
// the calling code already adds the startIndex so this method does not need to
@@ -575,6 +600,32 @@ object ByteString {
575600
else -1
576601
}
577602

603+
override def lastIndexOf[B >: Byte](elem: B, end: Int): Int = {
604+
if (end < 0) -1
605+
else {
606+
var found = -1
607+
var i = math.min(end, length - 1)
608+
while (i >= 0 && found == -1) {
609+
if (bytes(startIndex + i) == elem) found = i
610+
i -= 1
611+
}
612+
found
613+
}
614+
}
615+
616+
override def lastIndexOf(elem: Byte, end: Int): Int = {
617+
if (end < 0) -1
618+
else {
619+
var found = -1
620+
var i = math.min(end, length - 1)
621+
while (i >= 0 && found == -1) {
622+
if (bytes(startIndex + i) == elem) found = i
623+
i -= 1
624+
}
625+
found
626+
}
627+
}
628+
578629
override def copyToArray[B >: Byte](dest: Array[B], start: Int, len: Int): Int = {
579630
// min of the bytes available to copy, bytes there is room for in dest and the requested number of bytes
580631
val toCopy = math.min(math.min(len, length), dest.length - start)
@@ -895,6 +946,67 @@ object ByteString {
895946
}
896947
}
897948

949+
override def lastIndexOf[B >: Byte](elem: B, end: Int): Int = {
950+
if (end < 0) -1
951+
else {
952+
val byteStringsLast = bytestrings.size - 1
953+
954+
@tailrec
955+
def find(bsIdx: Int, relativeIndex: Int, len: Int): Int = {
956+
if (bsIdx < 0) -1
957+
else {
958+
val bs = bytestrings(bsIdx)
959+
val bsStartIndex = len - bs.length
960+
961+
if (relativeIndex < bsStartIndex || bs.isEmpty) {
962+
if (bsIdx == 0) -1
963+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
964+
} else {
965+
val subIndexOf = bs.lastIndexOf(elem, relativeIndex)
966+
if (subIndexOf < 0) {
967+
if (bsIdx == 0) -1
968+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
969+
} else subIndexOf + bsStartIndex
970+
}
971+
}
972+
}
973+
974+
find(byteStringsLast, math.min(end, length), length)
975+
}
976+
}
977+
978+
override def lastIndexOf(elem: Byte, end: Int): Int = {
979+
if (end < 0) -1
980+
else {
981+
if (end < 0) -1
982+
else {
983+
val byteStringsLast = bytestrings.size - 1
984+
985+
@tailrec
986+
def find(bsIdx: Int, relativeIndex: Int, len: Int): Int = {
987+
if (bsIdx < 0) -1
988+
else {
989+
val bs = bytestrings(bsIdx)
990+
val bsStartIndex = len - bs.length
991+
992+
if (relativeIndex < bsStartIndex || bs.isEmpty) {
993+
if (bsIdx == 0) -1
994+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
995+
} else {
996+
val subIndexOf = bs.lastIndexOf(elem, relativeIndex)
997+
if (subIndexOf < 0) {
998+
if (bsIdx == 0) -1
999+
else find(bsIdx - 1, relativeIndex, bsStartIndex)
1000+
} else subIndexOf + bsStartIndex
1001+
}
1002+
}
1003+
}
1004+
1005+
find(byteStringsLast, math.min(end, length), length)
1006+
}
1007+
}
1008+
}
1009+
8981010
override def copyToArray[B >: Byte](dest: Array[B], start: Int, len: Int): Int = {
8991011
if (bytestrings.size == 1) bytestrings.head.copyToArray(dest, start, len)
9001012
else {
@@ -1056,6 +1168,31 @@ sealed abstract class ByteString
10561168
*/
10571169
def indexOf(elem: Byte): Int = indexOf(elem, 0)
10581170

1171+
/**
1172+
* Finds index of last occurrence of some byte in this ByteString before or at some end index.
1173+
*
1174+
* Similar to lastIndexOf, but it avoids boxing if the value is already a byte.
1175+
*
1176+
* @param elem the element value to search for.
1177+
* @param end the end index
1178+
* @return the index `<= end` of the last element of this ByteString that is equal (as determined by `==`)
1179+
* to `elem`, or `-1`, if none exists.
1180+
* @since 2.0.0
1181+
*/
1182+
def lastIndexOf(elem: Byte, end: Int): Int = lastIndexOf[Byte](elem, end)
1183+
1184+
/**
1185+
* Finds index of last occurrence of some byte in this ByteString.
1186+
*
1187+
* Similar to lastIndexOf, but it avoids boxing if the value is already a byte.
1188+
*
1189+
* @param elem the element value to search for.
1190+
* @return the index of the last element of this ByteString that is equal (as determined by `==`)
1191+
* to `elem`, or `-1`, if none exists.
1192+
* @since 2.0.0
1193+
*/
1194+
def lastIndexOf(elem: Byte): Int = lastIndexOf(elem, length - 1)
1195+
10591196
override def contains[B >: Byte](elem: B): Boolean = indexOf(elem, 0) != -1
10601197

10611198
/**

0 commit comments

Comments
 (0)