135

I need to display a file size as a string using sensible units.

For example,

1L ==> "1 B";
1024L ==> "1 KB";
2537253L ==> "2.3 MB"

etc.

I found this previous answer, which I didn't find satisfactory.

I have come up with my own solution which has similar shortcomings:

private static final long K = 1024;
private static final long M = K * K;
private static final long G = M * K;
private static final long T = G * K;

public static String convertToStringRepresentation(final long value){
    final long[] dividers = new long[] { T, G, M, K, 1 };
    final String[] units = new String[] { "TB", "GB", "MB", "KB", "B" };
    if(value < 1)
        throw new IllegalArgumentException("Invalid file size: " + value);
    String result = null;
    for(int i = 0; i < dividers.length; i++){
        final long divider = dividers[i];
        if(value >= divider){
            result = format(value, divider, units[i]);
            break;
        }
    }
    return result;
}

private static String format(final long value,
    final long divider,
    final String unit){
    final double result =
        divider > 1 ? (double) value / (double) divider : (double) value;
    return String.format("%.1f %s", Double.valueOf(result), unit);
}

The main problem is my limited knowledge of Decimalformat and / or String.format. I would like 1024L, 1025L, etc. to map to 1 KB rather than 1.0 KB.

So, two possibilities:

  1. I would prefer a good out-of-the-box solution in a public library like Apache Commons or Google Guava.
  2. If there isn't, how can I get rid of the '.0' part (without resorting to string replacement and regex, I can do that myself)?
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 6
    See this answer: http://stackoverflow.com/a/3758880/34088 – Aaron Digulla Apr 23 '12 at 13:52
  • I know. I would have accepted that one, had it been posted here. – Sean Patrick Floyd Apr 23 '12 at 13:58
  • If you change the last line to return NumberFormat.getFormat("#,##0.#").format(result) + " " + unit; it works in GWT too! Thanks for this, it's still not in Guava. – tom Oct 24 '13 at 12:31
  • 1
    By ISO standard, kilo is expressed with a lowercase 'k'. – Willem Van Onsem Oct 30 '14 at 00:47
  • You might like [the answer here] [1] since it's a really efficient solution, and respects SI standards. [1]:http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java – WhyNotHugo Mar 25 '12 at 05:18
  • I am aware of that question (and of the accepted answer), I have also [posted an answer there](http://stackoverflow.com/a/3758653/342852), referencing this question. This question came first, however, and didn't get that good an answer. – Sean Patrick Floyd Mar 25 '12 at 09:53
  • My bad, I didn't notice it was you who had posted the link to this question on that other one. That's actually how I got here. – WhyNotHugo Mar 25 '12 at 15:04
  • You might want to try the triava Open Source library. If you pass a proper DecimalFormat, you can get the desired "1 KB" output. Examples how to use it can be found in http://stackoverflow.com/a/38390338/1280825 and https://github.com/trivago/triava/blob/master/src/test/java/com/trivago/triava/util/UnitToolsTest.java . – Christian Esken Jan 05 '17 at 14:00

3 Answers3

400
public static String readableFileSize(long size) {
    if(size <= 0) return "0";
    final String[] units = new String[] { "B", "kB", "MB", "GB", "TB" };
    int digitGroups = (int) (Math.log10(size)/Math.log10(1024));
    return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}

This will work up to 1000 TB.... and the program is short!

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
Mr Ed
  • 5,068
  • 1
  • 19
  • 12
  • This should be the "correct" answer as it takes the unit into account. – Stefan Hoth May 30 '11 at 17:10
  • 3
    Agree, should be the correct answer. Look [here](http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java/3758880#3758880) for an even better output. – Kariem Aug 08 '11 at 17:03
  • 4
    final String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "EB" }; // now it works up to Long.MAX_VALUE! – Joe Jul 06 '12 at 23:49
  • 32
    To comply with international standards: final String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; /* used with 1000 * / final String[] units = new String[] { "Bi", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; /* used with 1024 */ See: http://physics.nist.gov/cuu/Units/binary.html – Mr Ed Jul 07 '12 at 12:07
  • 3
    and reverse function?... – nefo_x Dec 12 '13 at 15:31
  • nefo_x: The reverse function is impossible because you lose precision. If you wanted an approximation, you can parse the string to get the number part, then multiply that number by the factor which corresponds with the units string, i.e. "KB" would use the factor 1000. – Mr Ed Jan 16 '14 at 12:09
  • 2
    A small comment: "kilo" is expressed with a lowercase 'k' and not an uppercase 'K'. – Willem Van Onsem Oct 30 '14 at 00:45
  • 1
    Might want to return "0 B" or otherwise include units in zero case. – dbro Apr 10 '16 at 02:39
  • Joe: Petabyte comes before Exabyte and don't forget these: Zettabyte, Yottabyte, so the complete list is: "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" See: https://en.wikipedia.org/wiki/Yottabyte Yes I know ZB and YB are not needed today, but maybe in the future...;) – user4955663 Oct 25 '16 at 08:39
  • "This will work up to 1000 TB" yes it is not working after this limit – Himanshu Mori Jan 23 '17 at 11:45
  • Maybe using `.format(size/Math.pow(1024, Math.min(digitGroups, units.length)))` could fix the problem of filesizes above 1000TB? – Chop Feb 26 '17 at 16:23
  • 2
    Just a tip for Android developers : To show the file size in a nice readable way on Android, you can use `Formatter.formatShortFileSize(context,file.length())` – android developer Sep 06 '18 at 08:47
  • See scala solution ported from this code: https://stackoverflow.com/questions/35609587/human-readable-size-units-file-sizes-for-scala-code-like-duration/40235429#40235429 Also support for ZB and YB. – user4955663 Nov 07 '18 at 08:36
  • Getting crash sometimes. Usually when its in KB java.lang.NumberFormatException: For input string: "1,010.3" @WillemVanOnsem Can you please help me – Anupreet Kaur Apr 27 '20 at 16:32
  • This DecimalFormat will produce different results based on Locale. I added this to have consistency regardless of environment: `DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); DecimalFormat formatter = new DecimalFormat("#,##0.##", symbols);` – Denis K May 29 '20 at 07:29
  • Sadly, this won't work in conjunction with Linux `sort -h` like `du -h` output would. So back to the drawing board for me. – Sridhar Sarnobat Nov 05 '22 at 18:40
8

You'll probably have more luck with java.text.DecimalFormat. This code should probably do it (just winging it though...)

new DecimalFormat("#,##0.#").format(value) + " " + unit

Robert Watkins
  • 2,196
  • 15
  • 17
7

It is surprising for me, but a loop-based algorithm is about 10% faster.

public static String toNumInUnits(long bytes) {
    int u = 0;
    for ( ; bytes > 1024*1024; bytes >>= 10) {
        u++;
    }
    if (bytes > 1024)
        u++;
    return String.format("%.1f %cB", bytes/1024f, " kMGTPE".charAt(u));
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
R Jobs
  • 87
  • 1
  • 1