ORC与Parquet压缩分析

news2024/11/27 22:24:41

ORC与Parquet压缩分析

@date:2023年6月14日

文章目录

  • ORC与Parquet压缩分析
    • 压测环境
      • 数据schema
    • 数据实验
    • 压缩结果
    • 文件使用建议
    • 附录
      • 编译hadoop-lzo
        • 编译前提
        • 编译程中出现的错误
        • 结果文件
      • file-compress.jar源码
        • ReadWriterOrc类
        • NativeParquet类
        • FileUtil类

压测环境

  • OS:CentOS 6.5
  • JDK:1.8
  • 内存:256G
  • 磁盘:HDD
  • CPU:Dual 8-core Intel® Xeon® CPU (32 Hyper-Threads) E5-2630 v3 @ 2.40GHz

通过Orc和Parquet原生方式进行数据写入,并采用以下算法进行压缩测试

  • lzo
  • lz4(lz4_raw)
  • Zstandard
  • snappy

数据schema

尽可能的保持parquet与ORC的schema一致。

parquet

        MessageType schema = MessageTypeParser.parseMessageType("message schema {\n" +
                " required INT64 long_value;\n" +
                " required double double_value;\n" +
                " required boolean boolean_value;\n" +
                " required binary string_value (UTF8);\n" +
                " required binary decimal_value (DECIMAL(32,18));\n" +
                " required INT64 time_value;\n" +
                " required INT64 time_instant_value;\n" +
                " required INT64 date_value;\n" +
                "}");

orc

        TypeDescription readSchema = TypeDescription.createStruct()
                .addField("long_value", TypeDescription.createLong())
                .addField("double_value", TypeDescription.createDouble())
                .addField("boolean_value", TypeDescription.createBoolean())
                .addField("string_value", TypeDescription.createString())
                .addField("decimal_value", TypeDescription.createDecimal().withScale(18))
                .addField("time_value", TypeDescription.createTimestamp())
                .addField("time_instant_value", TypeDescription.createTimestampInstant())
                .addField("date_value", TypeDescription.createDate());

数据实验

将工程打包成uber JAR,通过java命令执行

⚠️对parquet使用lzo时需要额外的配置

  1. 在使用lzo的时候需要在系统上安装Lzo 2.x

    # 查询是否有lzo安装包
    [root@demo ~]# rpm -q lzo
    
    # yum方式安装
    yum install lzo
    
    # rpm方式 下载lzo的rpm包
    rpm -ivh lzo-2.06-8.el7.x86_64.rpm
    
    # 源码编译安装
    # 1源码编译的依赖
    yum -y install lzo-devel zlib-devel gcc autoconf automake libtool
    # 解压缩源码
    tar -zxvf lzo-2.10.tar.gz -C ../source
    # 配置和安装
    cd ~/source/lzo-2.10
    ./configure --enable-shared --prefix /usr/local/lzo-2.1
    make && sudo make install
    
  2. 由于GPLNativeCodeLoader类在加载的时候默认lib的目录是/native/Linux-amd64-64/lib,所以需要使用的lib copy进去。

    -rw-r--r-- 1 root root  112816 Jun 13 17:57 hadoop-lzo-0.4.20.jar
    -rw-r--r-- 1 root root  117686 Jun 13 17:17 libgplcompression.a
    -rw-r--r-- 1 root root    1157 Jun 13 17:17 libgplcompression.la
    -rwxr-xr-x 1 root root   75368 Jun 13 17:17 libgplcompression.so
    -rwxr-xr-x 1 root root   75368 Jun 13 17:17 libgplcompression.so.0
    -rwxr-xr-x 1 root root   75368 Jun 13 17:17 libgplcompression.so.0.0.0
    -rw-r--r-- 1 root root 1297096 Jun 13 17:17 libhadoop.a
    -rw-r--r-- 1 root root 1920190 Jun 13 17:17 libhadooppipes.a
    -rwxr-xr-x 1 root root  765897 Jun 13 17:17 libhadoop.so
    -rwxr-xr-x 1 root root  765897 Jun 13 17:17 libhadoop.so.1.0.0
    -rw-r--r-- 1 root root  645484 Jun 13 17:17 libhadooputils.a
    -rw-r--r-- 1 root root  438964 Jun 13 17:17 libhdfs.a
    -rwxr-xr-x 1 root root  272883 Jun 13 17:17 libhdfs.so
    -rwxr-xr-x 1 root root  272883 Jun 13 17:17 libhdfs.so.0.0.0
    -rw-r--r-- 1 root root  290550 Jun 13 17:17 liblzo2.a
    -rw-r--r-- 1 root root     929 Jun 13 17:17 liblzo2.la
    -rwxr-xr-x 1 root root  202477 Jun 13 17:17 liblzo2.so
    -rwxr-xr-x 1 root root  202477 Jun 13 17:17 liblzo2.so.2
    -rwxr-xr-x 1 root root  202477 Jun 13 17:17 liblzo2.so.2.0.0
    -rw-r--r-- 1 root root  246605 Jun 13 17:17 libsigar-amd64-linux.so
    
  3. 在执行java需要手动配置java.library.path和引用hadoop-lzo-0.4.20.jar(没有找到将其一并打包到工程uber.jar里面的方式) hadoop-lzo编译

 # 命令解释
 java -cp file-compress.jar com.donny.orc.ReadWriterOrc {数据记录数} {压缩简称}
 # ORC未压缩
 java -cp file-compress.jar com.donny.orc.ReadWriterOrc 10000 none
 # ORC采用lzo压缩
 java -cp file-compress.jar com.donny.orc.ReadWriterOrc 10000 lzo
 # ORC采用lz4压缩
 java -cp file-compress.jar com.donny.orc.ReadWriterOrc 10000 lz4
 # ORC采用zstd压缩
 java -cp file-compress.jar com.donny.orc.ReadWriterOrc 10000 zstd
 # ORC采用snappy压缩
 java -cp file-compress.jar com.donny.orc.ReadWriterOrc 10000 snappy
 
 # Parquet未压缩
 java -cp file-compress.jar com.donny.parquet.NativeParquet 10000 none
 # Parquet采用lzo压缩
 java -Djava.library.path=/native/Linux-amd64-64/lib -cp file-compress.jar:hadoop-lzo-0.4.20.jar com.donny.parquet.NativeParquet 300000000 lzo
 # Parquet采用lz4压缩
 java -cp file-compress.jar com.donny.parquet.NativeParquet 10000 lz4_raw
 # Parquet采用zstd压缩
 java -cp file-compress.jar com.donny.parquet.NativeParquet 10000 zstd
 # Parquet采用snappy压缩
 java -cp file-compress.jar com.donny.parquet.NativeParquet 10000 snappy

压缩结果

在这里插入图片描述
在这里插入图片描述

文件使用建议

在数仓和数据湖的场景中,数据一般按以下结构进行分层存储:
在这里插入图片描述

  • 贴源层:该层是将数据源中的数据直接抽取过来的,数据类型以文本为主,需要保持数据原样。数据不会发生变化,在初次清洗之后被读取的概率也不大,可以采用ORC格式文件外加Zstandard存储。以控制存储最小。

  • 加工汇总层:该层是数仓的数据加工组织阶段,会做一些数据的清洗和规范化的操作,比如去除空数据、脏数据、离群值等。采用ORC能够较好支持该阶段的数据ACID需求。数据压缩可以采用Lz4,以达到最优的性价比。

  • 应用层:该层的数据是供数据分析和数据挖掘使用,比如常用的数据报表就是存在这里。此时的数据已经具备了对外部的直接使用的能力。数据的可能具备了一定层度的结构化,而Parquet在实现复杂的嵌套结构方面,比ORC更具有优势。所以该层一般采用Parquet,处于该层的数据一般变化不大,可以采用Zstandard压缩。

    主要考虑的因素

    • 数据的变化性
    • 数据的结构复杂性
    • 数据的读写高效性
    • 数据压缩率

附录

编译hadoop-lzo

编译前提

  • 安装JDK1.8+
  • 安装maven
  • OS已经安装lzo的库
  • 下载源码包 https://github.com/twitter/hadoop-lzo/releases/tag/release-0.4.20
# 解压安装包
tar -zxvf hadoop-lzo-0.4.20.tar.gz -C /opt/software/hadoop-lzo/;
# 重命名
mv hadoop-lzo-release-0.4.20 hadoop-lzo-0.4.20;
# 进入项目目录
cd /opt/software/hadoop-lzo/hadoop-lzo-0.4.20;
# 进行编译
mvn clean package

可以通过对root模块的pom.xml进行修改来对Hadoop进行适配。一般开源的不需要调整。

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
   <!-- <hadoop.current.version>2.6.4</hadoop.current.version>-->
    <hadoop.current.version>2.9.2</hadoop.current.version>
    <hadoop.old.version>1.0.4</hadoop.old.version>
</properties>

编译程中出现的错误

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-antrun-plugin:1.7:run (build-native-non-win) on project hadoop-lzo: An Ant BuildException has occured: exec returned: 1
[ERROR] around Ant part ...<exec failonerror="true" dir="${build.native}" executable="sh">... @ 16:66 in /opt/software/hadoop-lzo/hadoop-lzo-0.4.20/target/antrun/build-build-native-non-win.xml
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

通过配置JAVA_HOME环境变量解决

结果文件

  • target/hadoop-lzo-0.4.20.jar
  • target/native/Linux-amd64-64/lib下的文件

file-compress.jar源码

ReadWriterOrc类

package com.donny.orc;


import com.donny.base.utils.FileUtil;
import com.donny.parquet.NativeParquet;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.common.type.HiveDecimal;
import org.apache.hadoop.hive.ql.exec.vector.*;
import org.apache.hadoop.hive.ql.io.sarg.PredicateLeaf;
import org.apache.hadoop.hive.ql.io.sarg.SearchArgumentFactory;
import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable;
import org.apache.orc.*;
import org.apache.parquet.hadoop.metadata.CompressionCodecName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * <dependency>
 * <groupId>org.apache.orc</groupId>
 * <artifactId>orc-core</artifactId>
 * <version>1.8.3</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>org.apache.hadoop</groupId>
 * <artifactId>hadoop-client</artifactId>
 * <version>2.9.2</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>org.lz4</groupId>
 * <artifactId>lz4-java</artifactId>
 * <version>1.8.0</version>
 * </dependency>
 *
 * @author 1792998761@qq.com
 * @description
 * @date 2023/6/8
 */
public class ReadWriterOrc {

    private static final Logger LOG = LoggerFactory.getLogger(ReadWriterOrc.class);
    public static String path = System.getProperty("user.dir") + File.separator + "demo.orc";
    public static CompressionKind codecName;
    static int records;

    public static void main(String[] args) throws IOException {
        // 写入记录数
        String recordNum = args[0];
        records = Integer.parseInt(recordNum);
        if (records < 10000 || records > 300000000) {
            LOG.error("压缩记录数范围是10000~300000000");
            return;
        }
        // 压缩算法
        String compressionCodecName = args[1];
        switch (compressionCodecName.toLowerCase()) {
            case "none":
                codecName = CompressionKind.NONE;
                break;
            case "lzo":
                codecName = CompressionKind.LZO;
                break;
            case "lz4":
                codecName = CompressionKind.LZ4;
                break;
            case "zstd":
                codecName = CompressionKind.ZSTD;
                break;
            default:
                LOG.error("目前压缩算法支持none、lzo、lz4、zstd");
                return;
        }

        long t1 = System.currentTimeMillis();
        writerToOrcFile();
        long duration = System.currentTimeMillis() - t1;

        String fileSize = "";
        File afterFile = new File(path);
        if (afterFile.exists() && afterFile.isFile()) {
            fileSize = FileUtil.fileSizeByteConversion(afterFile.length(), 2);
        }
        LOG.info("Using the {} compression algorithm to write {} pieces of data takes time: {}s, file size is {}.",
                compressionCodecName, recordNum, (duration / 1000), fileSize);
    }

    public static void readFromOrcFile() throws IOException {
        Configuration conf = new Configuration();

        TypeDescription readSchema = TypeDescription.createStruct()
                .addField("long_value", TypeDescription.createLong())
                .addField("double_value", TypeDescription.createDouble())
                .addField("boolean_value", TypeDescription.createBoolean())
                .addField("string_value", TypeDescription.createString())
                .addField("decimal_value", TypeDescription.createDecimal().withScale(18))
                .addField("time_value", TypeDescription.createTimestamp())
                .addField("time_instant_value", TypeDescription.createTimestampInstant())
                .addField("date_value", TypeDescription.createDate());


        Reader reader = OrcFile.createReader(new Path(path),
                OrcFile.readerOptions(conf));
        OrcFile.WriterVersion writerVersion = reader.getWriterVersion();
        System.out.println("writerVersion=" + writerVersion);
        Reader.Options readerOptions = new Reader.Options()
                .searchArgument(
                        SearchArgumentFactory
                                .newBuilder()
                                .between("long_value", PredicateLeaf.Type.LONG, 0L, 1024L)
                                .build(),
                        new String[]{"long_value"}
                );

        RecordReader rows = reader.rows(readerOptions.schema(readSchema));

        VectorizedRowBatch batch = readSchema.createRowBatch();
        int count = 0;
        while (rows.nextBatch(batch)) {
            LongColumnVector longVector = (LongColumnVector) batch.cols[0];
            DoubleColumnVector doubleVector = (DoubleColumnVector) batch.cols[1];
            LongColumnVector booleanVector = (LongColumnVector) batch.cols[2];
            BytesColumnVector stringVector = (BytesColumnVector) batch.cols[3];
            DecimalColumnVector decimalVector = (DecimalColumnVector) batch.cols[4];
            TimestampColumnVector dateVector = (TimestampColumnVector) batch.cols[5];
            TimestampColumnVector timestampVector = (TimestampColumnVector) batch.cols[6];
            count++;
            if (count == 1) {
                for (int r = 0; r < batch.size; r++) {
                    long longValue = longVector.vector[r];
                    double doubleValue = doubleVector.vector[r];
                    boolean boolValue = booleanVector.vector[r] != 0;
                    String stringValue = stringVector.toString(r);
                    HiveDecimalWritable hiveDecimalWritable = decimalVector.vector[r];
                    long time1 = dateVector.getTime(r);
                    Date date = new Date(time1);
                    String format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
                    long time = timestampVector.time[r];
                    int nano = timestampVector.nanos[r];
                    Timestamp timestamp = new Timestamp(time);
                    timestamp.setNanos(nano);
                    System.out.println(longValue + ", " + doubleValue + ", " + boolValue + ", " + stringValue + ", " + hiveDecimalWritable.getHiveDecimal().toFormatString(18) + ", " + format + ", " + timestamp);

                }
            }

        }
        System.out.println("count=" + count);
        rows.close();
    }


    public static void writerToOrcFile() throws IOException {

        Configuration configuration = new Configuration();
        configuration.set("orc.overwrite.output.file", "true");
        TypeDescription schema = TypeDescription.createStruct()
                .addField("long_value", TypeDescription.createLong())
                .addField("double_value", TypeDescription.createDouble())
                .addField("boolean_value", TypeDescription.createBoolean())
                .addField("string_value", TypeDescription.createString())
                .addField("decimal_value", TypeDescription.createDecimal().withScale(18))
                .addField("time_value", TypeDescription.createTimestamp())
                .addField("time_instant_value", TypeDescription.createTimestampInstant())
                .addField("date_value", TypeDescription.createDate());

        Writer writer = OrcFile.createWriter(new Path(path),
                OrcFile.writerOptions(configuration)
                        .setSchema(schema)
                        .stripeSize(67108864)
                        .bufferSize(64 * 1024)
                        .blockSize(128 * 1024 * 1024)
                        .rowIndexStride(10000)
                        .blockPadding(true)
                        .compress(codecName));

        //根据 列数和默认的1024 设置创建一个batch
        VectorizedRowBatch batch = schema.createRowBatch();
        LongColumnVector longVector = (LongColumnVector) batch.cols[0];
        DoubleColumnVector doubleVector = (DoubleColumnVector) batch.cols[1];
        LongColumnVector booleanVector = (LongColumnVector) batch.cols[2];
        BytesColumnVector stringVector = (BytesColumnVector) batch.cols[3];
        DecimalColumnVector decimalVector = (DecimalColumnVector) batch.cols[4];
        TimestampColumnVector dateVector = (TimestampColumnVector) batch.cols[5];
        TimestampColumnVector timestampVector = (TimestampColumnVector) batch.cols[6];
        for (int r = 0; r < records; ++r) {
            int row = batch.size++;
            longVector.vector[row] = r;
            doubleVector.vector[row] = r;
            booleanVector.vector[row] = r % 2;
            stringVector.setVal(row, UUID.randomUUID().toString().getBytes());
            BigDecimal bigDecimal = BigDecimal.valueOf((double) r / 3).setScale(18, RoundingMode.DOWN);
            HiveDecimal hiveDecimal = HiveDecimal.create(bigDecimal).setScale(18);
            decimalVector.set(row, hiveDecimal);
            long time = new Date().getTime();
            Timestamp timestamp = new Timestamp(time);
            dateVector.set(row, timestamp);
            timestampVector.set(row, timestamp);

            if (batch.size == batch.getMaxSize()) {
                writer.addRowBatch(batch);
                batch.reset();
            }
        }
        if (batch.size != 0) {
            writer.addRowBatch(batch);
            batch.reset();
        }
        writer.close();
    }
}

NativeParquet类

package com.donny.parquet;

import com.donny.base.utils.FileUtil;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.parquet.column.ParquetProperties;
import org.apache.parquet.example.data.Group;
import org.apache.parquet.example.data.GroupFactory;
import org.apache.parquet.example.data.simple.SimpleGroupFactory;
import org.apache.parquet.hadoop.ParquetFileWriter;
import org.apache.parquet.hadoop.ParquetReader;
import org.apache.parquet.hadoop.ParquetWriter;
import org.apache.parquet.hadoop.example.GroupReadSupport;
import org.apache.parquet.hadoop.example.GroupWriteSupport;
import org.apache.parquet.hadoop.metadata.CompressionCodecName;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.MessageTypeParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.Random;
import java.util.UUID;

/**
 * <dependency>
 * <groupId>org.lz4</groupId>
 * <artifactId>lz4-java</artifactId>
 * <version>1.8.0</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>org.apache.hadoop</groupId>
 * <artifactId>hadoop-client</artifactId>
 * <version>2.9.2</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>org.apache.parquet</groupId>
 * <artifactId>parquet-avro</artifactId>
 * <version>1.13.1</version>
 * </dependency>
 *
 * <dependency>
 * <groupId>org.apache.avro</groupId>
 * <artifactId>avro</artifactId>
 * <version>1.11.1</version>
 * </dependency>
 *
 * @author 1792998761@qq.com
 * @description
 * @date 2023/6/12
 */
public class NativeParquet {
    private static final Logger LOG = LoggerFactory.getLogger(NativeParquet.class);

    public static String path = System.getProperty("user.dir") + File.separator + "demo.parquet";

    public static void main(String[] args) throws IOException {
        // 写入记录数
        String recordNum = args[0];
        int records = Integer.parseInt(recordNum);
        if (records < 10000 || records > 300000000) {
            LOG.error("压缩记录数范围是10000~300000000");
            return;
        }
        // 压缩算法
        String compressionCodecName = args[1];
        CompressionCodecName codecName;
        switch (compressionCodecName.toLowerCase()) {
            case "none":
                codecName = CompressionCodecName.UNCOMPRESSED;
                break;
            case "lzo":
                codecName = CompressionCodecName.LZO;
                break;
            case "lz4":
                codecName = CompressionCodecName.LZ4;
                break;
            case "lz4_raw":
                codecName = CompressionCodecName.LZ4_RAW;
                break;
            case "zstd":
                codecName = CompressionCodecName.ZSTD;
                break;
            default:
                LOG.error("目前压缩算法支持none、lzo、lz4、lz4_raw、zstd");
                return;
        }
        long t1 = System.currentTimeMillis();

        MessageType schema = MessageTypeParser.parseMessageType("message schema {\n" +
                " required INT64 long_value;\n" +
                " required double double_value;\n" +
                " required boolean boolean_value;\n" +
                " required binary string_value (UTF8);\n" +
                " required binary decimal_value (DECIMAL(32,18));\n" +
                " required INT64 time_value;\n" +
                " required INT64 time_instant_value;\n" +
                " required INT64 date_value;\n" +
                "}");

        GroupFactory factory = new SimpleGroupFactory(schema);


        Path dataFile = new Path(path);

        Configuration configuration = new Configuration();
        GroupWriteSupport.setSchema(schema, configuration);
        GroupWriteSupport writeSupport = new GroupWriteSupport();

        ParquetWriter<Group> writer = new ParquetWriter<>(
                dataFile,
                ParquetFileWriter.Mode.OVERWRITE,
                writeSupport,
                codecName,
                ParquetWriter.DEFAULT_BLOCK_SIZE,
                ParquetWriter.DEFAULT_PAGE_SIZE,
                ParquetWriter.DEFAULT_PAGE_SIZE, /* dictionary page size */
                ParquetWriter.DEFAULT_IS_DICTIONARY_ENABLED,
                ParquetWriter.DEFAULT_IS_VALIDATING_ENABLED,
                ParquetProperties.WriterVersion.PARQUET_1_0,
                configuration
        );
        Group group;
        for (int i = 0; i < records; i++) {
            group = factory.newGroup();
            group.append("long_value", new Random().nextLong())
                    .append("double_value", new Random().nextDouble())
                    .append("boolean_value", new Random().nextBoolean())
                    .append("string_value", UUID.randomUUID().toString())
                    .append("decimal_value", BigDecimal.valueOf((double) i / 3).setScale(18, RoundingMode.DOWN).toString())
                    .append("time_value", new Date().getTime())
                    .append("time_instant_value", new Date().getTime())
                    .append("date_value", new Date().getTime());
            writer.write(group);
        }

        writer.close();

//        GroupReadSupport readSupport = new GroupReadSupport();
//        ParquetReader<Group> reader = new ParquetReader<>(dataFile, readSupport);
//        Group result = null;
//        while ((result = reader.read()) != null) {
//            System.out.println(result);
//        }
        long duration = System.currentTimeMillis() - t1;

        String fileSize = "";
        File afterFile = new File(path);
        if (afterFile.exists() && afterFile.isFile()) {
            fileSize = FileUtil.fileSizeByteConversion(afterFile.length(), 2);
        }
        LOG.info("Using the {} compression algorithm to write {} pieces of data takes time: {}s, file size is {}.",
                compressionCodecName, recordNum, (duration / 1000), fileSize);
    }
}

FileUtil类

package com.donny.base.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

/**
 * File使用帮助工具类
 *
 * @author 1792998761@qq.com
 * @date 2019/11/21 14:44
 * @since 1.0
 */
public class FileUtil {

    /**
     * 数据存储单位类型 B
     */
    public static final int STORAGE_UNIT_TYPE_B = 0;
    /**
     * 数据存储单位类型 KB
     */
    public static final int STORAGE_UNIT_TYPE_KB = 1;
    /**
     * 数据存储单位类型 MB
     */
    public static final int STORAGE_UNIT_TYPE_MB = 2;
    /**
     * 数据存储单位类型 GB
     */
    public static final int STORAGE_UNIT_TYPE_GB = 3;
    /**
     * 数据存储单位类型 TB
     */
    public static final int STORAGE_UNIT_TYPE_TB = 4;
    /**
     * 数据存储单位类型 PB
     */
    public static final int STORAGE_UNIT_TYPE_PB = 5;
    /**
     * 数据存储单位类型 EB
     */
    public static final int STORAGE_UNIT_TYPE_EB = 6;
    /**
     * 数据存储单位类型 ZB
     */
    public static final int STORAGE_UNIT_TYPE_ZB = 7;
    /**
     * 数据存储单位类型 YB
     */
    public static final int STORAGE_UNIT_TYPE_YB = 8;
    /**
     * 数据存储单位类型 BB
     */
    public static final int STORAGE_UNIT_TYPE_BB = 9;
    /**
     * 数据存储单位类型 NB
     */
    public static final int STORAGE_UNIT_TYPE_NB = 10;
    /**
     * 数据存储单位类型 DB
     */
    public static final int STORAGE_UNIT_TYPE_DB = 11;

    private FileUtil() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 将文件大小转为人类惯性理解方式
     *
     * @param size               大小 单位默认B
     * @param decimalPlacesScale 精确小数位
     */
    public static String fileSizeByteConversion(Long size, Integer decimalPlacesScale) {
        int scale = 0;
        long fileSize = 0L;
        if (decimalPlacesScale != null && decimalPlacesScale >= 0) {
            scale = decimalPlacesScale;
        }
        if (size != null && size >= 0) {
            fileSize = size;
        }
        return sizeByteConversion(fileSize, scale, STORAGE_UNIT_TYPE_B);
    }

    /**
     * 将文件大小转为人类惯性理解方式
     *
     * @param size               大小
     * @param decimalPlacesScale 精确小数位
     * @param storageUnitType    起始单位类型
     */
    public static String fileSizeByteConversion(Long size, Integer decimalPlacesScale, int storageUnitType) {
        int scale = 0;
        long fileSize = 0L;
        if (decimalPlacesScale != null && decimalPlacesScale >= 0) {
            scale = decimalPlacesScale;
        }
        if (size != null && size >= 0) {
            fileSize = size;
        }
        return sizeByteConversion(fileSize, scale, storageUnitType);
    }

    private static String sizeByteConversion(long size, int decimalPlacesScale, int storageUnitType) {
        BigDecimal fileSize = new BigDecimal(size);
        BigDecimal param = new BigDecimal(1024);
        int count = storageUnitType;
        while (fileSize.compareTo(param) > 0 && count < STORAGE_UNIT_TYPE_NB) {
            fileSize = fileSize.divide(param, decimalPlacesScale, RoundingMode.HALF_UP);
            count++;
        }
        StringBuilder dd = new StringBuilder();
        int s = decimalPlacesScale;
        dd.append("0");
        if (s > 0) {
            dd.append(".");
        }
        while (s > 0) {
            dd.append("0");
            s = s - 1;
        }
        DecimalFormat df = new DecimalFormat(dd.toString());
        String result = df.format(fileSize) + "";
        switch (count) {
            case STORAGE_UNIT_TYPE_B:
                result += "B";
                break;
            case STORAGE_UNIT_TYPE_KB:
                result += "KB";
                break;
            case STORAGE_UNIT_TYPE_MB:
                result += "MB";
                break;
            case STORAGE_UNIT_TYPE_GB:
                result += "GB";
                break;
            case STORAGE_UNIT_TYPE_TB:
                result += "TB";
                break;
            case STORAGE_UNIT_TYPE_PB:
                result += "PB";
                break;
            case STORAGE_UNIT_TYPE_EB:
                result += "EB";
                break;
            case STORAGE_UNIT_TYPE_ZB:
                result += "ZB";
                break;
            case STORAGE_UNIT_TYPE_YB:
                result += "YB";
                break;
            case STORAGE_UNIT_TYPE_DB:
                result += "DB";
                break;
            case STORAGE_UNIT_TYPE_NB:
                result += "NB";
                break;
            case STORAGE_UNIT_TYPE_BB:
                result += "BB";
                break;
            default:
                break;
        }
        return result;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

管理类联考——逻辑——真题篇——第四章 完型填空

第四章 完型填空 第一节 真题 2020-完型填空- Section I Use of English Directions&#xff1a; Read the following text. Choose the best word (s) for each numbered blank and mark A, B, C or D on the ANSWER SHEET. (10 points) Being a good parent is, of cour…

如何成为一名专业云渗透测试工程师

前言 很多人不知道网络安全发展前景好吗&#xff1f;学习网络安全能做什么&#xff1f;现在行业有哪些热门岗位&#xff1f;今天为大家解答下。 从宏观层面来看&#xff0c;新基建成为中国经济热词&#xff0c;政府和企业业务上云全面提速&#xff0c;随着云计算技术的快速发…

联想创新开放日:计算引领+AI赋能,联想超十项绿色技术重磅亮相

6月14日&#xff0c;联想2023年创新开放日精彩继续&#xff0c;六大主题的展览、多场圆桌论坛等活动吸引了络绎不绝的观众。聚焦ESG领域&#xff0c;本次联想创新开放日专门设立ESG零碳领航站和相关主题的圆桌论坛。期间&#xff0c;ESG展区还专门展示了联想温水水冷技术、智慧…

MM32F3273G8P火龙果开发板MindSDK开发教程20 - letter shell 的移植

MM32F3273G8P火龙果开发板MindSDK开发教程20 - letter shell 的移植 1、Letter Shell 犹如linux下的命令行&#xff0c;或者更像是uboot下的命令行&#xff0c;可以输入命令&#xff0c;执行相对应的函数。 2、Letter Shell移植&#xff08;GCC 环境&#xff09; 解压后&am…

虹科干货 | BI软件如何实时连接本地Excel?—以HK-Domo商业智能工具为例

由于资源和人才的限制&#xff0c;很多中小微企业目前在数据收集和数据应用上还处于比较落后的阶段&#xff0c;没有合适的方法处理数据。最典型的情况就是通过Excel收集数据&#xff0c;然后频繁的手动生成报告。这样会导致数据质量差&#xff0c;流程重复&#xff0c;还可能增…

读财报丨长期主义指引下,蔚来“下半年月均交付2万+”能否实现?

新能源汽车的互联网属性正在加速消解&#xff0c;工业产品的特质开始贯穿行业始终。 首先是过去几年风头无两的造车新势力&#xff0c;与传统车企在产品层面的边界越来越模糊。与此同时&#xff0c;新能源汽车的利润水平也在大幅下降&#xff0c;趋近燃油车。 行业整体的风格…

计算机组成原理(第三版)唐塑飞 答案 第三章

3.1什么是总线?总线传输有何特点?为了减轻总线的负载,总线上的部件都应具备什么特点&#xff1f; 答&#xff1a; ① 总线是连接多个部件的信息传输线,是个部件共享的传输介质。 ② 总线传输特点:在某一时刻,只允许有一个部件向总线发送信息,而多个部件可以同时从总线上接受相…

20年不变的初心,亚马逊云科技为初创企业的独角兽梦想加速赋能!

廿年初心不改&#xff0c; 为初创企业打造增长飞轮 【全球云观察 &#xff5c; 热点关注】20年前&#xff0c;亚马逊现任CEO Andy Jassy观察到&#xff0c;在亚马逊内部新业务的孵化过程中工程师需要花大量的时间&#xff0c;投入在一些重复性高却不产生附加价值的基础设施建设…

【UE 从零开始制作坦克】1-控制坦克视角

UE版本&#xff1a;4.26 效果 步骤 1. 在虚幻商城中下载如下资产导入到工程中 导入成功后可以在工程中看到如下文件夹 2. 接下来开始制作一下控制坦克视角的逻辑 首先在项目设置中添加轴映射 新建一个蓝图&#xff0c;父类为“WheelVehicle(轮式载具)” 这里就命名为“TankZ…

科一容易忘、容易混的点(二)

注意落石 高速应急车道用途&#xff1a;主要用于发生事故或故障时停车&#xff0c;以及专为救险所用的车道&#xff0c;被誉为高速公路的“生命通道” 注意 注意 扣9份场景&#xff1a; 城市快速路上 违法停车&#xff1b; 注意 交通事故逃逸&#xff0c; 不一定吊销驾驶证&a…

Python的type内置类详解

目录导览 欢迎来到本文Type简介Type的常见用法用法1-获取对象的类型用法2-创建新的类 Type做为元类&#xff08;metaclass&#xff09;元类的作用如何自定义元类 欢迎来到本文 Type简介 在Python中&#xff0c;type是一个内建的类&#xff0c;它是用于表示对象的类型的类。ty…

(九)枚举器和迭代器(2)

一、Enumerator 接口 实现了 IEnumerator 接口的枚举器包含3个函数成员&#xff1a;Current、MoveNext 以及 Reset。 1&#xff09;Current&#xff1a; 返回序列中当前位置项的属性。 只读属性。返回 Object 类型。可以返回对应的可枚举类型。 2&#xff09;MoveNext&…

牛客网基础语法41~50题

牛客网基础语法41~50题&#x1f618;&#x1f618;&#x1f618; &#x1f4ab;前言&#xff1a;今天是咱们第五期刷牛客网上的题目。 &#x1f4ab;目标&#xff1a;熟练用数学知识来解决编程问题&#xff0c;会利用每种循环。 &#x1f4ab;鸡汤&#xff1a;压抑了&#xff0…

8 个身份自动化模版,帮你提升企业身份治理效率

中大型企业的信息化建设已经成为了当前企业的主流趋势。随着日益增长的业务需求&#xff0c;中大型企业的信息系统逐渐发展成为一个拥有众多应用和数据库支撑的复杂体系。由于中大型企业员工众多&#xff0c;部门繁杂&#xff0c;且存在大量员工入、转、调、离等情况&#xff0…

springboot集成quartz

文章目录 前言一、Quartz是什么&#xff1f;难易程度 二、核心思想三、使用步骤1.引入依赖2.创建相应类 总结 前言 引用上一篇博客的说法 springboot整合xxl-job ,集群模式下,定时任务会造成很严重的事故,其次普通的任务也无法做到像xxl-job与Quartz 一样,及时启停,修改等;上一…

文心一言 VS 讯飞星火 VS chatgpt (41)-- 算法导论5.4 5题

五、在大小为n的集合中&#xff0c;一个k字符串构成一个k排列的概率是多少?这个问题和生日悖论有什么关系? 文心一言&#xff1a; 在大小为n的集合中&#xff0c;一个k字符串构成一个k排列的概率是1/k!&#xff0c;这是由于排列的总数是k!&#xff0c;而每个字符串被选中的…

十大开源 ChatGPT 替代品:弥合对话式 AI 的差距

聊天机器人彻底改变了企业与客户互动的方式。由 OpenAI 开发的最先进的 GPT-4 是该领域的主导者。但是&#xff0c;它不是开源的&#xff0c;这限制了开发人员复制结果或开发自己的类似于 GPT-4 的聊天机器人。 为了填补这一空白&#xff0c;开源社区已经开始提供 GPT-4 的替代…

Qt6.5.1+WebRTC学习笔记(十二)环境搭建流媒体服务器(ubuntu22.04+SRS)

前言 若只是实现一对一通信&#xff0c;仅使用webrtc就足够了。但有时间需要进行多个人的直播会议&#xff0c;当人比较多时&#xff0c;建议使用一个流媒体服务器&#xff0c;笔者使用的是SRS。 这个开源项目资料比较全&#xff0c;笔者仅在此记录下搭建过程 一、准备 1.操…

移远通信率先完成多场5G NTN技术外场验证,为卫星物联网应用落地提速

近日&#xff0c;由中国电信卫星公司牵头&#xff0c;移远通信联合紫光展锐、鹏鹄物宇等行业上下游合作伙伴&#xff0c;针对现有蜂窝通信在信号覆盖盲区&#xff0c;信息监测数据无法实时回传等痛点问题&#xff0c;以领先行业的速度开展了一系列的5G NTN&#xff08;non-terr…

Unity入门7——物理系统之碰撞检测

一、刚体 Rigid Body ​ 刚体利用体积&#xff08;碰撞器 Collider&#xff09;进行碰撞计算&#xff0c;模拟真实的碰撞效果&#xff0c;产生力的作用 ​ 碰撞产生的必要条件&#xff1a; 两个物体都有碰撞器 Collider至少一个物体有刚体 Mass&#xff1a;质量 默认为千克&a…