為什麼Java中序列化的SerialVersionUID總是無意義的?

2021-01-11 51CTO

這個題目不主要講serialVersionUID作用,而是講後面的那一串數字的意義,當然也會對java的這個serialVersionUID的作用進行一個講解。這篇文章是我積壓了很久的一篇文章,寫了一半,幾個月了才發現,於是拿出來好好整理一下。

一、serialVersionUID的作用

通過java進行網絡之間的數據傳輸是不能直接把對象進行傳的,需要在發送端把數據切分,在接收端對切分的數據進行重裝。這種切分和重裝的方式就叫做序列化。下面我們舉一個例子:

(1)不指定serialVersionUID

首先我們定義一個User類,繼承Serializable接口

然後序列化

public static void main(String[] args) throws Exception {         // 序列化         User an = new User();         FileOutputStream fos = new FileOutputStream("user");         ObjectOutputStream oos = new ObjectOutputStream(fos);         oos.writeObject(an);         oos.close();     } 

反序列化

public static void main(String[] args) throws Exception {         // 反序列化         FileInputStream fis = new FileInputStream("user");         ObjectInputStream ois = new ObjectInputStream(fis);         User u = (User)ois.readObject();         System.out.println(u.name+"  " + u.age);         ois.close();         fis.close();     } 

現在我們舉了一個序列化的例子,沒有指定serialVersionUID,此時程序在編譯的時候就會自動為我們生成一個ID號,整個過程是這樣的:

(1)發送端不指定serialVersionUID,編譯器為我們默認生成,並序列化保存在流中發送到接收端。

(2)接收端把serialVersionUID保存起來,進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化。也就是說傳過來的ID和本地ID不一致時候就會出現錯誤。

現在驗證一下第二種情況:

我們再去反序列化的時候,因為JVM會把傳來的字節流中的serialVersionUID與本地相應實體的serialVersionUID進行比較,發現不一致,因此會出現異常錯誤:

(2)指定serialVersionUID

這個情況就不展示了,不斷你之前添加了多少個欄位,或者進行更改,因為serialVersionUID唯一,因此反序列化都不會出現錯誤。

OK,這就是java中這個serialVersionUID的作用,其實就是給這個類添加一個身份ID,進行在序列化之前和之後進行版本的比對。上面這個其實也是一個面試常問的一個問題,再次湊巧給總結了一下,不過今天的主題不是講這個serialVersionUID的,而是後面的那一串數字為什麼總是無意義的?

二、為什麼總是無意義的ID?

java序列化中的serialVersionUID後面我們通常是1L、或者是xxxL。這些數字有什麼意義呢?為什麼我們總是需要這些無意義的ID。帶著這些問題我們一步一步來揭曉答案。

1、有意義的ID

有一些ID是有意義的,最常見的就是我們的身份證號,一共18位。分別代表著省市縣等等。在通常情況下這個ID在全國內是惟一的。他就像是一個標識符一樣,唯一地代表了我們。

標識符(identifier)就是一個可以唯一識別一個對象或者物體的名稱,被識別的對象可能是一些想法、物理上可數的對象或者物理上的不可數物質。它的前綴 ID 經常被用來表示身份、鑑定過程或者標識符。

因此唯一性是ID的最大特點。好比是我們的身份證號碼,整個中國你找不出第二個和你一樣號碼的人。現在我們知道了有意義的ID通常情況下是一個標識符,唯一地代表了這個物體。現在我們把目光轉到無意義的ID。

2、無意義的ID

我們的java序列化id、資料庫中的自增主鍵、消息隊列、甚至於我們的TCP通信中都會使用到這個。無意義的真正含義其實是和我們要做的事無關,也就是說這個ID數字不應該和我們的業務邏輯產生聯繫。

大多數業務的主鍵都會使用整數,它的上限一般就是 2^64,如果這些位數都用來表示記錄的 ID,那麼在有生之年基本上是不可能被使用完的,但是一旦我們將業務信息加入 ID,就會讓原本無意義的 ID 變得有意義從而影響它的唯一性。

java序列化的那個例子,你看到serialVersionUID==xxxL,應該想不到這一串數字和這個類有什麼聯繫吧。而且一旦有聯繫就有可能會出現錯誤。那為什麼無意義的ID是有用的呢?我們舉一個例子:在分布式系統中有一個分布式的 ID 生成器,Snowflake 算法會為 64 個比特的整數賦予不同的信息:

範圍 長度 作用 0-0 1 不使用 1-41 41 毫秒級時間戳 42-46 5 數據中心標識符 47-51 5 機器標識符 52-63 12 序列號

假設一臺機器上一個時間單位最多只能生成 4096 個 ID,一旦超過了這個這個數量就有可能導致 ID 衝突或者亂序,從而失去其唯一性;這個算法中涉及的時間戳、數據中心標識符、機器標識符都沒有辦法解決唯一性的問題,哪怕這三者完全相等,此時仍然需要使用無其他意義的序列號來保證 ID 的唯一。

因此使用無意義 ID 的主要目的就是利用它的唯一性保證對象的標識符不會發生衝突,無意義 ID 的唯一作用就是保證唯一性,這能幫助我們避免業務欄位可能存在潛在衝突的可能,這也提示我們想要使用聯合欄位構成主鍵時一定要深思熟慮。

3、總結

上面其實說了這麼多,是想讓各位有個稍微全面的了解。就像很多時候一句話講完的事,非要BB半天。幾句話總結:

對於有意義的ID,在特定場景下ID數字和業務邏輯有關,比如身份證號和每個人的唯一標識有關。

對於無意義的ID:這個ID數子一旦和業務邏輯產生聯繫,就有重複的可能,而且極其不安全。此時一個無意義的ID就有了唯一性。

不管有沒有意義都是為了進行唯一標識,但是使用的場景不相同。

本文轉載自微信公眾號「愚公要移山」,可以通過以下二維碼關注。轉載本文請聯繫愚公要移山公眾號。

【編輯推薦】

【責任編輯:

武曉燕

TEL:(010)68476606】

點讚 0

相關焦點

  • java序列化反序列化中serialVersionUID到底有什麼用
    序列化\反序列化:java序列化是指把java對象轉換為字節序列的過程,而java反序列化是指把字節序列恢復為java對象的過程。用途:當兩個進程進行遠程通信時,可以相互發送各種類型的數據,包括文本,圖片,音頻,視頻等,而這些數據都會以二進位的形式在網絡上傳送;當兩個java進行進行通信時,要傳送對象,怎麼傳對象,通過序列化與反序列化;永久性保存對象,保存對象的字節序列到本地文件或者資料庫中,實現了數據的持久化;利用序列化實現遠程通信,可以在網絡上傳送對象的字節序列
  • Java中 serialVersionUID 的作用是什麼?舉個例子說明
    在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。
  • 全方位解析Java序列化
    ,於是 Web 容器就會把一些 Session 先序列化到硬碟中,等要用了,再把保存在硬碟中的對象還原到內存中。顯然這個靜態屬性並沒有進行序列化。其實,靜態(static)成員變量是屬於類級別的,而序列化是針對對象的,所以不能序列化哦。經過序列化和反序列化過程後,specialty 欄位變量值由'計算機專業'變為空了,為什麼呢?
  • Java程式設計師必備:序列化全方位解析
    序列化:把Java對象轉換為字節序列的過程反序列:把字節序列恢復為Java對象的過程 二、為什麼需要序列化?Java對象是運行在JVM的堆內存中的,如果JVM停止後,它的生命也就戛然而止。java.io.ObjectInputStream表示對象輸入流,它的readObject()方法,從輸入流中讀取到字節序列,反序列化成為一個對象,最後將其返回。五、序列化的使用序列化如何使用?
  • 什麼是序列化,怎麼序列化,為什麼序列化,反序列化會遇到什麼問題,如何解決.
    二、什麼情況下需要序列化 當你想把的內存中的對象狀態保存到一個文件中或者資料庫中時候;(老實說,上面的幾種,我可能就用過個存資料庫的)三、如何實現序列化 實現Serializable接口即可上面這些理論都比較簡單,下面實際代碼看看這個序列化到底能幹啥
  • 支付寶面試:什麼是序列化,怎麼序列化,為什麼序列化,反序列化會遇到什麼問題,如何解決?
    # 關於 serialVersionUID 的描述序列化運行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用於驗證序列化對象的發送者和接收者是否為該對象加載了與序列化兼容的類
  • 【Java拾遺】不可不知的 Java 序列化
    為什麼需要序列化通常情況下,我們的語言一方面用於交流,比如聊天,把我腦海中的思想,通過語言表達出來,對方聽到我們的話語,會意我們的想法。另一方面,我們的語言除了用於溝通交流,還可以用於記錄。有一句話叫做『好記性不如爛筆頭』。說的就是記錄的重要性,因為話在我們的腦子裡,很容易就忘了,通過記錄下來可以保存更久。
  • 一文帶你全面了解Java 序列化
    在開發過程中經常會對實體進行序列化,但其實我們只是在「只知其然,不知其所以然」的狀態,很多時候會有這些問題:什麼是序列化和反序列化?為什麼要序列化?怎麼實現序列化?序列化的原理是什麼呢?JDK類庫中序列化和反序列化APIjava.io.ObjectInputStream:對象輸入流該類中的readObject()方法從輸入流中讀取字節序列,然後將字節序列反序列化為一個對象並返回。
  • 明晰 | Java序列化與反序列化
    實戰含義序列化:對象寫入IO流中,實現對象變成文件。反序列化:把文件中的對象,進行恢復,恢復到內存中,實現反序列化。意義:序列化的最大的意義在於實現對象可以跨主機進行傳輸,這些對象可以實現保存在磁碟中,並且脫離程序而獨立存在。
  • 教你徹底學會Java序列化和反序列化
    Java序列化是指把Java對象轉換為字節序列的過程,Java反序列化是指把字節序列恢復為Java對象的過程。反序列化:客戶端重文件,或者網絡中獲取到文件以後,在內存中重構對象。序列化:對象序列化的最重要的作用是傳遞和保存對象的時候,保證對象的完整性和可傳遞性。方便字節可以在網絡上傳輸以及保存在本地文件。
  • Java反序列化漏洞從理解到實踐
    這也是為什麼我們在學到知識後要付諸實踐的原因所在。在本文中,我們會深入分析大家非常熟悉的Java發序列化漏洞。對我們而言,最好的實踐就是真正理解手頭掌握的知識,並可以根據實際需要加以改進利用。本文的主要內容包括以下兩方面:1. 利用某個反序列化漏洞。2. 自己手動創建利用載荷。
  • 關於 Java 序列化你不知道的 5 件事
    為了使 Java 運行時相信兩種類型實際上是一樣的,第二版和隨後版本的 Person 必須與第一版有相同的序列化版本 hash(存儲為 private static final serialVersionUID 欄位)。因此,我們需要 serialVersionUID 欄位,它是通過對原始(或 V1)版本的 Person 類運行 JDK serialver命令計算出的。
  • 深入學習 Java 序列化
    第一部分:WhatJava序列化是指把Java對象保存為二進位字節碼的過程,Java反序列化是指把二進位碼重新轉換成Java對象的過程。那麼為什麼需要序列化呢?因為數據只能夠以二進位的形式在網絡中進行傳輸,因此當把對象通過網絡發送出去之前需要先序列化成二進位數據,在接收端讀到二進位數據之後反序列化成Java對象。第二部分:How本部分以序列化到文件為例講解Java序列化的基本用法。
  • 為什麼還要實現它?
    Serializable 接口之所以定義為空,是因為它只起到了一個標識的作用,告訴程序實現了它的對象是可以被序列化的,但真正序列化和反序列化的操作並不需要它來完成。03、再來點注意事項 開門見山的說吧,static 和 transient 修飾的欄位是不會被序列化的。為什麼呢?我們先來證明,再來解釋原因。
  • JAVA反序列化—FastJson抗爭的一生
    @type那麼除開正常的序列化,反序列化。fastjson提供特殊字符段@type,這個欄位可以指定反序列化任意類,並且會自動調用類中屬性的特定的set,get方法。並沒有調用is開頭的方法自己從源碼中分析或者嘗試在類中添加isXx方法都是不會被調用的,這裡只是為了指出其他文章中的一個錯誤。這個與調用的set方法綁定的屬性,再之後並沒有發現對於調用過程有什麼影響。所以只要目標類中有滿足條件的set方法,然後得到的方法變量名存在於序列化字符串中,這個set方法就可以被調用。
  • 文庫 | 反序列化漏洞匯總
    Java 反射主要提供以下功能:重點:是運行時而不是編譯時反射機制在java反序列化漏洞的利用過程中有很重要的作用,此處做一個簡單的介紹,具體知識與實現可參考:http://r6d.cn/MfXa關鍵類說明位置:Java.io.ObjectOutputStream  java.io.ObjectInputStream  序列化:
  • 聽說這10道Java面試題90%的人都不會!!!
    傳統單例的另一個問題是,一旦實現可序列化接口,它們就不再是 Singleton, 因為 readObject() 方法總是返回一個新實例, 就像 Java 中的構造函數一樣。如果沒有方法,那麼可序列化接口的用途是什麼?可序列化 Serializalbe 接口存在於java.io包中,構成了 Java 序列化機制的核心。它沒有任何方法, 在 Java 中也稱為標記接口。當類實現 java.io.Serializable 接口時, 它將在 Java 中變得可序列化, 並指示編譯器使用 Java 序列化機制序列化此對象。
  • Java中枚舉的線程安全性及序列化問題
    本文將深入分析枚舉的源碼,看一看枚舉是怎麼實現的,他是如何保證線程安全的,以及為什麼用枚舉實現的單例是最好的方式。要想看源碼,首先得有一個類吧,那麼枚舉類型到底是什麼類呢?是enum嗎?當我們使用enmu來定義一個枚舉類型的時候,編譯器會自動幫我們創建一個final類型的類繼承Enum類,所以枚舉類型不能被繼承,我們看到這個類中有幾個屬性和方法。
  • Kryo 2.23 發布,高性能 Java 序列化庫
    a generic class (f9cb9ea)Fix #176 Remove unused "kryo" fields from a number of classes. (77e319f)Fix #168 Infinite loop while extending buffer (82d134d)Fix #100 Serialization for java.util.Locale