【问题标题】:Pretty file size in Ruby?Ruby 中漂亮的文件大小?
【发布时间】:2013-04-08 05:00:53
【问题描述】:

我正在尝试创建一种方法,将表示字节的整数转换为具有“美化”格式的字符串。

这是我半途而废的尝试:

class Integer
  def to_filesize
    {
      'B'  => 1024,
      'KB' => 1024 * 1024,
      'MB' => 1024 * 1024 * 1024,
      'GB' => 1024 * 1024 * 1024 * 1024,
      'TB' => 1024 * 1024 * 1024 * 1024 * 1024
    }.each_pair { |e, s| return "#{s / self}#{e}" if self < s }
  end
end

我做错了什么?

【问题讨论】:

    标签: ruby integer byte filesize


    【解决方案1】:

    Filesize gem 怎么样?它似乎能够从字节(和其他格式)转换成漂亮的打印值:

    示例:

    Filesize.from("12502343 B").pretty      # => "11.92 MiB"
    

    http://rubygems.org/gems/filesize

    【讨论】:

    【解决方案2】:

    我同意@David 的观点,最好使用现有的解决方案,但要回答您关于您做错了什么的问题:

    1. 主要错误是将s 除以self,而不是相反。
    2. 你真的想除以之前的s,所以将s除以1024。
    3. 进行整数运算会得到令人困惑的结果,因此请转换为浮点数。
    4. 也许是答案。

    所以:

    class Integer
      def to_filesize
        {
          'B'  => 1024,
          'KB' => 1024 * 1024,
          'MB' => 1024 * 1024 * 1024,
          'GB' => 1024 * 1024 * 1024 * 1024,
          'TB' => 1024 * 1024 * 1024 * 1024 * 1024
        }.each_pair { |e, s| return "#{(self.to_f / (s / 1024)).round(2)}#{e}" if self < s }
      end
    end
    

    让你:

    1.to_filesize # => "1.0B" 1020.to_filesize # => "1020.0B" 1024.to_filesize # => "1.0KB" 1048576.to_filesize # => "1.0MB"

    同样,我不建议实际这样做,但似乎值得纠正错误。

    【讨论】:

    • 非常感谢。我明白我做错了什么。 (我可能是个白痴)
    • 不客气。如果错误是愚蠢的证据,那么我们都对此感到内疚。这是一个好的开始。
    • 这太棒了。我宁愿使用它而不是 gem。
    • @Darshan-JosiahBarber。此实现并不总是返回预期值,因为它假定迭代将按照编写顺序发生。但是 Ruby Hashes 对键值对的顺序不提供任何保证。我花了一段时间才弄清楚为什么我总是得到错误的结果。
    • @Darshan-JosiahBarber。很高兴知道。我使用的是 1.8.7
    【解决方案3】:

    @Darshan Computing 的解决方案在这里只是部分的。由于不能保证哈希键是有序的,这种方法不能可靠地工作。您可以通过在 to_filesize 方法中执行类似的操作来解决此问题,

     conv={
          1024=>'B',
          1024*1024=>'KB',
          ...
     }
     conv.keys.sort.each { |s|
         next if self >= s
         e=conv[s]
         return "#{(self.to_f / (s / 1024)).round(2)}#{e}" if self < s }
     }
    

    这就是我最终在 Float 中为类似方法所做的,

     class Float
       def to_human
         conv={
           1024=>'B',
           1024*1024=>'KB',
           1024*1024*1024=>'MB',
           1024*1024*1024*1024=>'GB',
           1024*1024*1024*1024*1024=>'TB',
           1024*1024*1024*1024*1024*1024=>'PB',
           1024*1024*1024*1024*1024*1024*1024=>'EB'
         }
         conv.keys.sort.each { |mult|
            next if self >= mult
            suffix=conv[mult]
            return "%.2f %s" % [ self / (mult / 1024), suffix ]
         }
       end
     end
    

    【讨论】:

    • 是否有任何理由订购散列键?您提供的代码没有考虑哈希键的顺序,而是需要额​​外的资源来执行不必要的排序。此外,基准测试表明,您的解决方案至少比原始解决方案慢 1.5 倍,由@Darshan-Josiah Barber 修复并由PBEB 支持更新。
    • 这里比较了 3 个提供的解决方案(@Darshan-Josiah Barber 的 +PBEB、@Steeve McCauley 和 @ChuckCottrill 的):n = 10000000 Benchmark.bm do |x| x.report('to_filesize:') { 1.upto(n) do |i| ; (i.to_filesize); end } x.report('format_mb:') { 1.upto(n) do |i| ; (i.format_mb); end } x.report('to_human:') { 1.upto(n) do |i| ; (i.to_human); end } end user system total real to_filesize:130.470000 0.740000 131.210000 (134.722463) format_mb: 86.650000 0.620000 87.270000 ( 89.221210) to_human:199.590000 1.040000 200.630000 (203.380451)
    • @TooroSan 为什么说键的顺序无关紧要?如果它没有被订购,它可以从 1024^7 开始,如果数字是 1 MiB,它不会 >= 而不是 mult,并假设它是“EB”。需要订购密钥才能使代码正常工作。
    • @FelipeC,因为在 ruby​​ 中 Hashes enumerate their values in the order that the corresponding keys were inserted. docs.ruby-lang.org/en/2.0.0/Hash.html 所以如果你已经创建了排序哈希,你不需要再次排序。
    【解决方案4】:

    如果您将它与 Rails 一起使用 - 标准 Rails 编号帮助器呢?

    http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_human_size

    number_to_human_size(number, options = {})

    ?

    【讨论】:

      【解决方案5】:

      向 Integer 添加方法会获得积分,但这似乎更特定于文件,所以我建议在 File 中四处游荡,比如向 File 添加一个名为 .prettysize() 的方法。

      但这是一个使用迭代的替代解决方案,并且避免将单个字节打印为浮点数:-)

      def format_mb(size)
        conv = [ 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb' ];
        scale = 1024;
      
        ndx=1
        if( size < 2*(scale**ndx)  ) then
          return "#{(size)} #{conv[ndx-1]}"
        end
        size=size.to_f
        [2,3,4,5,6,7].each do |ndx|
          if( size < 2*(scale**ndx)  ) then
            return "#{'%.3f' % (size/(scale**(ndx-1)))} #{conv[ndx-1]}"
          end
        end
        ndx=7
        return "#{'%.3f' % (size/(scale**(ndx-1)))} #{conv[ndx-1]}"
      end
      

      【讨论】:

        【解决方案6】:

        这是我的解决方案:

        def filesize(size)
          units = %w[B KiB MiB GiB TiB Pib EiB ZiB]
        
          return '0.0 B' if size == 0
          exp = (Math.log(size) / Math.log(1024)).to_i
          exp += 1 if (size.to_f / 1024 ** exp >= 1024 - 0.05)
          exp = units.size - 1 if exp > units.size - 1
        
          '%.1f %s' % [size.to_f / 1024 ** exp, units[exp]]
        end
        

        与其他解决方案相比,它更简单、更高效,并且生成更合适的输出。

        格式

        所有其他方法都有他们报告1023.95 bytes错误的问题。此外,to_filesize 只是用大数字出错(它返回一个数组)。

         -       method: [     filesize,     Filesize,  number_to_human,  to_filesize ]
         -          0 B: [        0.0 B,       0.00 B,          0 Bytes,         0.0B ]
         -          1 B: [        1.0 B,       1.00 B,           1 Byte,         1.0B ]
         -         10 B: [       10.0 B,      10.00 B,         10 Bytes,        10.0B ]
         -       1000 B: [     1000.0 B,    1000.00 B,       1000 Bytes,      1000.0B ]
         -        1 KiB: [      1.0 KiB,     1.00 KiB,             1 KB,        1.0KB ]
         -      1.5 KiB: [      1.5 KiB,     1.50 KiB,           1.5 KB,        1.5KB ]
         -       10 KiB: [     10.0 KiB,    10.00 KiB,            10 KB,       10.0KB ]
         -     1000 KiB: [   1000.0 KiB,  1000.00 KiB,          1000 KB,     1000.0KB ]
         -        1 MiB: [      1.0 MiB,     1.00 MiB,             1 MB,        1.0MB ]
         -        1 GiB: [      1.0 GiB,     1.00 GiB,             1 GB,        1.0GB ]
         -  1023.95 GiB: [      1.0 TiB,  1023.95 GiB,          1020 GB,    1023.95GB ]
         -        1 TiB: [      1.0 TiB,     1.00 TiB,             1 TB,        1.0TB ]
         -        1 EiB: [      1.0 EiB,     1.00 EiB,             1 EB,        ERROR ]
         -        1 ZiB: [      1.0 ZiB,     1.00 ZiB,          1020 EB,        ERROR ]
         -        1 YiB: [   1024.0 ZiB,  1024.00 ZiB,       1050000 EB,        ERROR ]
        

        性能

        此外,它具有最佳性能(处理 100 万个数字的秒数):

         - filesize:           2.15
         - Filesize:          15.53
         - number_to_human:  139.63
         - to_filesize:        2.41
        

        【讨论】:

          【解决方案7】:

          这是一个使用log10的方法:

          def number_format(d)
             e = Math.log10(d).to_i / 3
             return '%.3f' % (d / 1000 ** e) + ['', ' k', ' M', ' G'][e]
          end
          
          s = number_format(9012345678.0)
          puts s == '9.012 G'
          

          https://ruby-doc.org/core/Math.html#method-c-log10

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-08
            • 1970-01-01
            • 2011-09-22
            • 2012-01-06
            • 1970-01-01
            • 2011-03-23
            相关资源
            最近更新 更多