java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java处理字节类型数据

Java处理字节类型数据的实现步骤

作者:赵侠客

字节(Byte)是计算机信息技术用于计量存储容量的一种基本单位,通常简写为B,在ASCII编码中1Byte可以表示一个标准的英文字符,包括大写字母、小写字母、数字、标点符号和控制字符等,本文给大家介绍了Java如何优雅的处理字节类型数据,需要的朋友可以参考下

前言

字节(Byte)是计算机信息技术用于计量存储容量的一种基本单位,通常简写为B,1Byte=8bit,在ASCII编码中1Byte可以表示一个标准的英文字符,包括大写字母、小写字母、数字、标点符号和控制字符等,共128个不同的字符,如1、2、3、a、b、c都占用一个Byte,所以1Byte其实是非常小的单位,比Byte大的单位就是KB,一般一篇博客文字的大小应该在几十KB,比KB大的单位是MB,目前手机拍摄一张照片的大小大概是几MB,比MB大的还有GB、TB、PB、EB、ZB、YB,以下是各单位间的转换。

名称简写换算 
比特(Byte)B1B=8bit
千字节(KiloByte)KB1KB=2^10 B =1024B
兆字节(Mega Byte)MB1MB=2^10 KB =2^20 B
吉字节(GigaByte)GB1GB=2^10 MB =2^30 B
太字节(TeraByte)TB1TB=2^10 GB =2^40 B
拍字节(PetaByte)PB1PB=2^10 TB =2^50 B
艾字节(EXAByte)EB1EB=2^10 PB =2^60 B
泽字节(Zetta Byte)ZB1ZB=2^10 EB =2^70 B
尧字节(Yotta Byte)YB1YB=2^10 ZB =2^80 B

从字节有这么多单位可以看出选择合适的单位可以让人很直观有个大小概念,比如你可以说我买了最新款的IPhone15 128GB版本,别人一看就知道是最低配版本了,可能觉得你是买了丐版的来装逼一下,但是你说我买最新款的IPhone15Pro 134217728KB版本,别人第一感肯定不知道你买的是一个丐版,但是会觉得你是个SB。为了精度我一般在数据库中会存储Byte类型,另外也方便我们在代码中作计算和比较,返回给用户时则会转成对用户友好的单位,例如我们记录用户空间使用量在数据库中会存储最小单位Byte

用户ID空间用量
183142212058073480
167652085350264853
151381439009188728
91521319605924042
240801259223266116
33251139222905087
94011128752330535
38381125023100502

返回给用户显示时会转成对用户友好的单位

由于字节的单位比较多,所以代码中会经常出现手动单位转换,这样代码就不太优雅,本文介绍一种优雅处理这些字节转换的方法,接下来我们以用户空间使用量为为例,说明如何优雅的处理这种数据格式转换。

应用场景

比如我们现在有一个类似百度云盘的系统,需要记录用户云盘空间使用量,并且后台可以设置用户云盘的最大容量。那么我们至少有两个接口,一个是返回用户当前云盘空间使用量,另一个是设置云盘最大容量

GET http://localhost:80/userSize/1

返回结果
{
  "id": 1,
  "size": "1.5M"
}

需要解决的问题:将数据库存的1572864格式化成1.5M

POST http://localhost:80/userSize  
Content-Type: application/json  
  
{  
    "id":1,  
    "maxSize":"10.5G"  
}

需要解决的问题:将前端传的10.5G转成11274289152存入数据库

解决思路

目前大部分开发框架都使用SpringBootSpringBootJAVA对象序列化成JSON和将JSON反序列化成JAVA对象默认使用Jackson,那么我们可以自定义Jackson序列化器和反序列器来达到此效果。最终效果是:我们在想要格式化的字段中增加 @ByteFormat(scale = 1)返回时自动将1572864格式化成1.5M,接收时自动将10.5G转成11274289152,这样是不是很优雅?而且项目中所有地方只要增加这个注解,就自动处理这个格式转换,下次再遇到字节类型再也不需要去做一大堆的格式转换了。

@Data
public class UserDTO {
    private Long id;
    @ByteFormat(scale = 3)
    private Long size;
    @ByteFormat(scale = 1)
    private Long maxSize;
}

实现步骤

定义注解ByteFormat

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = ByteFormatSerializer.class)
@JsonDeserialize(using = ByteFormatDeserializer.class)
@JacksonAnnotationsInside
public @interface ByteFormat {
    // 保留精度
    int scale() default 2;
}

Jackson是可以支持自定义序列化器和反序列化器的, 所以基于此我们可以扩展实现一些自定义序列化注解, 就像 @JsonFormat注解对时间格式处理一样。 那我们扩展自定义注解原理也很简单,主要是利用 @JsonSerialize@JsonDeserialize@JacksonAnnotationsInside注解去实现, @JacksonAnnotationsInside是一个组合注解,主要标记在用户的自定义注解上,那么这个用户自定义注解上标记的所有其他注解也会生效。

定义序列化器ByteFormatSerializer

ByteFormatSerializer类的作用是当Jackson序列化遇到Number类型时会调用createContextual()方法,在该方法中判断字段上是否有ByteFormat注解,如果有则告诉Jackson来调用ByteFormatSerializerserialize来序列化,在serialize()方法中完成了数据格式的转换。

public class ByteFormatSerializer extends JsonSerializer<Number> implements ContextualSerializer {
    protected ByteFormat byteFormat;
    
    public ByteFormatSerializer(){
    }

    public ByteFormatSerializer(ByteFormat byteFormat){
        this.byteFormat=byteFormat;
    }

    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value == null){
            return;
        }
        int scale = byteFormat.scale();
        BigDecimal bigValue = new BigDecimal(value.toString());
        String result = ByteConvert.convertValue(bigValue,  scale);
        gen.writeString(result );
    }
    

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass().getGenericSuperclass(), Number.class) ) {
                ByteFormat t = beanProperty.getAnnotation(ByteFormat.class);
                if (t != null) {
                    return  new ByteFormatSerializer(t);
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }

}

定义反序列化器ByteFormatDeserializer

ByteFormatDeserializer类的作用是Jackson反序列化遇到Number类型时会调用createContextual()方法,在该方法中判断如果字段上有ByteFormat注解则告诉Jackson来调用ByteFormatDeserializerdeserialize方法,在deserialize()方法中完成了数据的转换。

public class ByteFormatDeserializer extends JsonDeserializer<Number> implements ContextualDeserializer  {
    protected ByteFormat byteFormat;

    public ByteFormatDeserializer(){
    }

    public ByteFormatDeserializer(ByteFormat byteFormat){
        this.byteFormat=byteFormat;
    }


    @Override
    public Number deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (!StringUtils.hasText(p.getText())) {
            return null;
        }
        if(byteFormat!=null){
            String value = p.getText();
            return ByteConvert.convertNumber(value);
        }
        return null;
    }


    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            if (Objects.equals(beanProperty.getType().getRawClass().getGenericSuperclass(), Number.class) ) {
                ByteFormat t = beanProperty.getAnnotation(ByteFormat.class);
                if (t != null) {
                    return  new ByteFormatDeserializer(t);
                }
            }
            return serializerProvider.findContextualValueDeserializer(beanProperty.getType(), beanProperty);
        }
        return this;
    }
}


格式转换工具类ByteConvert

public class ByteConvert {
    public static final Long KB=1L<<10;
    public static final Long MB=KB<<10;
    public static final Long GB=MB<<10;
    public static final Long TB=GB<<10;
    
    public static String convertValue(BigDecimal bigValue, int scale) {
        if(bigValue.compareTo(BigDecimal.valueOf(TB))>=0){
            return String.format("%sT",bigValue.divide(BigDecimal.valueOf(TB), scale, RoundingMode.HALF_UP));
        }
        if(bigValue.compareTo(BigDecimal.valueOf(GB))>=0){
            return String.format("%sG",bigValue.divide(BigDecimal.valueOf(GB), scale, RoundingMode.HALF_UP));
        }
        if(bigValue.compareTo(BigDecimal.valueOf(MB))>=0){
            return String.format("%sM",bigValue.divide(BigDecimal.valueOf(MB), scale, RoundingMode.HALF_UP));
        }
        if(bigValue.compareTo(BigDecimal.valueOf(KB))>=0){
            return String.format("%sK",bigValue.divide(BigDecimal.valueOf(KB), scale, RoundingMode.HALF_UP));
        }
        return String.format("%sB",bigValue);
    }

    public static Number convertNumber(String stringValue) {
        if (stringValue.endsWith("T")) {
            Double value = Double.parseDouble(stringValue.replaceAll("T", "")) * TB;
            return value.longValue();
        }
        if (stringValue.endsWith("G")) {
            Double value = Double.parseDouble(stringValue.replaceAll("G", "")) * GB;
            return value.longValue();
        }
        if (stringValue.endsWith("M")) {
            Double value = Double.parseDouble(stringValue.replaceAll("M", "")) * MB;
            return value.longValue();
        }
        if (stringValue.endsWith("K")) {
            Double value = Double.parseDouble(stringValue.replaceAll("K", "")) * KB;
            return value.longValue();
        }
        return Double.valueOf(stringValue).longValue();
    }
}

测试

编写两个测试接口,一个接口返回用户当前使用容器量,然后把size大小设置成1572864,另一个是设置用户最大使用容量,使用UserDTO直接接收。

    @GetMapping("/userSize/{id}")
    public ResponseEntity<UserDTO> userSize(@PathVariable Long id) {
        UserDTO userDTO = new UserDTO();
        userDTO.setId(id);
        userDTO.setSize(1572864L);
        return ResponseEntity.ok(userDTO);
    }

    @PostMapping("/userSize")
    public ResponseEntity<UserDTO> setUserSize(@RequestBody UserDTO userDTO) {
        log.info("user {} maxSize {}", userDTO.getId(), userDTO.getMaxSize());
        return ResponseEntity.ok(userDTO);
    }

可以接口返回用户使用容量字段size成功格式化成1.5M,当然如里返回List<UserDTO>Map中也是能正常格式化的,完全符合预期

可以看出用户传maxSize:10.5G,后端成功使用Long maxSize类型接收到了String类型数据,并且将String数值转成了11274289152,完全符合预期。

总结

本文使用Jackson自定义了ByteFormat注解,解决了字节类型数据在前端与后端之间的优雅转换。当然本方法不仅可以解决字节类型的数据格式转换,还可以用于如时间格式、枚举格式、金钱格式的转换,再扩展一下也可以用于数据脱敏等场景。本解决方法主要有以下优点:

当然本方法也是有缺点的:

以上就是Java处理字节类型数据的实现步骤的详细内容,更多关于Java处理字节类型数据的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文