将长整型转换为“字节数组格式”的文本,避免使用堆分配。

3
我想将一个正的原始长整型转换为它的文本形式下的 byte[],在Java中。
例如,一种简单的方法是:123 => "123" => [49, 50, 51] 然而,将长整型转换为 String 会分配一个堆内存的 String,而我想避免这种情况,因为我正在使用无GC库。我宁愿直接在预分配的字节数组上输出结果。
因此,我的问题是如何直接从长整型转换为 byte[] 表示形式,如果我将其传递给 String 构造函数,则(对于上面的示例)会给我 "123"。为了澄清,我不是要将长整型编码为二进制形式的 byte[],而是文本形式。
谢谢! N

2
这是一个不错的问题。使用哪个字符集? - Sotirios Delimanolis
这与我的使用情况无关,我们可以说是ASCII。 - NT_
1
@SotiriosDelimanolis 并没有太多字符集将数字 0...9 映射到代码 48...57 - biziclop
1
我是疯了还是字符集在这里不是特别重要?你将用这个 byte[] 做什么?写入文件吗?你还会写其他东西到那个文件吗?你将如何读取它? - Sotirios Delimanolis
@Boann 避免在示例中预分配字节数组是微不足道的,我理解这只是为了说明问题。 - NT_
显示剩余2条评论
5个回答

4
这个简单的程序:
long l = 123;
int size = (int)(Math.log10(l)+1);
byte[] array = new byte[size];
for (int i = 0; i < size; i++) {
    long temp = (long) Math.pow(10, size - i - 1);
    array[i] = (byte) ((l / temp) + 48);
    l = l % temp;
}
System.out.println(Arrays.toString(array)); // [49, 50, 51]

我还测试了当前时间(以毫秒为单位),结果也和预期一样:

long l = System.currentTimeMillis();
...
System.out.println(Arrays.toString(array));

输出:

1406485727149
[49, 52, 48, 54, 52, 56, 53, 55, 50, 55, 49, 52, 57]

1
不过这并不是 OP 需要的。此外,使用 Math.pow() 来寻找 10 的幂次方并不是最好的选择,尽管它可行。 - biziclop
2
int size = (int) l % 10; 这是错误的。它只适用于个位数与数字位数相同的数字。 - Eran
1
@Eran 不,我已经测试了1234、12345、123456、1234567等等,结果符合预期。 - Braj
2
@user3218114 尝试输入 1230。1234 可以工作,因为它有 4 个数字并以 4 结尾。 - Eran
1
@Eran 感谢您抽出宝贵的时间。现在使用 int size = (int)(Math.log10(l)+1); 已经可以工作了。 - Braj
显示剩余5条评论

3
为了不使用byte[]或者使用相当昂贵的Math.pow和Math.log函数,你可以采用以下方法来编写代码。
public static void toStream(OutputStream os, long l) throws IOException {
    toStream0(os, l / 10);
    os.write((int) ('0' + l % 10));
}

private static void toStream0(OutputStream os, long l) throws IOException {
    if (l == 0) return;
    toStream0(os, l / 10);
    os.write((int) ('0' + l % 10));
}

toStream(System.out, System.currentTimeMillis());

打印

1406486588664

如果你在写入 ByteBuffer,使用 put(,或者如果是 StringBuilder 就使用 append((char)

你可以考虑使用更大的基数来使数字变小。

base 36: hy4p6ugr
base 16: 147791288bb
base 10: 1406485563579

+1 感谢您对OutputStream方法和基本编码节省的解释! - NT_
1
对于 StringBuilder,您可以使用 append((char) ...,而对于 ByteBuffer,则使用 put(... 而不是 write(... - Peter Lawrey
1
使用递归解决问题也是不错的选择。 - biziclop

2
我想这就是你要求的内容吧(?):
static int toChars(long num, byte[] buffer) {
    if (num < 0) throw new IllegalArgumentException();
    int length = 0;
    do {
        buffer[length++] = (byte)(num % 10 + '0');
    } while ((num /= 10) != 0);
    for (int i = 0, mid = length >> 1, j = length - 1; i < mid; i++, j--) {
        byte tmp = buffer[i];
        buffer[i] = buffer[j];
        buffer[j] = tmp;
    }
    return length;
}

使用方法:

byte[] buffer = new byte[19]; // 19 = enough for everything up to Long.MAX_VALUE

int length = toChars(51239827493284L, buffer);

System.out.println(new String(buffer, 0, length,
    java.nio.charset.StandardCharsets.US_ASCII));

我认为OP想要避免创建任何对象。你能否在不使用new(或使用new的方法)的情况下编写这个代码? - Peter Lawrey
@PeterLawrey:buffer 可以通过静态使用并重复利用来避免 new。而 String 无法避免 new,因为它们是不可变的。 - Boann
你不需要创建一个字符串来将byte[]写入OutputStream ;) - Peter Lawrey
@PeterLawrey 这个问题字面上要求一个东西,“如果我使用String构造函数调用它,会(对于上面的例子)给我“123””。我演示了如何做到这一点。 - Boann

1
据我所见,这并没有什么诀窍。

public class Test {

    public static void main(String[] args) {
        byte [] result = new byte[6];
        convert( 123456, result );
        System.out.println( new String( result ) );
    }

    public static void convert( long n, byte [] array ) {
        for (int i = size( n ) - 1; i >= 0; --i) {
            array[i] = (byte)(n % 10 + '0');
            n = n / 10;
        }
    }

    public static int size( long n ) {
        int ret = 0;
        while (n > 0) {
            ret++;
            n /= 10;
        }
        return ret;
    }

}

请注意,这仅适用于正数,并且不检查数组是否足够大以容纳数字,在实际代码中应该同时进行。它也无法处理n=0的简单情况。
您可以通过从后往前填充数组,然后翻转它来避免使用size()方法,但我不确定它在性能方面会带来任何好处,而且阅读起来会很困难。

1
你可以预分配一个长度为最长long的byte[],并将long转换为字符串存入该数组中。
当然,如果你不想浪费20个字符用于数字17,你可以快速计算出long中的字符数(例如,看它是否大于10^18,如果大于则继续判断是否大于10^17等等...你可以对数字位数进行二分搜索)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接