-
-
Save fabiomsr/845664a9c7e92bafb6fb0ca70d4e44fd to your computer and use it in GitHub Desktop.
private val HEX_CHARS = "0123456789ABCDEF".toCharArray() | |
fun ByteArray.toHex() : String{ | |
val result = StringBuffer() | |
forEach { | |
val octet = it.toInt() | |
val firstIndex = (octet and 0xF0).ushr(4) | |
val secondIndex = octet and 0x0F | |
result.append(HEX_CHARS[firstIndex]) | |
result.append(HEX_CHARS[secondIndex]) | |
} | |
return result.toString() | |
} |
private val HEX_CHARS = "0123456789ABCDEF" | |
fun String.hexStringToByteArray() : ByteArray { | |
val result = ByteArray(length / 2) | |
for (i in 0 until length step 2) { | |
val firstIndex = HEX_CHARS.indexOf(this[i]); | |
val secondIndex = HEX_CHARS.indexOf(this[i + 1]); | |
val octet = firstIndex.shl(4).or(secondIndex) | |
result.set(i.shr(1), octet.toByte()) | |
} | |
return result | |
} |
Be careful, it only accepts uppercase strings.
It took me a half an hour to realize that my problem was there 🤦♂️
Another one liner:
byteArray.map { String.format("%02X", (it.toInt() and 0xFF)) }.joinToString(separator = "")
Or
byteArray.joinToString("") { String.format("%02X", (it.toInt() and 0xFF)) }
I would also add some validation
if (firstIndex < 0) throw Exception("${this[i]} is not a valid Hex value")
same with secondIndex
fun ByteArray.toHexString()=this.joinToString(""){ String.format("%02X",(it.toInt() and 0xFF)) }
fun String.byteArrayFromHexString()=this.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
update for
//will handle Upper or Lower sequence
fun String.byteArrayFromHexString()=this.chunked(2).map { it.toUpperCase().toInt(16).toByte() }.toByteArray()
I more like this,inline
or not take easy
val ByteArray.asHexLower inline get() = this.joinToString(separator = ""){ String.format("%02x",(it.toInt() and 0xFF))}
val ByteArray.asHexUpper inline get() = this.joinToString(separator = ""){ String.format("%02X",(it.toInt() and 0xFF))}
val String.hexAsByteArray inline get() = this.chunked(2).map { it.toUpperCase().toInt(16).toByte() }.toByteArray()
if make it more safe
I realy suggestion that better not throw Exception that not meanful,return null is may better choice
val String.tryHexAsByteArray inline get() = try{this.chunked(2).map { it.toUpperCase().toInt(16).toByte() }.toByteArray()}catch(e:Throwable){null}
//could be used as
func someAction(src:String){
val bytes=src.tryHexAsByteArray?:throw Exception("not valid HEX string")
//... do something with ByteArray
}
What's wrong with byteArray.joinToString("") { "%02X".format(it) }
? It seems to produce exactly the same results as the other proposed answers. Is (it.toInt() and 0xFF)
really necessary?
@gmk57
a full testing
val HEX_CHARS = "0123456789abcdef"
val toHEX = { b: ByteArray ->
val result = StringBuffer()
b.forEach {
val octet = it.toInt()
val firstIndex = (octet and 0xF0).ushr(4)
val secondIndex = octet and 0x0F
result.append(HEX_CHARS[firstIndex])
result.append(HEX_CHARS[secondIndex])
}
result.toString()
}
val toHEX2 = { b: ByteArray ->
buildString {
b.forEach {
val octet = it.toInt()
val firstIndex = (octet and 0xF0).ushr(4)
val secondIndex = octet and 0x0F
append(HEX_CHARS[firstIndex])
append(HEX_CHARS[secondIndex])
}
}
}
val source = "zzhz中午".toByteArray()
val measureOp = { op: String, act: () -> String ->
measureNanoTime {
(0..1000000).forEach {
act()
}
}.apply {
println("$op: ${this / 1000000.0 / 1000.0 / 8} ms/op/byte")
}
}
source.joinToString("") { String.format("%02x", it) }.apply(::println)
//7a7a687ae4b8ade58d88 good
source.joinToString("") { it.toString(16).padStart(2, '0') }.apply(::println)
//7a7a687a-1c-48-53-1b-73-78 not good
source.joinToString("") { it.toInt().and(0xff).toString(16).padStart(2, '0') }.apply(::println)
//7a7a687ae4b8ade58d88 good
toHEX(source).apply(::println)
//7a7a687ae4b8ade58d88 good
toHEX2(source).apply(::println)
//7a7a687ae4b8ade58d88 good
mapOf(
"toHEX" to { toHEX(source) },
"toHEX2" to { toHEX2(source) },
"format" to { source.joinToString("") { String.format("%02x", it) } },
"pad" to { source.joinToString("") { it.toInt().and(0xff).toString(16).padStart(2, '0') } }
).forEach { o, a -> measureOp(o, a) }
and result show
toHEX: 0.018858339749999998 ms/op/byte
toHEX2: 0.012625058 ms/op/byte
format: 0.723910916375 ms/op/byte
pad: 0.042981906875 ms/op/byte
current speed winner is
{ b: ByteArray ->
buildString {
b.forEach {
val octet = it.toInt()
val firstIndex = (octet and 0xF0).ushr(4)
val secondIndex = octet and 0x0F
append(HEX_CHARS[firstIndex])
append(HEX_CHARS[secondIndex])
}
}
}
and avg winner is
source.joinToString("") { it.toInt().and(0xff).toString(16).padStart(2, '0') }
recover test
@Test
fun fromHEX() {
val hex = "7a7a687ae4b8ade58d88"
val f1 = { hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray() }
val f2 = {
val len = hex.length
val result = ByteArray(len / 2)
for (i in 0 until len step 2) {
val firstIndex = HEX_CHARS.indexOf(hex[i])
val secondIndex = HEX_CHARS.indexOf(hex[i + 1])
val octet = firstIndex.shl(4).or(secondIndex)
result[i.shr(1)] = octet.toByte()
}
result
}
val f3 = {
val len = hex.length
val result = ByteArray(len / 2)
(0 until len step 2).forEach { i ->
val firstIndex = HEX_CHARS.indexOf(hex[i])
val secondIndex = HEX_CHARS.indexOf(hex[i + 1])
val octet = firstIndex.shl(4).or(secondIndex)
result[i.shr(1)] = octet.toByte()
}
result
}
val f4 = {
val len = hex.length
val result = ByteArray(len / 2)
(0 until len step 2).forEach { i ->
result[i.shr(1)] = HEX_CHARS.indexOf(hex[i]).shl(4).or(HEX_CHARS.indexOf(hex[i + 1])).toByte()
}
result
}
val f5 = {
(hex.indices step 2).map { i ->
HEX_CHARS.indexOf(hex[i]).shl(4).or(HEX_CHARS.indexOf(hex[i + 1])).toByte()
}.toByteArray()
}
f1().apply(print)
f2().apply(print)
f3().apply(print)
f4().apply(print)
f5().apply(print)
mapOf(
"f1" to f1
, "f2" to f2
, "f3" to f3
, "f4" to f4
, "f5" to f5
).forEach { o, a -> measureOp(o, a) }
}
f1: 0.480646209 ms/op
f2: 0.126997772 ms/op
f3: 0.113088663 ms/op
f4: 0.098126381 ms/op
f5: 0.201921568 ms/op
For people commenting alternatives to the gist, bear in mind that the gist is in Kotlin. The API does not have String.format
, as this is from the Kotlin JVM lib. The posted examples won't work in Kotlin Native.
For people commenting alternatives to the gist, bear in mind that the gist is in Kotlin. The API does not have
String.format
, as this is from the Kotlin JVM lib. The posted examples won't work in Kotlin Native.
source.joinToString("") { it.toInt().and(0xff).toString(16).padStart(2, '0') }
works with Kotlin/Native at least for the targets JVM, Android, iOS & macOS as expected
probably not the best performance, but if you are looking for one-liners: