Java處理字節(jié)類型數(shù)據(jù)的實(shí)現(xiàn)步驟
前言
字節(jié)(Byte)是計(jì)算機(jī)信息技術(shù)用于計(jì)量存儲容量的一種基本單位,通常簡寫為B,1Byte=8bit,在ASCII編碼中1Byte可以表示一個標(biāo)準(zhǔn)的英文字符,包括大寫字母、小寫字母、數(shù)字、標(biāo)點(diǎn)符號和控制字符等,共128個不同的字符,如1、2、3、a、b、c都占用一個Byte,所以1Byte其實(shí)是非常小的單位,比Byte大的單位就是KB,一般一篇博客文字的大小應(yīng)該在幾十KB,比KB大的單位是MB,目前手機(jī)拍攝一張照片的大小大概是幾MB,比MB大的還有GB、TB、PB、EB、ZB、YB,以下是各單位間的轉(zhuǎn)換。
| 名稱 | 簡寫 | 換算 |
|---|---|---|
| 比特(Byte) | B | 1B=8bit |
| 千字節(jié)(KiloByte) | KB | 1KB=2^10 B =1024B |
| 兆字節(jié)(Mega Byte) | MB | 1MB=2^10 KB =2^20 B |
| 吉字節(jié)(GigaByte) | GB | 1GB=2^10 MB =2^30 B |
| 太字節(jié)(TeraByte) | TB | 1TB=2^10 GB =2^40 B |
| 拍字節(jié)(PetaByte) | PB | 1PB=2^10 TB =2^50 B |
| 艾字節(jié)(EXAByte) | EB | 1EB=2^10 PB =2^60 B |
| 澤字節(jié)(Zetta Byte) | ZB | 1ZB=2^10 EB =2^70 B |
| 堯字節(jié)(Yotta Byte) | YB | 1YB=2^10 ZB =2^80 B |
從字節(jié)有這么多單位可以看出選擇合適的單位可以讓人很直觀有個大小概念,比如你可以說我買了最新款的IPhone15 128GB版本,別人一看就知道是最低配版本了,可能覺得你是買了丐版的來裝逼一下,但是你說我買最新款的IPhone15Pro 134217728KB版本,別人第一感肯定不知道你買的是一個丐版,但是會覺得你是個SB。為了精度我一般在數(shù)據(jù)庫中會存儲Byte類型,另外也方便我們在代碼中作計(jì)算和比較,返回給用戶時則會轉(zhuǎn)成對用戶友好的單位,例如我們記錄用戶空間使用量在數(shù)據(jù)庫中會存儲最小單位Byte:
| 用戶ID | 空間用量 |
|---|---|
| 18314 | 2212058073480 |
| 16765 | 2085350264853 |
| 15138 | 1439009188728 |
| 9152 | 1319605924042 |
| 24080 | 1259223266116 |
| 3325 | 1139222905087 |
| 9401 | 1128752330535 |
| 3838 | 1125023100502 |
返回給用戶顯示時會轉(zhuǎn)成對用戶友好的單位

由于字節(jié)的單位比較多,所以代碼中會經(jīng)常出現(xiàn)手動單位轉(zhuǎn)換,這樣代碼就不太優(yōu)雅,本文介紹一種優(yōu)雅處理這些字節(jié)轉(zhuǎn)換的方法,接下來我們以用戶空間使用量為為例,說明如何優(yōu)雅的處理這種數(shù)據(jù)格式轉(zhuǎn)換。
應(yīng)用場景
比如我們現(xiàn)在有一個類似百度云盤的系統(tǒng),需要記錄用戶云盤空間使用量,并且后臺可以設(shè)置用戶云盤的最大容量。那么我們至少有兩個接口,一個是返回用戶當(dāng)前云盤空間使用量,另一個是設(shè)置云盤最大容量
- 獲取用戶當(dāng)前空間使用量接口
GET http://localhost:80/userSize/1
返回結(jié)果
{
"id": 1,
"size": "1.5M"
}
需要解決的問題:將數(shù)據(jù)庫存的1572864格式化成1.5M
- 設(shè)置用戶最大容量接口
POST http://localhost:80/userSize
Content-Type: application/json
{
"id":1,
"maxSize":"10.5G"
}
需要解決的問題:將前端傳的10.5G轉(zhuǎn)成11274289152存入數(shù)據(jù)庫
解決思路
目前大部分開發(fā)框架都使用SpringBoot,SpringBoot將JAVA對象序列化成JSON和將JSON反序列化成JAVA對象默認(rèn)使用Jackson,那么我們可以自定義Jackson序列化器和反序列器來達(dá)到此效果。最終效果是:我們在想要格式化的字段中增加 @ByteFormat(scale = 1)返回時自動將1572864格式化成1.5M,接收時自動將10.5G轉(zhuǎn)成11274289152,這樣是不是很優(yōu)雅?而且項(xiàng)目中所有地方只要增加這個注解,就自動處理這個格式轉(zhuǎn)換,下次再遇到字節(jié)類型再也不需要去做一大堆的格式轉(zhuǎn)換了。
@Data
public class UserDTO {
private Long id;
@ByteFormat(scale = 3)
private Long size;
@ByteFormat(scale = 1)
private Long maxSize;
}
實(shí)現(xiàn)步驟
定義注解ByteFormat
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize(using = ByteFormatSerializer.class)
@JsonDeserialize(using = ByteFormatDeserializer.class)
@JacksonAnnotationsInside
public @interface ByteFormat {
// 保留精度
int scale() default 2;
}
Jackson是可以支持自定義序列化器和反序列化器的, 所以基于此我們可以擴(kuò)展實(shí)現(xiàn)一些自定義序列化注解, 就像 @JsonFormat注解對時間格式處理一樣。 那我們擴(kuò)展自定義注解原理也很簡單,主要是利用 @JsonSerialize、@JsonDeserialize、@JacksonAnnotationsInside注解去實(shí)現(xiàn), @JacksonAnnotationsInside是一個組合注解,主要標(biāo)記在用戶的自定義注解上,那么這個用戶自定義注解上標(biāo)記的所有其他注解也會生效。
定義序列化器ByteFormatSerializer
ByteFormatSerializer類的作用是當(dāng)Jackson序列化遇到Number類型時會調(diào)用createContextual()方法,在該方法中判斷字段上是否有ByteFormat注解,如果有則告訴Jackson來調(diào)用ByteFormatSerializer的serialize來序列化,在serialize()方法中完成了數(shù)據(jù)格式的轉(zhuǎn)換。
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類型時會調(diào)用createContextual()方法,在該方法中判斷如果字段上有ByteFormat注解則告訴Jackson來調(diào)用ByteFormatDeserializer的deserialize方法,在deserialize()方法中完成了數(shù)據(jù)的轉(zhuǎn)換。
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;
}
}
格式轉(zhuǎn)換工具類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();
}
}
測試
編寫兩個測試接口,一個接口返回用戶當(dāng)前使用容器量,然后把size大小設(shè)置成1572864,另一個是設(shè)置用戶最大使用容量,使用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,當(dāng)然如里返回List<UserDTO>或Map中也是能正常格式化的,完全符合預(yù)期

可以看出用戶傳maxSize:10.5G,后端成功使用Long maxSize類型接收到了String類型數(shù)據(jù),并且將String數(shù)值轉(zhuǎn)成了11274289152,完全符合預(yù)期。
總結(jié)
本文使用Jackson自定義了ByteFormat注解,解決了字節(jié)類型數(shù)據(jù)在前端與后端之間的優(yōu)雅轉(zhuǎn)換。當(dāng)然本方法不僅可以解決字節(jié)類型的數(shù)據(jù)格式轉(zhuǎn)換,還可以用于如時間格式、枚舉格式、金錢格式的轉(zhuǎn)換,再擴(kuò)展一下也可以用于數(shù)據(jù)脫敏等場景。本解決方法主要有以下優(yōu)點(diǎn):
- 使用優(yōu)雅:使用者只需要在字段上增加
@ByteFormat(scale = 3)即可,代碼很優(yōu)雅 - 方法通用:該方法不僅可用于
http接口參數(shù)的轉(zhuǎn)換,還可用于Jackson數(shù)據(jù)的轉(zhuǎn)換的所有場景 - 降本增效:該方法完全可以在團(tuán)隊(duì)中推廣,大家都可以使用,不用每個人寫一堆轉(zhuǎn)換
- 前端友好:前端拿到這樣的接口使用很方便,返回?cái)?shù)據(jù)直接顯示就好,用戶輸入數(shù)據(jù)直接傳后端
當(dāng)然本方法也是有缺點(diǎn)的:
- 只能用于
Jackson:其它JSON序列化工具不支持如使用FastJson、Gson等 - 使用域?qū)嶓w對象:實(shí)體對象一旦添加了
ByteFormat都會作格式轉(zhuǎn)換,如果有特殊場景不想做轉(zhuǎn)換則需要使用新實(shí)體對象
以上就是Java處理字節(jié)類型數(shù)據(jù)的實(shí)現(xiàn)步驟的詳細(xì)內(nèi)容,更多關(guān)于Java處理字節(jié)類型數(shù)據(jù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java如何利用poi解析doc和docx中的數(shù)據(jù)
這篇文章主要給大家介紹了關(guān)于java如何利用poi解析doc和docx中數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
深入了解SpringBoot中@ControllerAdvice的介紹及三種用法
這篇文章主要為大家詳細(xì)介紹了SpringBoot中@ControllerAdvice的介紹及三種用法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-02-02
json-lib將json格式的字符串,轉(zhuǎn)化為java對象的實(shí)例
下面小編就為大家?guī)硪黄猨son-lib將json格式的字符串,轉(zhuǎn)化為java對象的實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03
spring boot org.junit.jupiter.api不存在的解決
這篇文章主要介紹了spring boot org.junit.jupiter.api不存在的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Eclipse使用maven搭建spring mvc圖文教程
這篇文章主要為大家分享了Eclipse使用maven搭建spring mvc圖文教程,感興趣的小伙伴們可以參考一下2016-05-05

