如何优雅的处理字节类型数据

原文:赵侠客

前言

字节(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;}@Overridepublic 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 );}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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注解,解决了字节类型数据在前端与后端之间的优雅转换。当然本方法不仅可以解决字节类型的数据格式转换,还可以用于如时间格式、枚举格式、金钱格式的转换,再扩展一下也可以用于数据脱敏等场景。本解决方法主要有以下优点:

  • 使用优雅:使用者只需要在字段上增加@ByteFormat(scale = 3)即可,代码很优雅
  • 方法通用:该方法不仅可用于http接口参数的转换,还可用于Jackson数据的转换的所有场景
  • 降本增效:该方法完全可以在团队中推广,大家都可以使用,不用每个人写一堆转换
  • 前端友好:前端拿到这样的接口使用很方便,返回数据直接显示就好,用户输入数据直接传后端

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

  • 只能用于Jackson:其它JSON序列化工具不支持如使用FastJsonGson
  • 使用域实体对象:实体对象一旦添加了ByteFormat都会作格式转换,如果有特殊场景不想做转换则需要使用新实体对象

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

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

相关文章

nginx前端部署配置

nginx前端部署配置 Nginx部署项目 1、yarn build打包Vue项目 2、打开nginx.conf文件,配置对应的信息 nginx.conf location / {root C:\Users\17542\Desktop\rrpject-v2\dist;root index.html index.htm;try_files $uri $uri/ router; //解决页面刷新404问题 } location…

艾迈斯欧司朗最新推出的DURIS® LED将引领柔性多变照明新时代

中国 上海&#xff0c;2024年7月15日——全球领先的光学解决方案供应商艾迈斯欧司朗&#xff08;瑞士证券交易所股票代码&#xff1a;AMS&#xff09;今日宣布&#xff0c;艾迈斯欧司朗最新推出的DURIS E 2835 LED&#xff0c;实现从封装工艺到出光性能的升级与创新。这款LED采…

PyQt5图形界面--基础笔记

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QToolTip, QLabel, QLineEdit from PyQt5.QtGui import QIcon, QFont, QPixmap import sys https://www.bitbug.net/ 将图片转换为ico格式, 用来更改打包的文件图标 -F 只产生exe文件, 其他临时文件不产生 -…

K8S系列-Kubernetes基本概念及Pod、Deployment、Service的使用

一、Kubernetes 的基本概念和术语 一、资源对象 ​ Kubernetes 的基本概念和术语大多是围绕资源对象 Resource Object 来说的&#xff0c;而资源对象在总体上可分为以下两类: 1、某种资源的对象 ​ 例如节点 Node) Pod 服务 (Service) 、存储卷 (Volume&#xff09;。 2、…

408数据结构-图的应用3-有向无环图、拓扑排序 自学知识点整理

前置知识&#xff1a;表达式&#xff0c;图的遍历 有向无环图描述表达式 有向无环图&#xff1a;若一个有向图中不存在环&#xff0c;则称为有向无环图&#xff0c;简称 D A G DAG DAG图 。 &#xff08;图片来自王道考研408数据结构2025&#xff09; 由王道考研-咸鱼学长的讲…

emqx(v5.0)常见问题

emqx&#xff08;v5.0&#xff09;常见问题 1 官方常见问题解答2 EMQX 启动时日志提示 “WARNING: Default (insecure) Erlang cookie is in use.” 应该怎么办&#xff1f;3 EMQX 启动时日志提示“filed to merge schema”&#xff1f; 1 官方常见问题解答 常见问题解答 2 E…

前端框架学习之 搭建vue2的环境 书写案例并分析

目录 搭建vue的环境 Hello小案例 分析案例 搭建vue的环境 官方指南假设你已经了解关于HTML CSS 和JavaScript的中级知识 如果你刚开始学习前端开发 将框架作为你的第一步可能不是最好的主意 掌握好基础知识再来吧 之前有其他框架的使用经验会有帮助 但这不是必需的 最…

JDK垃圾回收机制和垃圾回收算法

查看java相关信息 java -XX:PrintCommandLineFlags -version UseParallelGC 即 Parallel Scavenge Parallel Old,再查看详细信息 内存分配策略 1. 对象优先在 Eden 分配 大多数情况下&#xff0c;对象在新生代 Eden 区分配&#xff0c;当 Eden 区空间不够时&#xff0c;发…

PX4 UM982 配合F9P Base 进行 RTK 定位

UM982是新兴的常见双天线GPS模块&#xff0c;支持双天线定向&#xff0c;RTK功能&#xff0c;PX4也引入了对其的支持&#xff0c;需要按需额外设置 官方手册号称直接用F9P做地面站&#xff0c;搭配QGC使用就能进行RTK定位 但是经过实践&#xff0c;发现这样是进不了RTK模式的…

Docker---最详细的服务部署案例

提供python服务的docker一键部署&#xff0c;示例已配置负载均衡&#xff0c;不需要的在nginx.conf和docker-compose注释相关代码即可 文件结构 1、dockerfile # 服务的dockerfile# 服务依赖的镜像 FROM python:3.7# 设置容器内服务的工作目录 WORKDIR /app# 复制当前文件夹所…

基于Rspack实现大仓应用构建提效实践|得物技术

一、实践背景 随着项目的逐步迭代&#xff0c;代码量和依赖的逐渐增长&#xff0c;应用的构建速度逐步进入缓慢期。以目前所在团队的业务应用来看&#xff08;使用webpack构建&#xff09;&#xff0c;应用整体构建耗时已经普遍偏高&#xff0c;影响日常开发测试的使用效率&am…

护网--2

实验要求&#xff1a; 1、办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 2、分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 3、多出口环境基于带宽比例进行选路&#xff0c;但是&#xff0c;…

实现keepalive+Haproxyde 的高可用

需要准备五台实验机 一台客户机&#xff1a;test1 两台&#xff1a;一主一备的实验机&#xff1a;test2 test3 两台真实服务器&#xff1a;nginx1 nginx2 实验 首先在两台实验机上安装Haproxy 安装依赖环境&#xff0c;并将Haproxy的包进行解压处理 yum install -y pcre…

nodejs安装部署运行vue前端项目

文章目录 1.安装nodejs2.安装Vue CLI1.配置npm镜像源&#xff1a;2.安装Vue CLI&#xff1a;3.创建Vue项目4.启动Vue项目5.Express 1.安装nodejs Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境&#xff0c;它让开发人员能够创建服务器、Web 应用、命令行工具和脚…

【自动驾驶汽车通讯协议】UART通信详解:理解串行数据传输的基石

文章目录 0. 前言1. 同步通讯与异步通讯1.1 同步通信1.2 异步通信 2. UART的数据格式3. 工作原理3.1 波特率和比特率3.2 UART的关键特性 4. UART在自动驾驶汽车中的典型应用4.1 UART特性4.2应用示例 5. 结语 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我自…

xlive.dll丢失怎么办,xlive.dll文件的主要用途

xlive.dll丢失怎么办&#xff1f;目前是有很多方法可以解决这个xlive.dll丢失的问题的&#xff0c;只要你仔细的去了解xlive.dll这个文件&#xff0c;至于使用哪种方法&#xff0c;主要还是看你的实际情况&#xff0c;因为情况不同选择使用的方法也是不一样的&#xff0c;下面一…

底软驱动 | Linux虚拟内存

为了更有效的管理内存并且少出错&#xff0c;现代操作系统提供了一种对主存的抽象概念&#xff0c;叫做虚拟内存(VM)。虚拟内存提供了三个重要的能力: 1.它将主存(物理内存)看成是一个存储在磁盘上的地址空间的高速缓存&#xff0c;在主存中只保留活动区域&#xff0c;并且根据…

openEuler 安装 podman 和 podman compose

在 openEuler 22.03 LTS SP4 中&#xff0c;你可以使用 dnf 包管理器来安装 Podman 和 Podman Compose。openEuler 默认使用 dnf 作为包管理器&#xff0c;所以这是安装软件的首选方式。 关于 openEuler 22.03 LTS SP4 下载地址&#xff1a; https://www.openeuler.org/zh/dow…

【256 Days】我的创作纪念日

目录 &#x1f33c;01 机缘 &#x1f33c;02 收获 &#x1f33c;03 日常 &#x1f33c;04 成就 &#x1f33c;05 憧憬 最近收到官方来信&#xff0c; 突然发现&#xff0c;不知不觉间&#xff0c;距离发布的第一篇博客已过256天&#xff0c;这期间我经历了春秋招、毕业答辩…

Access denied for user ‘root‘@‘localhost‘ (using password: YES)解决办法

在Spring配置数据源时&#xff0c;当使用Spring容器加载druid.properties数据库连接池配置文件时&#xff0c;容易碰到create connection SQLException, url: jdbc:mysql://127.0.0.1:3306/mydbs, errorCode 1045, state 28000 java.sql.SQLException: Access denied for user …