1. Overview
1.概述
When we get the size of a file in Java, usually, we’ll get the value in bytes. However, once a file is large enough, for example, 123456789 bytes, seeing the length expressed in bytes becomes a challenge for us trying to comprehend how big the file is.
当我们在Java中获取一个文件的大小时,通常我们会得到以字节为单位的值。然而,一旦文件足够大,例如123456789字节,看到以字节为单位的长度就会成为我们试图理解该文件有多大的一个挑战。
In this tutorial, we’ll explore how to convert file size in bytes into a human-readable format in Java.
在本教程中,我们将探讨如何在Java中把以字节为单位的文件大小转换成人类可读的格式。
2. Introduction to the Problem
2.对问题的介绍
As we’ve talked about earlier, when the size of a file in bytes is large, it’s not easy to understand for humans. Therefore, when we present an amount of data to humans, we often use a proper SI prefix, such as KB, MB, GB, and so on, to make a large number human-readable. For example, “270GB” is much easier to understand than “282341192 Bytes”.
正如我们前面谈到的,当一个文件的大小以字节为单位时,它对人类来说是不容易理解的。因此,当我们向人类展示一个数据量时,我们通常会使用一个适当的SI前缀,如KB、MB、GB等,以使一个大数字能够被人类理解。例如,”270GB “就比 “282341192字节 “容易理解得多。
However, when we get a file size through the standard Java API, usually, it’s in bytes. So, to have the human-readable format, we need to dynamically convert the value from the byte unit to the corresponding binary prefix, for example, converting “282341192 bytes” to “207MiB”, or converting “2048 bytes” to “2KiB”.
然而,当我们通过标准的Java API获得文件大小时,通常,它的单位是字节。因此,为了拥有人类可读的格式,我们需要动态地将数值从字节单位转换为相应的二进制前缀,例如,将 “282341192字节 “转换为 “207MiB”,或将 “2048字节 “转换为 “2KiB”。
It’s worth mentioning that there are two variants of the unit prefixes:
值得一提的是,单位的前缀有两种变体。
- Binary Prefixes – They are the powers of 1024; for example, 1MiB = 1024 KiB, 1GiB = 1024 MiB, and so on
- SI (International System of Units) Prefixes – They are the powers of 1000; for example, 1MB = 1000 KB, 1GB = 1000 MB, and so on.
Our tutorial will focus on both binary prefixes and SI prefixes.
我们的教程将侧重于二进制前缀和SI前缀。
3. Solving the Problem
3.解决问题
We may have already realized that the key to solving the problem is finding the suitable unit dynamically.
我们可能已经意识到,解决问题的关键是动态地找到合适的单位。。
For example, if the input is less than 1024, say 200, then we need to take the byte unit to have “200 Bytes”. However, when the input is greater than 1024 but less than 1024 * 1024, for instance, 4096, we should use the KiB unit, so we have “4 KiB”.
例如,如果输入小于1024,比如说200,那么我们就需要采用字节单位来得到 “200字节”。然而,当输入大于1024但小于1024*1024时,例如4096,我们应该使用KiB单位,所以我们有 “4 KiB”。
But, let’s solve the problem step by step. Before we dive into the unit determination logic, let’s first define all required units and their boundaries.
但是,让我们一步一步地解决这个问题。在我们深入研究单元确定逻辑之前,让我们首先定义所有需要的单元和它们的边界。
3.1. Defining Required Units
3.1.界定所需单位
As we’ve known, one unit multiplied by 1024 will transit to the unit at the next level. Therefore, we can create constants indicating all required units with their base values:
正如我们所知道的,一个单位乘以1024将转变成下一级的单位。因此,我们可以创建常数来表示所有需要的单位及其基值。
private static long BYTE = 1L;
private static long KiB = BYTE << 10;
private static long MiB = KiB << 10;
private static long GiB = MiB << 10;
private static long TiB = GiB << 10;
private static long PiB = TiB << 10;
private static long EiB = PiB << 10;
As the code above shows, we’ve used the binary left shift operator (<<) to calculate the base values. Here, “x << 10” does the same as “x * 1024” since 1024 is two to the power of 10.
如上面的代码所示,我们使用了二进制左移运算符(<<)来计算基值。这里,“x << 10“的作用与”x * 1024“相同,因为1024是10的2次方。
For SI Prefixes one unit multiplied by 1000 will transit to the unit at the next level. Therefore, we can create constants indicating all required units with their base values:
对于SI前缀一个单位乘以1000将转为下一级的单位。因此,我们可以创建常数来表示所有需要的单位及其基值。
private static long KB = BYTE * 1000;
private static long MB = KB * 1000;
private static long GB = MB * 1000;
private static long TB = GB * 1000;
private static long PB = TB * 1000;
private static long EB = PB * 1000;
3.1. Defining the Number Format
3.1.定义数字格式
Assuming that we’ve determined the right unit and we want to express the file size to two decimal places, we can create a method to output the result:
假设我们已经确定了正确的单位,并且我们想把文件大小表达到小数点后两位,我们可以创建一个方法来输出结果。
private static DecimalFormat DEC_FORMAT = new DecimalFormat("#.##");
private static String formatSize(long size, long divider, String unitName) {
return DEC_FORMAT.format((double) size / divider) + " " + unitName;
}
Next, let’s understand quickly what the method does. As we’ve seen in the code above, first, we defined the number format DEC_FORMAT.
接下来,让我们快速了解该方法的作用。正如我们在上面的代码中所看到的,首先,我们定义了数字格式 DEC_FORMAT。
The divider parameter is the base value of the chosen unit, while the String argument unitName is the unit’s name. For example, if we’ve chosen KiB as the suitable unit, divider=1024 and unitName = “KiB”.
divider参数是所选单位的基础值,而String参数unitName是单位的名称。例如,如果我们选择KiB作为合适的单位,divider=1024和unitName=”KiB”.。
This method centralizes the division calculation and the number format conversion.
该方法集中了除法计算和数字格式转换。
Now, it’s time to move to the core part of the solution: finding out the right unit.
现在,是时候进入解决方案的核心部分了:找出合适的单位。
3.2. Determining the Unit
3.2.确定单位
Let’s first have a look at the implementation of the unit determination method:
让我们先来看看单位确定方法的实现。
public static String toHumanReadableBinaryPrefixes(long size) {
if (size < 0)
throw new IllegalArgumentException("Invalid file size: " + size);
if (size >= EiB) return formatSize(size, EiB, "EiB");
if (size >= PiB) return formatSize(size, PiB, "PiB");
if (size >= TiB) return formatSize(size, TiB, "TiB");
if (size >= GiB) return formatSize(size, GiB, "GiB");
if (size >= MiB) return formatSize(size, MiB, "MiB");
if (size >= KiB) return formatSize(size, KiB, "KiB");
return formatSize(size, BYTE, "Bytes");
}
public static String toHumanReadableSIPrefixes(long size) {
if (size < 0)
throw new IllegalArgumentException("Invalid file size: " + size);
if (size >= EB) return formatSize(size, EB, "EB");
if (size >= PB) return formatSize(size, PB, "PB");
if (size >= TB) return formatSize(size, TB, "TB");
if (size >= GB) return formatSize(size, GB, "GB");
if (size >= MB) return formatSize(size, MB, "MB");
if (size >= KB) return formatSize(size, KB, "KB");
return formatSize(size, BYTE, "Bytes");
}
Now, let’s walk through the method and understand how it works.
现在,让我们走过这个方法,了解它是如何工作的。
First, we want to make sure the input is a positive number.
首先,我们要确保输入的是一个正数。
Then, we check the units in the direction from high (EB) to low (Byte). Once we find the input size is greater than or equal to the current unit’s base value, the current unit will be the right one.
然后,我们沿着从高(EB)到低(Byte)的方向检查单位。一旦我们发现输入的size大于或等于当前单位的基础值,当前单位将是正确的。
As soon as we find the right unit, we can call the previously created formatSize method to get the final result as a String.
一旦我们找到合适的单位,我们就可以调用之前创建的formatSize方法,以获得作为String的最终结果。
3.3. Testing the Solution
3.3.测试解决方案
Now, let’s write a unit test method to verify if our solution works as expected. To simplify testing the method, let’s initialize a Map<Long, String> holding inputs and the corresponding expected results:
现在,让我们写一个单元测试方法来验证我们的解决方案是否如预期的那样工作。为了简化测试方法,让我们初始化一个Map<Long, String> 容纳输入和相应的预期结果。
private static Map<Long, String> DATA_MAP_BINARY_PREFIXES = new HashMap<Long, String>() {{
put(0L, "0 Bytes");
put(1023L, "1023 Bytes");
put(1024L, "1 KiB");
put(12_345L, "12.06 KiB");
put(10_123_456L, "9.65 MiB");
put(10_123_456_798L, "9.43 GiB");
put(1_777_777_777_777_777_777L, "1.54 EiB");
}};
private final static Map<Long, String> DATA_MAP_SI_PREFIXES = new HashMap<Long, String>() {{
put(0L, "0 Bytes");
put(999L, "999 Bytes");
put(1000L, "1 KB");
put(12_345L, "12.35 KB");
put(10_123_456L, "10.12 MB");
put(10_123_456_798L, "10.12 GB");
put(1_777_777_777_777_777_777L, "1.78 EB");
}};
Next, let’s go through the Map DATA_MAP, taking each key value as the input and verifying if we can obtain the expected result:
接下来,让我们遍历MapDATA_MAP,将每个键值作为输入,并验证我们是否可以获得预期的结果。
DATA_MAP.forEach((in, expected) -> Assert.assertEquals(expected, FileSizeFormatUtil.toHumanReadable(in)));
When we execute the unit test, it passes.
当我们执行该单元测试时,它通过了。
4. Improving the Solution With an Enum and Loop
4.用一个枚举和循环来改进解决方案
So far, we’ve solved the problem. The solution is pretty straightforward. In the toHumanReadable method, we’ve written multiple if statements to determine the unit.
到目前为止,我们已经解决了这个问题。这个解决方案是非常直接的。在toHumanReadable方法中,我们写了多个if语句来确定单位。
If we think about the solution carefully, a couple of points might be error-prone:
如果我们仔细思考解决方案,有几个点可能是容易出错的。
- The order of those if statements must be fixed as they are in the method.
- In each if statement, we’ve hard-coded the unit constant and the corresponding name as a String object.
Next, let’s see how to improve the solution.
接下来,让我们看看如何改进解决方案。
4.1. Creating the SizeUnit enum
4.1.创建SizeUnit枚举
Actually, we can convert the unit constants into an enum so that we don’t have to hard-code the names in the method:
实际上,我们可以将单位常量转换成enum,这样我们就不必在方法中硬编码名称。
enum SizeUnitBinaryPrefixes {
Bytes(1L),
KiB(Bytes.unitBase << 10),
MiB(KiB.unitBase << 10),
GiB(MiB.unitBase << 10),
TiB(GiB.unitBase << 10),
PiB(TiB.unitBase << 10),
EiB(PiB.unitBase << 10);
private final Long unitBase;
public static List<SizeUnitBinaryPrefixes> unitsInDescending() {
List<SizeUnitBinaryPrefixes> list = Arrays.asList(values());
Collections.reverse(list);
return list;
}
//getter and constructor are omitted
}
enum SizeUnitSIPrefixes {
Bytes(1L),
KB(Bytes.unitBase * 1000),
MB(KB.unitBase * 1000),
GB(MB.unitBase * 1000),
TB(GB.unitBase * 1000),
PB(TB.unitBase * 1000),
EB(PB.unitBase * 1000);
private final Long unitBase;
public static List<SizeUnitSIPrefixes> unitsInDescending() {
List<SizeUnitSIPrefixes> list = Arrays.asList(values());
Collections.reverse(list);
return list;
}
//getter and constructor are omitted
}
As the enum SizeUnit above shows, a SizeUnit instance holds both unitBase and name.
正如上面的enum SizeUnit所示,一个SizeUnit实例同时持有unitBase和name。
Further, since we want to check the units in “descending” order later, we’ve created a helper method, unitsInDescending, to return all units in the required order.
此外,由于我们希望以后以 “降序 “检查单位,我们创建了一个辅助方法unitsInDescending,来返回所有按要求顺序排列的单位。
With this enum, we don’t have to code the names manually.
有了这个enum,我们就不必手动编码名称。
Next, let’s see if we can make some improvement on the set of if statements.
接下来,让我们看看是否可以对if语句集进行一些改进。
4.2. Using a Loop to Determine the Unit
4.2.使用循环来确定单位
As our SizeUnit enum can provide all units in a List in descending order, we can replace the set of if statements with a for loop:
由于我们的SizeUnit枚举可以按降序提供List中的所有单位,我们可以用for循环代替一组if语句。
public static String toHumanReadableWithEnum(long size) {
List<SizeUnit> units = SizeUnit.unitsInDescending();
if (size < 0) {
throw new IllegalArgumentException("Invalid file size: " + size);
}
String result = null;
for (SizeUnit unit : units) {
if (size >= unit.getUnitBase()) {
result = formatSize(size, unit.getUnitBase(), unit.name());
break;
}
}
return result == null ? formatSize(size, SizeUnit.Bytes.getUnitBase(), SizeUnit.Bytes.name()) : result;
}
As the code above shows, the method follows the same logic as the first solution. In addition, it avoids those unit constants, multiple if statements, and hard-coded unit names.
正如上面的代码所示,该方法遵循与第一个解决方案相同的逻辑。此外,它避免了那些单元常量、多个if语句和硬编码的单元名称。
To make sure it works as expected, let’s test our solution:
为了确保它按预期工作,让我们测试一下我们的解决方案。
DATA_MAP.forEach((in, expected) -> Assert.assertEquals(expected, FileSizeFormatUtil.toHumanReadableWithEnum(in)));
The test passes when we execute it.
当我们执行该测试时,该测试通过。
5. Using the Long.numberOfLeadingZeros Method
5.使用Long.numberOfLeadingZeros方法
We’ve solved the problem by checking units one by one and taking the first one that satisfies our condition.
我们已经通过逐一检查单位,并采取第一个满足我们条件的单位来解决这个问题。
Alternatively, we can use the Long.numberOfLeadingZeros method from the Java standard API to determine which unit the given size value falls in.
另外,我们可以使用Java标准API中的Long.numberOfLeadingZeros方法来确定给定尺寸值属于哪个单位。
Next, let’s take a closer look at this interesting approach.
接下来,让我们仔细看看这个有趣的方法。
5.1. Introduction to the Long.numberOfLeadingZeros Method
5.1.介绍Long.numberOfLeadingZeros方法
The Long.numberOfLeadingZeros method returns the number of zero bits preceding the leftmost one-bit in the binary representation of the given Long value.
Long.numberOfLeadingZeros方法返回给定Long值的二进制表示中最左边的一位之前的零位数。
As Java’s Long type is a 64-bit integer, Long.numberOfLeadingZeros(0L) = 64. A couple of examples may help us understand the method quickly:
由于Java的Long类型是一个64位的整数,Long.numberOfLeadingZeros(0L) = 64。几个例子可能有助于我们快速理解这个方法。
1L = 00... (63 zeros in total) .. 0001 -> Long.numberOfLeadingZeros(1L) = 63
1024L = 00... (53 zeros in total) .. 0100 0000 0000 -> Long.numberOfLeadingZeros(1024L) = 53
Now, we’ve understood the Long.numberOfLeadingZeros method. But why can it help us to determine the unit?
现在,我们已经理解了Long.numberOfLeadingZeros方法。但为什么它能帮助我们确定单位呢?
Let’s figure it out.
我们来算算看。
5.2. The Idea to Solve the Problem
5.2.解决问题的想法
We’ve known the factor between the units is 1024, which is two to the power of ten (2^10). Therefore, if we calculate the number of leading zeros of each unit’s base value, the difference between two adjacent units is always 10:
我们已经知道单位之间的系数是1024,也就是10的2次方(2^10)。因此,如果我们计算每个单位基值的前导零数,相邻两个单位之间的差值总是10。
Index Unit numberOfLeadingZeros(unit.baseValue)
----------------------------------------------------
0 Byte 63
1 KiB 53
2 MiB 43
3 GiB 33
4 TiB 23
5 PiB 13
6 EiB 3
Further, we can calculate the number of leading zeros of the input value and see the result falls in which unit’s range to find the suitable unit.
此外,我们可以计算输入值的前导零数,看看结果落在哪个单位的范围内,从而找到合适的单位。
Next, let’s see an example – how to determine the unit and calculate the unit base value for the size 4096:
接下来,让我们看一个例子–如何确定单位并计算大小为4096的单位基础值。
if 4096 < 1024 (Byte's base value) -> Byte
else:
numberOfLeadingZeros(4096) = 51
unitIdx = (numberOfLeadingZeros(1) - 51) / 10 = (63 - 51) / 10 = 1
unitIdx = 1 -> KB (Found the unit)
unitBase = 1 << (unitIdx * 10) = 1 << 10 = 1024
Next, let’s implement this logic as a method.
接下来,让我们把这个逻辑实现为一个方法。
5.3. Implementing the Idea
5.3.实施这一想法
Let’s create a method to implement the idea we’ve discussed just now:
让我们创建一个方法来实现我们刚才讨论的想法。
public static String toHumanReadableByNumOfLeadingZeros(long size) {
if (size < 0) {
throw new IllegalArgumentException("Invalid file size: " + size);
}
if (size < 1024) return size + " Bytes";
int unitIdx = (63 - Long.numberOfLeadingZeros(size)) / 10;
return formatSize(size, 1L << (unitIdx * 10), " KMGTPE".charAt(unitIdx) + "iB");
}
As we can see, the method above is pretty compact. It doesn’t need unit constants or an enum. Instead, we’ve created a String containing units: ” KMGTPE”. Then, we use the calculated unitIdx to pick the right unit letter and append the “iB” to build the complete unit name.
我们可以看到,上面的方法是相当紧凑的。它不需要单位常量或enum。相反,我们已经创建了一个包含单位的String。” KMGTPE”。然后,我们使用计算出的unitIdx来挑选正确的单位字母,并附加 “iB “来建立完整的单位名称。
It’s worth mentioning that we leave the first character empty on purpose in the String ” KMGTPE”. This is because the unit “Byte” doesn’t follow the pattern “*B“, and we handled it separately: if (size < 1024) return size + ” Bytes”;
值得一提的是,我们在String ” KMGTPE”中故意将第一个字符留空。这是因为单位”Byte“并不遵循”*B“的模式,我们对它进行了单独处理。if (size < 1024) return size + ” Bytes”;
Again, let’s write a test method to make sure it works as expected:
同样,让我们写一个测试方法,以确保它按预期工作。
DATA_MAP.forEach((in, expected) -> Assert.assertEquals(expected, FileSizeFormatUtil.toHumanReadableByNumOfLeadingZeros(in)));
6. Using Apache Commons IO
6.使用Apache Commons IO
So far, we’ve implemented two different approaches to converting a file size value into a human-readable format.
到目前为止,我们已经实现了两种不同的方法,将文件大小的值转换为人类可读的格式。
Actually, some external library has already provided a method to solve the problem: Apache Commons-IO.
实际上,一些外部库已经提供了一种方法来解决这个问题:Apache Commons-IO。
Apache Commons-IO’s FileUtils allows us to convert byte size to a human-readable format through the byteCountToDisplaySize method.
Apache Commons-IO的FileUtils允许我们通过byteCountToDisplaySize方法将字节大小转换成人类可读的格式。
However, this method rounds the decimal part up automatically.
然而,这种方法会自动将小数部分四舍五入。
Finally, let’s test the byteCountToDisplaySize method with our input data and see what it prints:
最后,让我们用我们的输入数据测试一下byteCountToDisplaySize方法,看看它打印出什么。
DATA_MAP.forEach((in, expected) -> System.out.println(in + " bytes -> " + FileUtils.byteCountToDisplaySize(in)));
The test outputs:
测试输出。
0 bytes -> 0 bytes
1024 bytes -> 1 KB
1777777777777777777 bytes -> 1 EB
12345 bytes -> 12 KB
10123456 bytes -> 9 MB
10123456798 bytes -> 9 GB
1023 bytes -> 1023 bytes
7. Conclusion
7.结语
In this article, we’ve addressed different ways to convert file size in bytes into a human-readable format.
在这篇文章中,我们已经解决了将文件大小的字节数转换成人类可读格式的不同方法。
As always, the code presented in this article is available over on GitHub.
一如既往,本文介绍的代码可在GitHub上获得over。