/* * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.util; import java.io.*; import java.nio.*; import java.util.*; import java.util.stream.*; import static java.nio.charset.StandardCharsets.*; /** * Converts binary data to and from its hexadecimal (base 16) string * representation. It can also generate the classic Unix {@code hexdump(1)} * format. *
* Example usages: *
{@code // Initialize a 16-byte array from a hexadecimal string * byte[] bytes = HexFormat.fromString("a1a2a3a4a5a6a7a8a9aaabacadaeaf"); * * // Display the hexadecimal representation of a file's 256-bit hash code * MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); * System.out.println( * HexFormat.toString(sha256.digest(Files.readAllBytes(Paths.get("mydata"))))); * * // Write the printable representation of a file to the standard output stream * // in 64-byte chunks formatted according to the supplied Formatter function * try (InputStream is = Files.newInputStream(Paths.get("mydata"))) { * HexFormat.dumpAsStream(is, 64, * (offset, chunk, fromIndex, toIndex) -> * String.format("%d %s", * offset / 64 + 1, * HexFormat.toPrintableString(chunk, fromIndex, toIndex))) * .forEachOrdered(System.out::println); * } catch (IOException ioe) { * ... * } * * // Write the standard input stream to the standard output stream in hexdump format * HexFormat.dump(System.in, System.out); * }* * @since 12 */ public final class HexFormat { private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); private static final String NEWLINE = System.lineSeparator(); private static final int NEWLINE_LENGTH = NEWLINE.length(); private static final int DEFAULT_CHUNK_SIZE = 16; /** * A formatter that generates the classic Unix {@code hexdump(1)} format. * It behaves as if: *
{@code * String.format("%08x %s |%s|", * offset, * HexFormat.toFormattedString(chunk, from, to), * HexFormat.toPrintableString(chunk, from, to)); * }*/ public static final Formatter HEXDUMP_FORMATTER = new Formatter() { public String format(long offset, byte[] chunk, int fromIndex, int toIndex) { return String.format("%08x %s |%s|", offset, HexFormat.toFormattedString(chunk, fromIndex, toIndex), HexFormat.toPrintableString(chunk, fromIndex, toIndex)); } }; private HexFormat() {} /** * Returns a hexadecimal string representation of the contents of the * provided byte array, with no additional formatting. *
* The binary value is converted to a string comprising pairs of * hexadecimal digits that use only the following ASCII characters: *
* {@code 0123456789abcdef} ** * @param bytes a byte array * @return a hexadecimal string representation of the byte array. * The string length is twice the array length. * @throws NullPointerException if {@code bytes} is {@code null} */ public static String toString(byte[] bytes) { Objects.requireNonNull(bytes, "bytes"); return toString(bytes, 0, bytes.length); } /** * Returns a hexadecimal string representation of a range within the * provided byte array, with no additional formatting. *
* The binary value is converted to a string comprising pairs of * hexadecimal digits that use only the following ASCII characters: *
* {@code 0123456789abcdef} ** The range to be converted extends from index {@code fromIndex}, * inclusive, to index {@code toIndex}, exclusive. * If {@code fromIndex==toIndex}, the range to be converted is empty. * * @param bytes a byte array * @param fromIndex the index of the first byte (inclusive) to be converted * @param toIndex the index of the last byte (exclusive) to be converted * @return a hexadecimal string representation of the byte array. * The string length is twice the number of bytes converted. * @throws NullPointerException if {@code bytes} is {@code null} * @throws IllegalArgumentException if {@code fromIndex > toIndex} * @throws ArrayIndexOutOfBoundsException * if {@code fromIndex < 0} or {@code toIndex > bytes.length} */ public static String toString(byte[] bytes, int fromIndex, int toIndex) { Objects.requireNonNull(bytes, "bytes"); Arrays.rangeCheck(bytes.length, fromIndex, toIndex); return toChunkedString(bytes, fromIndex, toIndex, toIndex - fromIndex, 1, false); } /** * Returns a formatted hexadecimal string representation of the contents of * the provided byte array. *
* The binary value is converted to a string in the canonical hexdump * format of two columns of eight space-separated pairs of hexadecimal * digits that use only the following ASCII characters: *
* {@code 0123456789abcdef} **
* If the number of bytes to be converted is greater than 16 then * {@link System#lineSeparator()} characters are inserted after each 16-byte chunk. * If the final chunk is less than 16 bytes then the result is padded with spaces * to match the length of the preceding chunks. * The general output format is as follows: *
* 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ** * @param bytes a byte array * @return a formatted hexadecimal string representation of the byte array * @throws NullPointerException if {@code bytes} is {@code null} */ public static String toFormattedString(byte[] bytes) { Objects.requireNonNull(bytes, "bytes"); return toFormattedString(bytes, 0, bytes.length); } /** * Returns a formatted hexadecimal string representation of the contents of * a range within the provided byte array. *
* The binary value is converted to a string in the canonical hexdump * format of two columns of eight space-separated pairs of hexadecimal * digits that use only the following ASCII characters: *
* {@code 0123456789abcdef} **
* The range to be converted extends from index {@code fromIndex}, * inclusive, to index {@code toIndex}, exclusive. * If {@code fromIndex==toIndex}, the range to be converted is empty. *
* If the number of bytes to be converted is greater than 16 then * {@link System#lineSeparator()} characters are inserted after each 16-byte chunk. * If the final chunk is less than 16 bytes then the result is padded with spaces * to match the length of the preceding chunks. * The general output format is as follows: *
* 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ** * @param bytes a byte array * @param fromIndex the index of the first byte (inclusive) to be converted * @param toIndex the index of the last byte (exclusive) to be converted * @return a formatted hexadecimal string representation of the byte array * @throws NullPointerException if {@code bytes} is {@code null} * @throws IllegalArgumentException if {@code fromIndex > toIndex} * @throws ArrayIndexOutOfBoundsException * if {@code fromIndex < 0} or {@code toIndex > bytes.length} */ public static String toFormattedString(byte[] bytes, int fromIndex, int toIndex) { Objects.requireNonNull(bytes, "bytes"); Arrays.rangeCheck(bytes.length, fromIndex, toIndex); return toChunkedString(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, 2, true); } /** * Returns a printable representation of the contents of the * provided byte array. *
* The binary value is converted to a string comprising printable * {@link java.nio.charset.StandardCharsets#ISO_8859_1} * characters, or {@code '.'} if the byte maps to a non-printable character. * A non-printable character is one outside of the range * {@code '\u005Cu0020'} through {@code '\u005Cu007E'} and * {@code '\u005Cu00A0'} through {@code '\u005Cu00FF'}. * * @param bytes a byte array * @return a printable representation of the byte array * @throws NullPointerException if {@code bytes} is {@code null} */ public static String toPrintableString(byte[] bytes) { Objects.requireNonNull(bytes, "bytes"); return toPrintableString(bytes, 0, bytes.length); } /** * Returns a printable representation of the contents of a * range within the provided byte array. *
* The binary value is converted to a string comprising printable * {@link java.nio.charset.StandardCharsets#ISO_8859_1} * characters, or {@code '.'} if the byte maps to a non-printable character. * A non-printable character is one outside of the range * {@code '\u005Cu0020'} through {@code '\u005Cu007E'} and * {@code '\u005Cu00A0'} through {@code '\u005Cu00FF'}. * * @param bytes a byte array * @param fromIndex the index of the first byte (inclusive) to be converted * @param toIndex the index of the last byte (exclusive) to be converted * @return a printable representation of the byte array * @throws NullPointerException if {@code bytes} is {@code null} * @throws IllegalArgumentException if {@code fromIndex > toIndex} * @throws ArrayIndexOutOfBoundsException * if {@code fromIndex < 0} or {@code toIndex > bytes.length} */ public static String toPrintableString(byte[] bytes, int fromIndex, int toIndex) { Objects.requireNonNull(bytes, "bytes"); Arrays.rangeCheck(bytes.length, fromIndex, toIndex); StringBuilder printable = new StringBuilder(toIndex - fromIndex); for (int i = fromIndex; i < toIndex; i++) { if (bytes[i] > 0x1F && bytes[i] < 0x7F) { printable.append((char) bytes[i]); } else if (bytes[i] > (byte)0x9F && bytes[i] <= (byte)0xFF) { printable.append((char) (bytes[i] & 0xFF)); } else { printable.append('.'); } } return printable.toString(); } /** * Returns a byte array containing the provided sequence of hexadecimal * digits. The sequence may be prefixed with the hexadecimal indicator * {@code "0x"}. * The optional prefix of {@code "0x"} is ignored. *
* The binary value is generated from pairs of hexadecimal digits that use * only the following ASCII characters: *
* {@code 0123456789abcdefABCDEF} ** * @param hexString an even numbered sequence of hexadecimal digits. * If this is a {@link CharBuffer} then its position does not get * advanced. * @return a byte array * @throws IllegalArgumentException if {@code hexString} has an odd number * of digits or contains an illegal hexadecimal character * @throws NullPointerException if {@code hexString} is {@code null} */ public static byte[] fromString(CharSequence hexString) { Objects.requireNonNull(hexString, "hexString"); return hexToBytes(hexString, 0, hexString.length()); } /** * Returns a byte array containing a range within the provided * sequence of hexadecimal digits. The sequence may be prefixed with the * hexadecimal indicator {@code "0x"}. * The optional prefix of {@code "0x"} is ignored. *
* The binary value is generated from pairs of hexadecimal digits that use * only the following ASCII characters: *
* {@code 0123456789abcdefABCDEF} ** * @param hexString an even numbered sequence of hexadecimal digits. * If this is a {@link CharBuffer} then its position does not get * advanced. * @param fromIndex the index of the first digit (inclusive) to be converted * @param toIndex the index of the last digit (exclusive) to be converted * @return a byte array * @throws IllegalArgumentException if the specified range of {@code hexString} * has an odd number of digits or contains an illegal hexadecimal character, * or if {@code fromIndex > toIndex} * @throws NullPointerException if {@code hexString} is {@code null} * @throws ArrayIndexOutOfBoundsException * if {@code fromIndex < 0} or {@code toIndex > hexString.length()} */ public static byte[] fromString(CharSequence hexString, int fromIndex, int toIndex) { Objects.requireNonNull(hexString, "hexString"); Arrays.rangeCheck(hexString.length(), fromIndex, toIndex); return hexToBytes(hexString, fromIndex, toIndex); } /** * Generates a dump of the contents of the provided input stream, as a * stream of hexadecimal strings in hexdump format. * This method outputs the same format as * {@link #dump(byte[],PrintStream)}, * without the {@link System#lineSeparator()} characters. *
* If the input is not a multiple of 16 bytes then the final chunk will * be shorter than the preceding chunks. The result will be padded with * spaces to match the length of the preceding chunks. *
* On return, the generated stream lazily consumes the input stream. * This method does not close the input stream and may block indefinitely * reading from it. The behavior for the case where it is * asynchronously closed, or the thread interrupted, * is highly input stream specific, and therefore not specified. *
* If an I/O error occurs reading from the input stream then it may not be
* at end-of-stream and may be in an inconsistent state. It is strongly
* recommended that the input stream be promptly closed if an I/O error
* occurs.
*
* @param in the input stream, non-null
* @return a new sequential ordered stream of hexadecimal strings
* @throws NullPointerException if {@code in} is {@code null}
*/
public static Stream
* On return, the generated stream lazily consumes the input stream.
* This method does not close the input stream and may block indefinitely
* reading from it. The behavior for the case where it is
* asynchronously closed, or the thread interrupted,
* is highly input stream specific, and therefore not specified.
*
* If an I/O error occurs reading from the input stream then it may not be
* at end-of-stream and may be in an inconsistent state. It is strongly
* recommended that the input stream be promptly closed if an I/O error
* occurs.
*
* If an error occurs in the {@code formatter} then an unchecked exception
* will be thrown from the underlying {@code Stream} method.
*
* @param in the input stream, non-null
* @param chunkSize the number of bytes-per-chunk (typically 16)
* @param formatter a hexdump formatting function, or {@code null}
* @return a new sequential ordered stream of hexadecimal strings
* @throws IllegalArgumentException if {@code chunkSize <= 0}
* @throws NullPointerException if {@code in} is {@code null}
*/
public static Stream
* If the input is not a multiple of 16 bytes then the final chunk will
* be shorter than the preceding chunks. The result will be padded with
* spaces to match the length of the preceding chunks.
*
* @param bytes a byte array, assumed to be unmodified during use
* @return a new sequential ordered stream of hexadecimal strings
* @throws NullPointerException if {@code bytes} is {@code null}
*/
public static Stream
* The range to be converted extends from index {@code fromIndex},
* inclusive, to index {@code toIndex}, exclusive.
* If {@code fromIndex==toIndex}, the range to be converted is empty.
* If the input is not a multiple of {@code chunkSize} then the final chunk
* will be shorter than the preceding chunks. The result may be padded with
* spaces to match the length of the preceding chunks.
*
* If an error occurs in the {@code formatter} then an unchecked exception
* will be thrown from the underlying {@code Stream} method.
*
* @param bytes a byte array, assumed to be unmodified during use
* @param fromIndex the index of the first byte (inclusive) to be converted
* @param toIndex the index of the last byte (exclusive) to be converted
* @param chunkSize the number of bytes-per-chunk (typically 16)
* @param formatter a hexdump formatting function, or {@code null}
* @return a new sequential ordered stream of hexadecimal strings
* @throws NullPointerException if {@code bytes} is {@code null}
* @throws IllegalArgumentException if {@code fromIndex > toIndex}
* or {@code chunkSize <= 0}
* @throws ArrayIndexOutOfBoundsException
* if {@code fromIndex < 0} or {@code toIndex > bytes.length}
*/
public static Stream
* If the input is not a multiple of {@code chunkSize} then the final chunk
* will be shorter than the preceding chunks. The result will be padded with
* spaces to match the length of the preceding chunks.
*
* Access to the ByteBuffer is relative and its position gets advanced to
* the buffer's limit.
*
* @param buffer a byte buffer, assumed to be unmodified during use
* @return a new sequential ordered stream of hexadecimal strings
* @throws NullPointerException if {@code buffer} is {@code null}
*/
public static Stream
* If the input is not a multiple of {@code chunkSize} then the final chunk
* will be shorter than the preceding chunks. The result may be padded with
* spaces to match the length of the preceding chunks.
*
* Access to the ByteBuffer is relative and its position gets advanced to
* the buffer's limit.
*
* If an error occurs in the {@code formatter} then an unchecked exception
* will be thrown from the underlying {@code Stream} method.
*
* @param buffer a byte buffer, assumed to be unmodified during use
* @param chunkSize the number of bytes-per-chunk (typically 16)
* @param formatter a hexdump formatting function, or {@code null}
* @return a new sequential ordered stream of hexadecimal strings
* @throws IllegalArgumentException if {@code chunkSize <= 0}
* @throws NullPointerException if {@code buffer} is {@code null}
*/
public static Stream
* This method does not close the output stream and may block indefinitely
* writing to it. The behavior for the case where it is
* asynchronously closed, or the thread interrupted,
* is highly output stream specific, and therefore not specified.
*
* If an I/O error occurs writing to the output stream, then it may be
* in an inconsistent state. It is strongly recommended that the output
* stream be promptly closed if an I/O error occurs.
*
* @param bytes the byte array, assumed to be unmodified during use
* @param out the output stream, non-null
* @throws IOException if an I/O error occurs when writing
* @throws NullPointerException if {@code bytes} or {@code out} is
* {@code null}
*/
public static void dump(byte[] bytes, PrintStream out) throws IOException {
Objects.requireNonNull(bytes, "bytes");
dump(bytes, 0, bytes.length, out);
}
/**
* Generates a hexadecimal dump of the contents of a range within the
* provided byte array and writes it to the provided output stream.
* This method outputs the same format as
* {@link #dump(byte[],PrintStream)}.
*
* The range to be converted extends from index {@code fromIndex},
* inclusive, to index {@code toIndex}, exclusive.
* If {@code fromIndex==toIndex}, the range to be converted is empty.
*
* This method does not close the output stream and may block indefinitely
* writing to it. The behavior for the case where it is
* asynchronously closed, or the thread interrupted,
* is highly output stream specific, and therefore not specified.
*
* If an I/O error occurs writing to the output stream, then it may be
* in an inconsistent state. It is strongly recommended that the output
* stream be promptly closed if an I/O error occurs.
*
* @param bytes the byte array, assumed to be unmodified during use
* @param fromIndex the index of the first byte (inclusive) to be converted
* @param toIndex the index of the last byte (exclusive) to be converted
* @param out the output stream, non-null
* @throws IOException if an I/O error occurs when writing
* @throws NullPointerException if {@code bytes} or {@code out} is
* {@code null}
* @throws IllegalArgumentException if {@code fromIndex > toIndex}
* @throws ArrayIndexOutOfBoundsException
* if {@code fromIndex < 0} or {@code toIndex > bytes.length}
*/
public static void dump(byte[] bytes, int fromIndex, int toIndex,
PrintStream out) throws IOException {
dumpAsStream(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, null)
.forEachOrdered(out::println);
}
/**
* Generates a hexadecimal dump of the contents of the provided input stream
* and writes it to the provided output stream.
* This method outputs the same format as
* {@link #dump(byte[],PrintStream)}.
*
* Reads all bytes from the input stream.
* On return, the input stream will be at end-of-stream. This method does
* not close either stream and may block indefinitely reading from the
* input stream, or writing to the output stream. The behavior for the case
* where the input and/or output stream is asynchronously closed,
* or the thread interrupted, is highly input stream and output stream
* specific, and therefore not specified.
*
* If an I/O error occurs reading from the input stream or writing to the
* output stream, then it may do so after some bytes have been read or
* written. Consequently the input stream may not be at end-of-stream and
* one, or both, streams may be in an inconsistent state. It is strongly
* recommended that both streams be promptly closed if an I/O error occurs.
*
* @param in the input stream, non-null
* @param out the output stream, non-null
* @throws IOException if an I/O error occurs when reading or writing
* @throws NullPointerException if {@code in} or {@code out} is {@code null}
*/
public static void dump(InputStream in, PrintStream out)
throws IOException {
dumpAsStream(in, DEFAULT_CHUNK_SIZE, null)
.forEachOrdered(out::println);
}
/**
* Generates a hexadecimal dump of the contents of the provided ByteBuffer
* and writes it to the provided output stream.
* This method outputs the same format as
* {@link #dump(byte[],PrintStream)}.
*
* This method does not close the output stream and may block indefinitely
* writing to it. The behavior for the case where it is
* asynchronously closed, or the thread interrupted,
* is highly output stream specific, and therefore not specified.
*
* If an I/O error occurs writing to the output stream, then it may be
* in an inconsistent state. It is strongly recommended that the output
* stream be promptly closed if an I/O error occurs.
*
* @param buffer a byte buffer, assumed to be unmodified during use
* @param out the output stream, non-null
* @throws IOException if an I/O error occurs when writing
* @throws NullPointerException if {@code buffer} is {@code null}
*/
public static void dump(ByteBuffer buffer, PrintStream out)
throws IOException {
dumpAsStream(buffer, DEFAULT_CHUNK_SIZE, null)
.forEachOrdered(out::println);
}
// Returns a hexadecimal string formatted according to the specified number
// of columns and with/without space separators between pairs of hexadecimal
// digits. Newlines are added when the chunkSize is exceeded. If the final
// line is less than chunkSize then it is padded with spaces.
private static String toChunkedString(byte[] bytes, int fromIndex,
int toIndex, int chunkSize, int columns, boolean useSeparators) {
int range = toIndex - fromIndex;
if (range == 0) {
return "";
}
int columnWidth = chunkSize / columns;
int lineLength = useSeparators
? chunkSize * 3 + (columns - 1) - 1
: chunkSize * 2 + (columns - 1);
StringBuilder hexString =
new StringBuilder(lineLength + lineLength * (range / chunkSize));
int position = 1;
int newlineCount = 0;
for (int i = fromIndex; i < toIndex; i++, position++) {
// add the pair of hex. digits
hexString.append(HEX_DIGITS[(bytes[i] >> 4) & 0xF]);
hexString.append(HEX_DIGITS[(bytes[i] & 0xF)]);
// add a space between pairs of hex. digits
if (useSeparators && position != chunkSize) {
hexString.append(' ');
}
// add a space between columns
if (position % columnWidth == 0 && position != chunkSize) {
hexString.append(' ');
}
// handle end-of-line
if (position == chunkSize && (i + 1 < toIndex)) {
hexString.append(NEWLINE);
newlineCount++;
position = 0;
}
}
// add final line padding, if needed
if (position <= chunkSize) {
int len = hexString.length() - (newlineCount * NEWLINE_LENGTH);
for (int i = len % lineLength; i < lineLength; i++) {
hexString.append(' ');
}
}
return hexString.toString();
}
private static byte[] hexToBytes(CharSequence hexString, int fromIndex,
int toIndex) {
int len = toIndex - fromIndex;
if (len % 2 != 0) {
throw new IllegalArgumentException(
"contains an odd number of digits: " + hexString);
}
// Skip the '0x' prefix, if present
if (len > 2 &&
hexString.charAt(fromIndex) == '0' &&
hexString.charAt(fromIndex + 1) == 'x') {
fromIndex += 2;
len -= 2;
}
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int hexIndex = fromIndex + i;
int high = Character.digit(hexString.charAt(hexIndex), 16);
int low = Character.digit(hexString.charAt(hexIndex + 1), 16);
if (high == -1 || low == -1) {
throw new IllegalArgumentException(
"contains an illegal hexadecimal character: '" +
(high == -1 ? hexString.charAt(hexIndex) : hexString.charAt(hexIndex + 1))
+ "'");
}
bytes[i / 2] = (byte) (high * 16 + low);
}
return bytes;
}
private static int roundUp(int total, int chunkSize) {
return (total + chunkSize - 1) / chunkSize;
}
private static byte[] readChunk(InputStream inStream, int chunkSize)
throws IOException {
byte[] buffer = new byte[chunkSize];
int n = inStream.readNBytes(buffer, 0, buffer.length);
if (n == 0) {
return null;
} else if (n < chunkSize) {
return Arrays.copyOf(buffer, n);
} else {
return buffer;
}
}
/**
* Represents a function that formats a byte array as a hexadecimal
* string.
*
* This is a functional interface
* whose functional method is
* {@link #format}.
*
* @see java.util.function.Function
* @since 12
*/
@FunctionalInterface
public interface Formatter {
/**
* Returns a formatted hexadecimal string representation of the contents
* of a chunk within a byte array.
*
* @param offsetField is the offset into the byte array
* @param chunk a byte array
* @param fromIndex the index of the first byte (inclusive) of the
* chunk to be converted
* @param toIndex the index of the last byte (exclusive) of the
* chunk to be converted
* @return a hexadecimal string representation of a chunk of the byte
* array
* @throws NullPointerException if {@code chunk} is {@code null}
* @throws IllegalArgumentException if {@code fromIndex > toIndex}
* @throws ArrayIndexOutOfBoundsException
* if {@code fromIndex < 0} or {@code toIndex > chunk.length}
*/
String format(long offsetField, byte[] chunk, int fromIndex, int toIndex);
}
}
{@code
* byte[] bytes = ...
* PrintStream out = ...
* HexFormat.dumpAsStream(bytes, 16,
* (offset, chunk, from, to) ->
* String.format("%08x %s |%s|",
* offset,
* HexFormat.toFormattedString(chunk, from, to),
* HexFormat.toPrintableString(chunk, from, to)))
* .forEachOrdered(out::println);
* }
*