0

I am looking for a way to read hex strings from a file line by line and append them as converted bytes to some ByteBuffer.

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

Files.lines(filePath).foreach( l -> 

        byteBuffer.put(
            // first of all strip newlines and normalize string
            l.replaceAll("/\n|\r/g", "").toUpperCase()

            // but what to do here?
            // is there something like
            //   take next 2 characters (-> Consumer)
            //   and replace them with the converted byte?
            //     E.g. "C8" -> 0xC8
            //   until the end of the string is reached
        )

);

This has been answered a million time. But I wondered if there is a solution using streams like returned by Files.lines().

Generally I like this answer. Can anybody help me about translating that into java-8 stream-based solution or completing my example from above?

Thank you!

qdbp
  • 109
  • 1
  • 1
  • 12

2 Answers2

1

You can use a utility method to parse the line as a hex string to a byte array:

public static byte[] hexStringToByteArray(String str) {
    if(str.startsWith("0x")) { // Get rid of potential prefix
        str = str.substring(2);
    }

    if(str.length() % 2 != 0) { // If string is not of even length
        str = '0' + str; // Assume leading zeroes were left out
    }

    byte[] result = new byte[str.length() / 2];
    for(int i = 0; i < str.length(); i += 2) {
        String nextByte = str.charAt(i) + "" + str.charAt(i + 1);
        // To avoid overflow, parse as int and truncate:
        result[i / 2] = (byte) Integer.parseInt(nextByte, 16);
    }
    return result;
}

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

Files.lines(filePath).forEach( l -> 
    byteBuffer.put(
        hexStringToByteArray(l.replaceAll("/\n|\r/g", "").toUpperCase())
    )
);
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
1

This looks a bit like an xy problem, as reading the file “line by line” is already part of your attempted solution while you actual task does not include any requirement to read the file “line by line”.

Actually, you want to process all hexadecimal numbers of the source, regardless of the line terminators, which is a job for java.util.Scanner. It also allows to process the items using the Stream API, though this specific task does not benefit much from it, compared to a loop:

ByteBuffer bb = ByteBuffer.allocate(1024);
try(Scanner s = new Scanner(yourFile)) {
    s.findAll("[0-9A-Fa-f]{2}")
     .mapToInt(m -> Integer.parseInt(m.group(), 16))
     .forEachOrdered(i -> { if(bb.hasRemaining()) bb.put((byte)i); });
}
try(Scanner s = new Scanner(yourFile)) {
    Pattern p = Pattern.compile("[0-9A-Fa-f]{2}");
    for(;;) {
        String next = s.findWithinHorizon(p, 0);
        if(next == null) break;
        if(!bb.hasRemaining()) // the thing hard to do with Stream API
            bb = ByteBuffer.allocate(bb.capacity()*2).put(bb.flip());
        bb.put((byte)Integer.parseInt(next, 16));
    }
}

Note that these examples use Java 9. In Java 8, the Buffer returned by Buffer.flip() needs a type cast back to ByteBuffer and Scanner.findAll is not available but has to be replaced by a back-port like the one in this answer.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • what is the use of `findWithinHorizon` with a zero? – Eugene May 04 '18 at 13:51
  • @Eugene as [the documentation](https://docs.oracle.com/javase/8/docs/api/java/util/Scanner.html#findWithinHorizon-java.util.regex.Pattern-int-) states: “*If horizon is `0`, then the horizon is ignored and this method continues to search through the input looking for the specified pattern without bound*”. – Holger May 04 '18 at 16:39
  • right, so why not find only? Seems like Im missing something – Eugene May 04 '18 at 17:08
  • @Eugene there is no “find only” in `Scanner`. There’s `next(pattern)`, but it requires the tokens to be separated by matches of the delimiter pattern, so it’s not semantically identical. – Holger May 05 '18 at 22:14