如何在Java中将字节大小转换为人类可读的格式?

比如1024应该变成“1 Kb”,1024*1024应该变成“1 Mb”。

我有点厌倦了为每个项目写这个实用方法。在Apache Commons中有这样的静态方法吗?


当前回答

我们可以完全避免使用缓慢的Math.pow()和Math.log()方法,而不会牺牲简单性,因为单位之间的因子(例如,B, KB, MB等)是1024,即2^10。Long类有一个方便的numberofleadingzero()方法,我们可以用它来告诉大小值落在哪个单元中。

重点:大小单位的距离为10位(1024 = 2^10),这意味着最高位的位置-换句话说,前导零的数量-相差10(字节= KB*1024, KB = MB*1024,等等)。

前导零数与大小单位的相关性:

# of leading 0's Size unit
>53 B (Bytes)
>43 KB
>33 MB
>23 GB
>13 TB
>3 PB
<=3 EB

最终代码:

public static String formatSize(long v) {
    if (v < 1024) return v + " B";
    int z = (63 - Long.numberOfLeadingZeros(v)) / 10;
    return String.format("%.1f %sB", (double)v / (1L << (z*10)), " KMGTPE".charAt(z));
}

其他回答

如果你使用Android,你可以简单地使用Android .text.format. formatter . formatfilesize()。它的优点是易于使用,并且它取决于区域设置,以便为用户更好地显示它。缺点是它不处理EB,而且它只用于公制单位(每个Kilo是1000字节,不能作为1024字节使用)。

或者,这里有一个基于这篇热门文章的解决方案:


interface BytesFormatter {
    /**called when the type of the result to format is Long. Example: 123KB
     * @param unitPowerIndex the unit-power we need to format to. Examples: 0 is bytes, 1 is kb, 2 is mb, etc...
     * available units and their order: B,K,M,G,T,P,E
     * @param isMetric true if each kilo==1000, false if kilo==1024
     * */
    fun onFormatLong(valueToFormat: Long, unitPowerIndex: Int, isMetric: Boolean): String

    /**called when the type of the result to format is Double. Example: 1.23KB
     * @param unitPowerIndex the unit-power we need to format to. Examples: 0 is bytes, 1 is kb, 2 is mb, etc...
     * available units and their order: B,K,M,G,T,P,E
     * @param isMetric true if each kilo==1000, false if kilo==1024
     * */
    fun onFormatDouble(valueToFormat: Double, unitPowerIndex: Int, isMetric: Boolean): String
}

/**
 * formats the bytes to a human readable format, by providing the values to format later in the unit that we've found best to fit it
 *
 * @param isMetric true if each kilo==1000, false if kilo==1024
 * */
fun bytesIntoHumanReadable(
    @IntRange(from = 0L) bytesToFormat: Long, bytesFormatter: BytesFormatter,
    isMetric: Boolean = true
): String {
    val units = if (isMetric) 1000L else 1024L
    if (bytesToFormat < units)
        return bytesFormatter.onFormatLong(bytesToFormat, 0, isMetric)
    var bytesLeft = bytesToFormat
    var unitPowerIndex = 0
    while (unitPowerIndex < 6) {
        val newBytesLeft = bytesLeft / units
        if (newBytesLeft < units) {
            val byteLeftAsDouble = bytesLeft.toDouble() / units
            val needToShowAsInteger =
                byteLeftAsDouble == (bytesLeft / units).toDouble()
            ++unitPowerIndex
            if (needToShowAsInteger) {
                bytesLeft = newBytesLeft
                break
            }
            return bytesFormatter.onFormatDouble(byteLeftAsDouble, unitPowerIndex, isMetric)
        }
        bytesLeft = newBytesLeft
        ++unitPowerIndex
    }
    return bytesFormatter.onFormatLong(bytesLeft, unitPowerIndex, isMetric)
}

Sample usage:

// val valueToTest = 2_000L
// val valueToTest = 2_000_000L
// val valueToTest = 2_000_000_000L
// val valueToTest = 9_000_000_000_000_000_000L
// val valueToTest = 9_200_000_000_000_000_000L
val bytesToFormat = Random.nextLong(Long.MAX_VALUE)
val bytesFormatter = object : BytesFormatter {
    val numberFormat = NumberFormat.getNumberInstance(Locale.ROOT).also {
        it.maximumFractionDigits = 2
        it.minimumFractionDigits = 0
    }

    private fun formatByUnit(formattedNumber: String, threePowerIndex: Int, isMetric: Boolean): String {
        val sb = StringBuilder(formattedNumber.length + 4)
        sb.append(formattedNumber)
        val unitsToUse = "B${if (isMetric) "k" else "K"}MGTPE"
        sb.append(unitsToUse[threePowerIndex])
        if (threePowerIndex > 0)
            if (isMetric) sb.append('B') else sb.append("iB")
        return sb.toString()
    }

    override fun onFormatLong(valueToFormat: Long, unitPowerIndex: Int, isMetric: Boolean): String {
        return formatByUnit(String.format("%,d", valueToFormat), unitPowerIndex, isMetric)
    }

    override fun onFormatDouble(valueToFormat: Double, unitPowerIndex: Int, isMetric: Boolean): String {
        //alternative for using numberFormat :
        //val formattedNumber = String.format("%,.2f", valueToFormat).let { initialFormattedString ->
        //    if (initialFormattedString.contains('.'))
        //        return@let initialFormattedString.dropLastWhile { it == '0' }
        //    else return@let initialFormattedString
        //}
        return formatByUnit(numberFormat.format(valueToFormat), unitPowerIndex, isMetric)
    }
}
Log.d("AppLog", "formatting of $bytesToFormat bytes (${String.format("%,d", bytesToFormat)})")
Log.d("AppLog", bytesIntoHumanReadable(bytesToFormat, bytesFormatter))
Log.d("AppLog", "Android:${android.text.format.Formatter.formatFileSize(this, bytesToFormat)}")

试试JSR 363。它的单元扩展模块,如Unicode CLDR(在GitHub: uom-systems中),为您完成所有这些。

你可以使用每个实现中包含的MetricPrefix或BinaryPrefix(与上面的一些例子相比),如果你在印度或附近的国家生活和工作,IndianPrefix(也在uom-系统的公共模块中)允许你使用和格式化“千万字节”或“Lakh字节”。

我们可以完全避免使用缓慢的Math.pow()和Math.log()方法,而不会牺牲简单性,因为单位之间的因子(例如,B, KB, MB等)是1024,即2^10。Long类有一个方便的numberofleadingzero()方法,我们可以用它来告诉大小值落在哪个单元中。

重点:大小单位的距离为10位(1024 = 2^10),这意味着最高位的位置-换句话说,前导零的数量-相差10(字节= KB*1024, KB = MB*1024,等等)。

前导零数与大小单位的相关性:

# of leading 0's Size unit
>53 B (Bytes)
>43 KB
>33 MB
>23 GB
>13 TB
>3 PB
<=3 EB

最终代码:

public static String formatSize(long v) {
    if (v < 1024) return v + " B";
    int z = (63 - Long.numberOfLeadingZeros(v)) / 10;
    return String.format("%.1f %sB", (double)v / (1L << (z*10)), " KMGTPE".charAt(z));
}

这是aioobe答案的修改版本。

变化:

Locale参数,因为有些语言使用。其他的,作为小数点。 人类可读的代码


private static final String[] SI_UNITS = { "B", "kB", "MB", "GB", "TB", "PB", "EB" };
private static final String[] BINARY_UNITS = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };

public static String humanReadableByteCount(final long bytes, final boolean useSIUnits, final Locale locale)
{
    final String[] units = useSIUnits ? SI_UNITS : BINARY_UNITS;
    final int base = useSIUnits ? 1000 : 1024;

    // When using the smallest unit no decimal point is needed, because it's the exact number.
    if (bytes < base) {
        return bytes + " " + units[0];
    }

    final int exponent = (int) (Math.log(bytes) / Math.log(base));
    final String unit = units[exponent];
    return String.format(locale, "%.1f %s", bytes / Math.pow(base, exponent), unit);
}

这是一个Go版本。为了简单起见,我只包含了二进制输出情况。

func sizeOf(bytes int64) string {
    const unit = 1024
    if bytes < unit {
        return fmt.Sprintf("%d B", bytes)
    }

    fb := float64(bytes)
    exp := int(math.Log(fb) / math.Log(unit))
    pre := "KMGTPE"[exp-1]
    div := math.Pow(unit, float64(exp))
    return fmt.Sprintf("%.1f %ciB", fb / div, pre)
}