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

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

可以接口返回用戶(hù)使用容量字段size成功格式化成1.5M,當(dāng)然如里返回List<UserDTO>或Map中也是能正常格式化的,完全符合預(yù)期

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

