将文件大小从字节转换为易于阅读的字符串

359

我正在使用这个函数将文件大小从字节转换为易于理解的格式:

function getReadableFileSizeString(fileSizeInBytes) {
  var i = -1;
  var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
  do {
    fileSizeInBytes /= 1024;
    i++;
  } while (fileSizeInBytes > 1024);

  return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
}

console.log(getReadableFileSizeString(1551859712)); // output is "1.4 GB"

然而,这似乎并不是百分之百准确的。例如:

getReadableFileSizeString(1551859712); // output is "1.4 GB"

这不应该是 "1.5 GB" 吗?似乎通过1024进行除法会失去精度。我是否完全误解了什么,或者有更好的方法来解决这个问题?


3
getReadableFileSizeString(0);返回0.1kb。p - Daniel Magnusson
3
为什么应该是1.5?实际上这个数是1.445281982421875,正确地四舍五入为1.4。 - mpen
1
1551859712/(1024^3)=1.445281982421875 这是正确的! - H.M.
6
我很喜欢你加入了“YB”。怀疑任何人都不可能为他的“DB”获得一个“YB”。这将花费100万亿美元! - Guy
13
@guyarad - 50年前有一张著名的5MB硬盘照片(当时硬盘的大小相当于整个房间,重约一吨)。我敢肯定,那时候他们甚至没想到过GB和TB的概念,看看我们今天所处的位置...永远不要说永远;-) - TheCuBeMan
显示剩余3条评论
25个回答

561

这是我写的一篇文章:

/**
 * Format bytes as human-readable text.
 * 
 * @param bytes Number of bytes.
 * @param si True to use metric (SI) units, aka powers of 1000. False to use 
 *           binary (IEC), aka powers of 1024.
 * @param dp Number of decimal places to display.
 * 
 * @return Formatted string.
 */
function humanFileSize(bytes, si=false, dp=1) {
  const thresh = si ? 1000 : 1024;

  if (Math.abs(bytes) < thresh) {
    return bytes + ' B';
  }

  const units = si 
    ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 
    : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  let u = -1;
  const r = 10**dp;

  do {
    bytes /= thresh;
    ++u;
  } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);


  return bytes.toFixed(dp) + ' ' + units[u];
}


console.log(humanFileSize(1551859712))  // 1.4 GiB
console.log(humanFileSize(5000, true))  // 5.0 kB
console.log(humanFileSize(5000, false))  // 4.9 KiB
console.log(humanFileSize(-10000000000000000000000000000))  // -8271.8 YiB
console.log(humanFileSize(999949, true))  // 999.9 kB
console.log(humanFileSize(999950, true))  // 1.0 MB
console.log(humanFileSize(999950, true, 2))  // 999.95 kB
console.log(humanFileSize(999500, true, 0))  // 1 MB


2
我做了一个调整:在评估阈值时,取绝对值。这样函数将支持负值。很好的函数!谢谢你没有使用switch语句!! - Aaron Blenkush
31
什么情况下会出现负的文件大小? - mpen
29
我刚刚将你的函数复制到一个我正在使用的Google表格中,用于展示“清理”操作后的大小差异。包括之前、之后以及差值。清理操作导致一些数据库表格的增长,而其他表格则减少。例如,表A的差值为-1.95 MB,而表B的差值为500 kB。因此:既有正值也有负值 :-) - Aaron Blenkush
7
谢谢提供的压缩版本。不过您能否告诉我,为什么在“EiB”的“E”后面插入了两个不可见的Unicode字符U+200C(零宽度非连接符)和U+200B(零宽度空格)?这是为了作为水印,以便跟踪使用此代码的人吗?如果是这样,我认为您应该在文章中透明地说明。 - Leviathan
2
@Leviathan 我并不是故意插入它们的。我只是通过UglifyJS运行它,并将其粘贴在这里,所以我不确定为什么这些字符会出现... - randers
显示剩余11条评论

172

计算的另一种实现

function humanFileSize(size) {
    var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
    return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
}

10
似乎无法处理0。 - Offirmo
5
它处理0还是不处理?毕竟,用if(size == 0) {} else {}这个方法仍然比我见过的大多数方法更加优雅。 - Rodrigo
23
如果size为0,将第一行改为var i = size == 0 ? 0 : Math.floor( Math.log(size) / Math.log(1024) );似乎可以解决问题。它会返回“0 B”。请注意不要改变原来的意思,并使翻译通俗易懂。 - Gavin
2
只是提供信息;我知道答案是纯JavaScript,但如果有人想在TypeScript中使用它,它不起作用(类型不正确,因为您正在使用toFixed,然后使用字符串进行数学运算。* 1是什么意思? - Frexuz
14
*1 将数据类型从字符串转换为数字,因此对于值 1024,您会得到 1 kB 而不是 1.00 kB。为了实现相同的结果,您可以使用 Number((size / Math.pow(1024, i)).toFixed(2)) 让 TypeScript 更加满意。 - Adrian Theodorescu
显示剩余2条评论

66

这取决于您想使用二进制还是十进制约定。

例如,RAM始终以二进制方式测量,因此将1551859712表示为 ~1.4GiB 是正确的。

另一方面,硬盘制造商喜欢使用十进制,所以他们会称其为 ~1.6GB。

而令人困惑的是,软盘使用两种系统的混合体-它们的1MB实际上是1024000字节。


7
非常有趣 ;-) 令人困惑的是,软盘使用两种系统的混合物——它们的1MB实际上是1024000个字节。 - FranXho
2
真实的RAM大小使用IEC单位进行测量,磁盘大小使用公制单位。有一个同构的npm模块可以转换两者:byte-size - Lloyd

38

这是一个将数字转换为可读字符串的原型,遵循新的国际标准。

有两种表示大数值的方法:你可以将它们显示成1000的倍数= 10 3(基于十进制),或者1024的倍数= 2 10(基于二进制)。如果你除以1000,你可能会使用SI前缀名称;如果你除以1024,你可能会使用IEC前缀名称。问题在于除以1024。许多应用程序使用SI前缀名称,而一些应用程序则使用IEC前缀名称。目前的情况很混乱。如果你看到SI前缀名称,你就不知道数字是被1000还是1024除。

https://wiki.ubuntu.com/UnitsPolicy

http://en.wikipedia.org/wiki/Template:Quantities_of_bytes

Object.defineProperty(Number.prototype,'fileSize',{value:function(a,b,c,d){
 return (a=a?[1e3,'k','B']:[1024,'K','iB'],b=Math,c=b.log,
 d=c(this)/c(a[0])|0,this/b.pow(a[0],d)).toFixed(2)
 +' '+(d?(a[1]+'MGTPEZY')[--d]+a[2]:'Bytes');
},writable:false,enumerable:false});

这个函数不包含任何循环,所以它可能比其他一些函数更快。

用法:

IEC 前缀

console.log((186457865).fileSize()); // default IEC (power 1024)
//177.82 MiB
//KiB,MiB,GiB,TiB,PiB,EiB,ZiB,YiB

国际单位制前缀

console.log((186457865).fileSize(1)); //1,true for SI (power 1000)
//186.46 MB 
//kB,MB,GB,TB,PB,EB,ZB,YB

我将IEC设置为默认值,因为我总是使用二进制模式计算文件大小...利用1024的幂


如果您只想在一个简短的一行函数中使用它们之一:

国际单位制(SI)

function fileSizeSI(a,b,c,d,e){
 return (b=Math,c=b.log,d=1e3,e=c(a)/c(d)|0,a/b.pow(d,e)).toFixed(2)
 +' '+(e?'kMGTPEZY'[--e]+'B':'Bytes')
}
//kB,MB,GB,TB,PB,EB,ZB,YB

IEC

function fileSizeIEC(a,b,c,d,e){
 return (b=Math,c=b.log,d=1024,e=c(a)/c(d)|0,a/b.pow(d,e)).toFixed(2)
 +' '+(e?'KMGTPEZY'[--e]+'iB':'Bytes')
}
//KiB,MiB,GiB,TiB,PiB,EiB,ZiB,YiB

用法:

console.log(fileSizeIEC(7412834521));

如果您对这些功能有任何问题,请随时提问。


非常好的紧凑代码,我个人会添加一些额外的字符来控制小数位。 - Orwellophile
你好!实际上,这段代码是我在jsfiddle上第一次编写的。在过去的几年中,我学会了使用简写和位运算。慢速移动设备、缓慢的互联网、有限的空间...这样做可以节省很多时间。但这还不是全部,整体性能在每个浏览器中都大幅提高,整个代码加载速度也更快...我不使用jquery,所以不必每次加载100kb。我还需要说的是,我也在微控制器、智能电视、游戏机等设备上编写javascript。这些设备的空间(MCU)、性能(智能电视)和自然的有时缓慢的连接(移动设备)都是有限的。 - cocco
我还应该提到,我总是将像'window.document'或'math.something'这样的东西缓存到全局变量中,因为我在各种函数中需要它们很多次。这样做最重要的是提高了性能。不用说也可以节省空间。"d=1e3,e=log(a)/log(d)|0,a/pow(d,e)" .... b和c不再需要。 - cocco
32
代码精简应该成为你的构建过程的一部分,而不是编码风格。由于这种写法需要花费较长时间来阅读和验证正确性,因此没有严肃的开发者会使用这段代码。 - huysentruitw
1
对于那些不喜欢看到“15.00字节”的人,你可以稍微修改一下这部分代码:.toFixed(e? 2:0) - Lukman
显示剩余2条评论

25
sizeOf = function (bytes) {
  if (bytes == 0) { return "0.00 B"; }
  var e = Math.floor(Math.log(bytes) / Math.log(1024));
  return (bytes/Math.pow(1024, e)).toFixed(2)+' '+' KMGTP'.charAt(e)+'B';
}
"1.91 GB" "6.73 MB" "3.00 MB"

2
如果你想要去掉字节的额外空间,你可以使用零宽度空格\u200b'\u200bKMGTP' - cdmckay

17

ReactJS组件的解决方案

Bytes = React.createClass({
    formatBytes() {
        var i = Math.floor(Math.log(this.props.bytes) / Math.log(1024));
        return !this.props.bytes && '0 Bytes' || (this.props.bytes / Math.pow(1024, i)).toFixed(2) + " " + ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][i]
    },
    render () {
        return (
            <span>{ this.formatBytes() }</span>
        );
    }
});

更新 对于那些使用es6的人,这是一个无状态版本的相同组件

const sufixes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const getBytes = (bytes) => {
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return !bytes && '0 Bytes' || (bytes / Math.pow(1024, i)).toFixed(2) + " " + sufixes[i];
};

const Bytes = ({ bytes }) => (<span>{ getBytes(bytes) }</span>);

Bytes.propTypes = {
  bytes: React.PropTypes.number,
};

1
太好了,谢谢。在getBytes函数的第一行中,你只是忘记在Math.log()里面加上“bytes”了。 - BaptWaels
1
非常好。为了消除歧义,并使用ES6符号,可以使用以下代码:return (!bytes && '0 Bytes') || ${(bytes / (1024 ** i)).toFixed(2)} ${suffixes[i]}; - Little Brain

13

另一个与此类似的例子

function fileSize(b) {
    var u = 0, s=1024;
    while (b >= s || -b >= s) {
        b /= s;
        u++;
    }
    return (u ? b.toFixed(1) + ' ' : b) + ' KMGTPEZY'[u] + 'B';
}

它在与具有类似特性的其他产品相比,仅略微提高了性能。


1
这确实比其他答案提供了更好的性能。我正在使用这个。其他一些答案会让我的Chrome标签挂起并占用99.9%的CPU,因为我正在进行周期性计算。 - Nir Lanka

11

基于cocco的想法,这里提供了一个不那么紧凑但是希望更加全面的例子。

<!DOCTYPE html>
<html>
<head>
<title>File info</title>

<script>
<!--
function fileSize(bytes) {
    var exp = Math.log(bytes) / Math.log(1024) | 0;
    var result = (bytes / Math.pow(1024, exp)).toFixed(2);

    return result + ' ' + (exp == 0 ? 'bytes': 'KMGTPEZY'[exp - 1] + 'B');
}

function info(input) {
    input.nextElementSibling.textContent = fileSize(input.files[0].size);
} 
-->
</script>
</head>

<body>
<label for="upload-file"> File: </label>
<input id="upload-file" type="file" onchange="info(this)">
<div></div>
</body>
</html> 

9

这里有很多很好的答案。但是如果你正在寻找一种非常简单的方法,而且不介意使用一个流行的库,一个很好的解决方案是 filesize https://www.npmjs.com/package/filesize

它有很多选项,使用也很简单,例如:

filesize(265318); // "259.1 KB"

从他们出色的示例中得出

8

我希望实现“文件管理器”(例如Windows资源管理器)的行为,其中小数位数与数字大小成比例关系。似乎其他答案都无法做到这一点。

function humanFileSize(size) {
    if (size < 1024) return size + ' B'
    let i = Math.floor(Math.log(size) / Math.log(1024))
    let num = (size / Math.pow(1024, i))
    let round = Math.round(num)
    num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round
    return `${num} ${'KMGTPEZY'[i-1]}B`
}

以下是一些示例:

humanFileSize(0)          // "0 B"
humanFileSize(1023)       // "1023 B"
humanFileSize(1024)       // "1.00 KB"
humanFileSize(10240)      // "10.0 KB"
humanFileSize(102400)     // "100 KB"
humanFileSize(1024000)    // "1000 KB"
humanFileSize(12345678)   // "11.8 MB"
humanFileSize(1234567890) // "1.15 GB"

使用 toFixed 将其转换为字符串,因此您的 round 是字符串或数字。这是不好的做法,您可以轻松地将其转换回数字:+num.tofixed(2) - Vincent Duprez
.toPrecision(3) 不包含所有情况吗?哦...我猜它不包括1000到1023之间的情况。遗憾。 - mpen
它显示输入为10130的值为9.9,但应该显示为9.89。对于10239- 10.0,应该显示为9.99。(适用于Win 10 Explorer) - KeyKi
humanFileSize(10000000000000000000000000000) -> 8.08 undefinedB 哎呀,这是我最喜欢的字节类型。 - Sam
最接近Windows资源管理器格式的功能:https://gist.github.com/AlttiRi/ee82de3728624f997b38e4fb90906914 - KeyKi

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