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

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个回答

8

截至2020年,您可以使用文件大小的npm包,它支持IEC(幂1024,默认值)、SI(幂1000)和JEDEC(替代SI单位表示法)格式。

npm install file-size

import filesize from "filesize";

// outputs: 186.46 MB
filesize(186457865).human('si');

// outputs: 177.82 MiB
filesize(186457865).human();

https://www.npmjs.com/package/file-size


3
注意:这个库 file-size 很少被使用,而且并没有得到真正的支持(上一次发布是7年前)。建议查看我的答案中提到的 filesize。https://dev59.com/xWkv5IYBdhLWcg3wsCzi#65124044 - James Mudd

7

这是另一个国际化的实现,使用TypeScript编写:

const UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte']
const BYTES_PER_KB = 1000


/**
 * Format bytes as human-readable text.
 *
 * @param sizeBytes Number of bytes.
 *
 * @return Formatted string.
 */
export function humanFileSize(sizeBytes: number | bigint): string {
    let size = Math.abs(Number(sizeBytes))

    let u = 0
    while(size >= BYTES_PER_KB && u < UNITS.length-1) {
        size /= BYTES_PER_KB
        ++u
    }

    return new Intl.NumberFormat([], {
        style: 'unit',
        unit: UNITS[u],
        unitDisplay: 'short',
        maximumFractionDigits: 1,
    }).format(size)
}

用语言代码替换[],例如fr,可以强制使用非默认的本地化设置。

console.log(humanFileSize(0))
console.log(humanFileSize(9))
console.log(humanFileSize(99))
console.log(humanFileSize(999))
console.log(humanFileSize(1000))
console.log(humanFileSize(1001))
console.log(humanFileSize(1023))
console.log(humanFileSize(1024))
console.log(humanFileSize(1025))
console.log(humanFileSize(100_000))
console.log(humanFileSize(1_000_000))
console.log(humanFileSize(1_000_000_000))
console.log(humanFileSize(1_000_000_000_000))
console.log(humanFileSize(1_000_000_000_000_000))
console.log(humanFileSize(1_000_000_000_000_000_000))

// fr
0 o
9 o
99 o
999 o
1 ko
1 ko
1 ko
1 ko
1 ko
100 ko
1Mo
1Go
1To
1Po
1000Po

// en-US
0 byte
9 byte
99 byte
999 byte
1 kB
1 kB
1 kB
1 kB
1 kB
100 kB
1 MB
1 GB
1 TB
1 PB
1,000 PB

现在您可以使用Intl.NumberFormat 自动进行单位转换。例如:

const sizeFormatter = new Intl.NumberFormat([], {
    style: 'unit',
    unit: 'byte',
    notation: "compact",
    unitDisplay: "narrow",
})

console.log(sizeFormatter.format(0))
console.log(sizeFormatter.format(1))
console.log(sizeFormatter.format(999))
console.log(sizeFormatter.format(1000))
console.log(sizeFormatter.format(1023))
console.log(sizeFormatter.format(1024))
console.log(sizeFormatter.format(1024**2))
console.log(sizeFormatter.format(1024**3))
console.log(sizeFormatter.format(1024**4))
console.log(sizeFormatter.format(1024**5))
console.log(sizeFormatter.format(1024**6))

...但单位有点奇怪,比如1024**41.1BB,我猜是“十亿字节”,尽管它在技术上是正确的,但我认为没有人会使用它。


1
我喜欢你如何解决了“BB”问题! - Ryan Norooz
1
我喜欢你如何解决了“BB”问题! - Ryan Norooz

6

虽然我的回答可能晚了,但我认为它会对某些人有所帮助。

度量单位前缀:

/**
 * Format file size in metric prefix
 * @param fileSize
 * @returns {string}
 */
const formatFileSizeMetric = (fileSize) => {
  let size = Math.abs(fileSize);

  if (Number.isNaN(size)) {
    return 'Invalid file size';
  }

  if (size === 0) {
    return '0 bytes';
  }

  const units = ['bytes', 'kB', 'MB', 'GB', 'TB'];
  let quotient = Math.floor(Math.log10(size) / 3);
  quotient = quotient < units.length ? quotient : units.length - 1;
  size /= (1000 ** quotient);

  return `${+size.toFixed(2)} ${units[quotient]}`;
};

二进制前缀:

/**
 * Format file size in binary prefix
 * @param fileSize
 * @returns {string}
 */
const formatFileSizeBinary = (fileSize) => {
  let size = Math.abs(fileSize);

  if (Number.isNaN(size)) {
    return 'Invalid file size';
  }

  if (size === 0) {
    return '0 bytes';
  }

  const units = ['bytes', 'kiB', 'MiB', 'GiB', 'TiB'];
  let quotient = Math.floor(Math.log2(size) / 10);
  quotient = quotient < units.length ? quotient : units.length - 1;
  size /= (1024 ** quotient);

  return `${+size.toFixed(2)} ${units[quotient]}`;
};

示例:

// Metrics prefix
formatFileSizeMetric(0)      // 0 bytes
formatFileSizeMetric(-1)     // 1 bytes
formatFileSizeMetric(100)    // 100 bytes
formatFileSizeMetric(1000)   // 1 kB
formatFileSizeMetric(10**5)  // 10 kB
formatFileSizeMetric(10**6)  // 1 MB
formatFileSizeMetric(10**9)  // 1GB
formatFileSizeMetric(10**12) // 1 TB
formatFileSizeMetric(10**15) // 1000 TB

// Binary prefix
formatFileSizeBinary(0)     // 0 bytes
formatFileSizeBinary(-1)    // 1 bytes
formatFileSizeBinary(1024)  // 1 kiB
formatFileSizeBinary(2048)  // 2 kiB
formatFileSizeBinary(2**20) // 1 MiB
formatFileSizeBinary(2**30) // 1 GiB
formatFileSizeBinary(2**40) // 1 TiB
formatFileSizeBinary(2**50) // 1024 TiB

5
一个简单而短小的“漂亮字节”函数,适用于SI系统,无需进行不必要的分数舍入。
实际上,由于数字大小应该是人类可读的,“千分之一的分数”显示已经不再人性化。
小数位数默认为2,但可以在调用函数时修改为其他值。通常显示的是默认的2位小数。
代码简短,使用了数字字符串三元组的方法。

// Simple Pretty Bytes with SI system
// Without fraction rounding

function numberPrettyBytesSI(Num=0, dec=2){
if (Num<1000) return Num+" Bytes";
Num =("0".repeat((Num+="").length*2%3)+Num).match(/.{3}/g);
return Number(Num[0])+"."+Num[1].substring(0,dec)+" "+"  kMGTPEZY"[Num.length]+"B";
}

console.log(numberPrettyBytesSI(0));
console.log(numberPrettyBytesSI(500));
console.log(numberPrettyBytesSI(1000));
console.log(numberPrettyBytesSI(15000));
console.log(numberPrettyBytesSI(12345));
console.log(numberPrettyBytesSI(123456));
console.log(numberPrettyBytesSI(1234567));
console.log(numberPrettyBytesSI(12345678));


4
这是我的代码,适用于非常大的文件 -_-。
function formatFileSize(size)
{
    var sizes = [' Bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'];
    for (var i = 1; i < sizes.length; i++)
    {
        if (size < Math.pow(1024, i)) return (Math.round((size/Math.pow(1024, i-1))*100)/100) + sizes[i-1];
    }
    return size;
}

它结合了循环和指数运算的性能损失,同时又很难阅读。我真的看不出有什么意义。 - spectras
2
那就不用它呗。只是客户端的 CPU 被使用了,谁在乎呢 ;) - fiffy
2
@fiffy 嗯,客户端 CPU 也很珍贵,特别是在移动设备上和处理复杂应用程序时。 :) - Raito

4

基于 cocco的答案,但稍微简化了一下(老实说,我只保留了自己觉得舒适的部分),并且不显示尾随零,但仍支持0,希望对其他人有用:

function fileSizeSI(size) {
    var e = (Math.log(size) / Math.log(1e3)) | 0;
    return +(size / Math.pow(1e3, e)).toFixed(2) + ' ' + ('kMGTPEZY'[e - 1] || '') + 'B';
}


// test:
document.write([0, 23, 4322, 324232132, 22e9, 64.22e12, 76.22e15, 64.66e18, 77.11e21, 22e24].map(fileSizeSI).join('<br>'));


3
1551859712 / 1024 = 1515488
1515488 / 1024 = 1479.96875
1479.96875 / 1024 = 1.44528198242188

你的解决方案是正确的。重要的是要意识到,为了从15518597121.5,你必须进行1000的除法运算,但字节是按照二进制到十进制的1024个块来计算的,因此吉比特值较小。


@Eli... 是的,看起来是这样。我猜我之前预期的是“1.5”,因为它是1551859712,但那意味着我是在十进制而不是二进制中。 - Hristo

3

我只晚了10年!对于ES6来说

function humanReadableSize(bytes) {
    let size = parseInt(data)
    for (let unit of ['b', 'Kb', 'Mb', 'Gb']) {
        if (size < 1024) return `${size.toFixed(2)} ${unit}`
        size /= 1024.0
    }
}

2

我发现@cocco的回答很有趣,但是我对它有以下问题:

  1. 不要修改本地类型或者你没有拥有的类型
  2. 编写干净易读的代码,让缩小器为机器优化代码
  3. (对于TypeScript用户的额外奖励)与TypeScript不兼容

TypeScript:

 /**
 * Describes manner by which a quantity of bytes will be formatted.
 */
enum ByteFormat {
  /**
   * Use Base 10 (1 kB = 1000 bytes). Recommended for sizes of files on disk, disk sizes, bandwidth.
   */
  SI = 0,
  /**
   * Use Base 2 (1 KiB = 1024 bytes). Recommended for RAM size, size of files on disk.
   */
  IEC = 1
}

/**
 * Returns a human-readable representation of a quantity of bytes in the most reasonable unit of magnitude.
 * @example
 * formatBytes(0) // returns "0 bytes"
 * formatBytes(1) // returns "1 byte"
 * formatBytes(1024, ByteFormat.IEC) // returns "1 KiB"
 * formatBytes(1024, ByteFormat.SI) // returns "1.02 kB"
 * @param size The size in bytes.
 * @param format Format using SI (Base 10) or IEC (Base 2). Defaults to SI.
 * @returns A string describing the bytes in the most reasonable unit of magnitude.
 */
function formatBytes(
  value: number,
  format: ByteFormat = ByteFormat.SI
) {
  const [multiple, k, suffix] = (format === ByteFormat.SI
    ? [1000, 'k', 'B']
    : [1024, 'K', 'iB']) as [number, string, string]
  // tslint:disable-next-line: no-bitwise
  const exp = (Math.log(value) / Math.log(multiple)) | 0
  // or, if you'd prefer not to use bitwise expressions or disabling tslint rules, remove the line above and use the following:
  // const exp = value === 0 ? 0 : Math.floor(Math.log(value) / Math.log(multiple)) 
  const size = Number((value / Math.pow(multiple, exp)).toFixed(2))
  return (
    size +
    ' ' +
    (exp 
       ? (k + 'MGTPEZY')[exp - 1] + suffix 
       : 'byte' + (size !== 1 ? 's' : ''))
  )
}

// example
[0, 1, 1024, Math.pow(1024, 2), Math.floor(Math.pow(1024, 2) * 2.34), Math.pow(1024, 3), Math.floor(Math.pow(1024, 3) * 892.2)].forEach(size => {
  console.log('Bytes: ' + size)
  console.log('SI size: ' + formatBytes(size))
  console.log('IEC size: ' + formatBytes(size, 1) + '\n')
});

1

使用新的“模板文字类型”对@Andrew V答案的typescript版本进行翻译。

export const humanFileSize = (bytes: number): `${number} ${'B' | 'KB' | 'MB' | 'GB' | 'TB'}` => {
    const index = Math.floor(Math.log(bytes) / Math.log(1024));
    return `${Number((bytes / Math.pow(1024, index)).toFixed(2)) * 1} ${(['B', 'KB', 'MB', 'GB', 'TB'] as const)[index]}`;
};

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