又是发现了项目中的一个工具方法 , 因为数据库里面存的直接就是一个bigint数据,也就是文件的Byte大小,返回给前端展示的时候需要处理到最大单位,然后保留小数 , 给他优化了一下写法 (原来的写法是用/1024这么搞的)
涉及到2的指数真的是非常好的一个东西 , 可以方便的用位运算 , 或者直接调用内置位运算方法
这里因为是1024一个进位 , 所以正好是10个bit位 , long是64bit , 所以最多其实表示的是 EB级别的
首先把60个bit位给他进位掉 , long是带符号的 , 再剩下就是3个bit也就是7 , 因为后面也都是1 , 最大值就是8EB少个byte
文件大小的进位是这样的 : “B”, “KB”, “MB”, “GB”, “TB”, “PB”, “EB”, “ZB”, “YB” , 这里ZB和YB用不着了, 不过留着也没事
虽然之前那个老方法里面一直 /1024 和 % 1024也不是不能用 (说起来/1024是可以优化为 << 10的,性能更好,但是实际几次计算而已,耗时忽略不计) ,所以虽然改了方法 , 但是性能貌似没有变快 , 主要还是 BigDecimal 的锅,bigDecimal除法比起直接计算肯定慢 . 然后BigDecimal也不知道是除法慢还是toString的时候慢 ,
- 然后第一版小优化 bigDecimal , 不过大差不差,
- 然后试了试自己做除法和做小数点 , 能挽回点性能,但是代码又变冗余了 , 没有一个两行代码简洁(外加一个辅助的数组)
代码
方法一和方法二
// ============================== 方法1
static String[] tails = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
public static String getPrintSize_bigDecimal(long size) {
int i = (63 - Long.numberOfLeadingZeros(size)) / 10;
return BigDecimal.valueOf(size).divide(BigDecimal.valueOf(1L << (i * 10)), 2, RoundingMode.HALF_UP) + tails[i];
}
static BigDecimal[] numbers = new BigDecimal[]{BigDecimal.ONE, BigDecimal.valueOf(1L << 10), BigDecimal.valueOf(1L << 20),
BigDecimal.valueOf(1L << 30), BigDecimal.valueOf(1L << 40), BigDecimal.valueOf(1L << 50), BigDecimal.valueOf(1L << 60)};
public static String getPrintSize_bigDecimal_v2(long size) {
int i = (63 - Long.numberOfLeadingZeros(size)) / 10;
return BigDecimal.valueOf(size).divide(numbers[i], 2, RoundingMode.HALF_UP) + tails[i];
}
方法三
static char[] unit = new char[]{' ', 'K', 'M', 'G', 'T', 'P', 'E'};
public static String getPrintSize(long size) {
if (size < 1024) {
return size + ".00B";
}
int i = (63 - Long.numberOfLeadingZeros(size)) / 10;
long base = 1L << (i * 10);
char[] resItem = new char[]{'0', '0', '0', '0', '.', '0', '0', ' ', 'B'};// . 是 4 ,
resItem[7] = unit[i];
long leftPart = size / base;
int start;
for (start = 3; leftPart > 0; start--) {
resItem[start] = (char) (leftPart % 10 + '0');
leftPart /= 10;
}
// long rightPart = (size * 100 / base) % 100; // 不四舍五入 , 直接舍弃第三位小数
long rightPart = ((size * 1000 / base) + 5) / 10 % 100; // 这个是四舍五入的
for (int rightStart = 6; rightPart > 0; rightStart--) {
resItem[rightStart] = (char) (rightPart % 10 + '0');
rightPart /= 10;
}
return new String(resItem, start + 1, 8 - start);
}
前置了解
如果看过这个 numberOfLeadingZeros 方法可以跳过
特殊处理部分 : 负数的首个bit是-1,所以直接返回32 , 0的话纯0 , 所以是32
正数部分 : 简单二分法思想来找到首位1的位置,刚开始的步长是int的bit位数(32)的一半16,利用和2的指数比来确定首位1的位置 . 如果在左侧 , 前置0数量-16 , 右移16位计算,右侧变成原来的前16位,步长变为8,一直重复.
public static int numberOfLeadingZeros(int i) {
// HD, Count leading 0's
if (i <= 0)
return i == 0 ? 32 : 0;
int n = 31;
if (i >= 1 << 16) { n -= 16; i >>>= 16; }
if (i >= 1 << 8) { n -= 8; i >>>= 8; }
if (i >= 1 << 4) { n -= 4; i >>>= 4; }
if (i >= 1 << 2) { n -= 2; i >>>= 2; }
return n - (i >>> 1);
}