<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ZIP32文件格式詳解

          共 4388字,需瀏覽 9分鐘

           ·

          2022-01-03 09:28

          ZIP32文件格式詳解

          為什么要去了解ZIP文件格式

          最近有個(gè)需求,需要加載jar包中的jar包中的class,此時(shí)有兩種方式:

          • 1、將jar解壓縮,然后將解壓縮后的路徑添加到class path,這樣就不存在嵌套jar的讀取方式了,tomcat就是采用的這種方式;

          • 2、不解壓縮jar,直接定位到j(luò)ar包中的jar包中的class,然后將其數(shù)據(jù)抽離出來,將其解析為class注冊(cè)到系統(tǒng)中去(可以利用ClassLoader的defineClass方法實(shí)現(xiàn)),spring-boot就是采用的這種方式;

          最終我們決定使用第二種方案(解壓可能會(huì)導(dǎo)致我們的環(huán)境污染,多出來許多文件),即不解壓縮jar包,而這就需要我們深入了解jar包的格式,由于jar包就是ZIP格式的,所以要實(shí)現(xiàn)該功能必須得了解ZIP格式,這樣才能從中抽取我們需要的數(shù)據(jù);

          ZIP文件設(shè)計(jì)

          。ZIP文件是存儲(chǔ)多個(gè)文件的存檔。ZIP允許使用許多不同的方法壓縮包含的文件,以及簡單地存儲(chǔ)文件而不壓縮它。每個(gè)文件都單獨(dú)存儲(chǔ),允許使用不同的方法壓縮同一存檔中的不同文件。由于 ZIP 存檔中的文件是單獨(dú)壓縮的,因此可以提取它們或添加新文件,而無需對(duì)整個(gè)存檔應(yīng)用壓縮或解壓縮。

          ZIP文件有一個(gè)目錄,記錄了ZIP文件中的各個(gè)文件的位置以及大小等信息,目錄放置在 ZIP 文件的末尾。這允許ZIP讀取器加載文件列表,而無需讀取整個(gè)ZIP存檔。ZIP 存檔還可以包含與 ZIP 存檔無關(guān)的額外數(shù)據(jù)。這允許將ZIP存檔預(yù)置到ZIP存檔中并將文件標(biāo)記為可執(zhí)行文件,從而將ZIP存檔制作成自解壓存檔(解壓縮其包含數(shù)據(jù)的應(yīng)用程序)。將目錄存儲(chǔ)在末尾還可以通過將壓縮文件附加到無害的文件(如 GIF 圖像文件)來隱藏壓縮文件。

          個(gè)人理解:ZIP文件的這種特性也就意味著我們不僅僅可以在前邊放置自解壓程序,還可以防止一些滲透攻擊程序,也就是說ZIP文件可能是不安全的,別人可能用一個(gè)ZIP文件來攻擊我們;

          ZIP文件格式

          ZIP文件通常由以下五部分組成:

          • local file header(每個(gè)文件對(duì)應(yīng)一個(gè))

          • data(文件實(shí)際數(shù)據(jù),可能壓縮,也可能未壓縮)

          • data descriptor(條件存在)

          • central directory file header(中央目錄,每個(gè)文件一條記錄)

          • end of central directory record(后續(xù)簡稱?EOCD?全局只有一個(gè))

          當(dāng)我們將多個(gè)文件壓縮為一個(gè)ZIP包時(shí),多個(gè)文件在ZIP包中是分別存儲(chǔ)的,每個(gè)文件都會(huì)生成一個(gè)entry,entry包括最前邊的?local file header?、中間的data(實(shí)際的文件數(shù)據(jù),可能被壓縮了,也可能沒壓縮)以及末尾的data descriptor(這個(gè)是有條件的存在,后續(xù)會(huì)介紹);所有文件組成的entry存儲(chǔ)完畢后生成?central directory file header?,每個(gè)文件都會(huì)對(duì)應(yīng)一個(gè)?central directory file header?,所有的?central directory file header?結(jié)束后會(huì)添加一個(gè)?EOCD?,這個(gè)是一個(gè)ZIP文件只有一個(gè);

          最終我們的ZIP文件存儲(chǔ)結(jié)構(gòu)如下:





          ZIP文件各個(gè)部分詳細(xì)說明

          local file header定義

          offsetbytesdescription
          04local file header簽名,固定值:0x04034b50
          42提取本文件需要的最低版本號(hào)
          62標(biāo)志位,如果第三個(gè)bit被設(shè)置了(0x08),表示寫入的時(shí)候不知道數(shù)據(jù)大小和CRC-32,則該entry包含data descriptor部分
          82壓縮方法,0表示本entry沒有壓縮,只是歸檔到ZIP中了,0x08表示使用了DEFLATE算法壓縮
          102文件最后修改時(shí)間
          122文件最后修改日期
          144文件壓縮前的CRC-32
          184文件壓縮后的大小,單位byte(如果是0xffffffff則表示是ZIP64文件,我們暫時(shí)不關(guān)心ZIP64,因?yàn)槟壳癹ar基本都是ZIP32的)
          224文件壓縮前的大小,單位byte(如果是0xffffffff則表示是ZIP64文件)
          262文件名長度,單位byte,這表示我們的文件名長度不能超過65535 byte,并且這里的文件名是包含前邊的目錄的
          282擴(kuò)展字段長度,單位byte(這個(gè)我們暫時(shí)并不關(guān)心,所以無需去解析)
          30n文件名
          30 + nm擴(kuò)展字段

          從這個(gè)local file header定義可以看出local file header實(shí)際上是變長的,并不是定長的(因?yàn)槲募蛿U(kuò)展字段長度都是不固定的);

          data descriptor

          如果entry的local file header的標(biāo)志位第三個(gè)bit被設(shè)置了(0x08),表示寫入的時(shí)候不知道數(shù)據(jù)大小和CRC-32,則該entry包含本部分;

          offsetbytesdescription
          04簽名,固定值:0x08074b50
          44文件壓縮前的CRC-32
          84文件壓縮后的大小,單位byte
          124文件壓縮前的大小,單位byte

          central directory file header

          每個(gè)entry都有一個(gè)central directory file header記錄,central directory file header中的大部分信息都是冗余的local file header(或者data descriptor)中的信息,然后就是entry的定位信息,主要就是為了根據(jù)中央目錄快速定位entry,而不用讀取完整的整個(gè)文件去查找某個(gè)文件,這在早期磁盤較?。ㄒ馕吨粋€(gè)ZIP文件可能存儲(chǔ)于多個(gè)磁盤)、讀取速度較慢的場景下極為有用,相當(dāng)于索引;

          offsetbytesdescription
          04簽名,固定值:0x02014b50
          42制作于那個(gè)ZIP版本
          62解析該目錄需要的最低版本號(hào),與對(duì)應(yīng)entry的local file header中的值一致
          82一般標(biāo)志位,與對(duì)應(yīng)entry的local file header中的值一致
          102壓縮方法,與對(duì)應(yīng)entry的local file header中的值一致
          122文件最后修改時(shí)間,與對(duì)應(yīng)entry的local file header中的值一致
          142文件最后修改日期,與對(duì)應(yīng)entry的local file header中的值一致
          164文件壓縮前的CRC-32
          204文件壓縮后大小
          244文件壓縮前大小
          282文件名長度(n),與對(duì)應(yīng)entry的local file header中的值一致
          302擴(kuò)展字段長度(m),與對(duì)應(yīng)entry的local file header中的值一致
          322文件備注長度(k)
          342文件起始位置所在的磁盤編號(hào)(這個(gè)用于zip跨磁盤的場景,在早期磁盤(軟盤)是很小的,所以一個(gè)zip文件可能會(huì)跨多個(gè)磁盤,而現(xiàn)在基本不太可能出現(xiàn)這種場景了)
          362內(nèi)部文件屬性
          384外部文件屬性
          424本目錄指向的entry相對(duì)于第一個(gè)entry的起始位置,單位byte,這也就限制了ZIP文件最大也就是4G了,再大就無法定位了
          46n文件名
          46+nm擴(kuò)展字段
          46+n+mk文件備注

          EOCD

          標(biāo)志ZIP文件結(jié)束,判斷一個(gè)文件是否是ZIP格式的文件就是讀取文件末尾的EOCD來判斷,而不是像其他文件一樣讀取文件頭來判斷;

          offsetbytesdescription
          04簽名,固定值:0x06054b50
          42占用磁盤數(shù)(0xffff表示是ZIP64格式)
          62中央目錄的起始位置所在的磁盤
          82當(dāng)前磁盤上的中央目錄記錄數(shù)
          102中央目錄的總數(shù)量(0xffff表示是ZIP64格式),這限制了一個(gè)ZIP32文件中存儲(chǔ)的文件數(shù)最多不能超過65534個(gè)
          124中央目錄的總大?。▎挝籦yte),(0xffffffff表示這是一個(gè)ZIP64文件)
          164中央目錄相對(duì)于ZIP文件的起始位置(注意,是ZIP文件,不是ZIP文件的第一個(gè)entry,這是一個(gè)細(xì)微的差別,在大多數(shù)場景下都是可以忽略的,但是如果ZIP文件頭包含一些前綴數(shù)據(jù),例如自解壓程序時(shí),這個(gè)起始位置是包含這些前綴數(shù)據(jù)的,而zip的第一個(gè)entry是在這些前綴數(shù)據(jù)之后,如果不關(guān)注這個(gè)細(xì)節(jié)可能會(huì)導(dǎo)致ZIP解析失?。?/td>
          202備注長度
          22n備注

          回到我們的需求

          由于我們采用了第二種方式,所以我們需要能從ZIP中抽取我們需要的數(shù)據(jù),而根據(jù)上述ZIP規(guī)范描述ZIP的中央目錄設(shè)計(jì)正好能滿足我們的需求,剩下的就是如何實(shí)現(xiàn)的問題了,下面說下大概思路(如果需要具體實(shí)現(xiàn)代碼可以參考spring-boot-loader中的相關(guān)代碼):

          根據(jù)ZIP規(guī)范,我們讀取一個(gè)ZIP時(shí)應(yīng)該從后讀取,先讀取到EOCD來確定這是一個(gè)ZIP文件,同時(shí)根據(jù)EOCD來定位到中央目錄,然后讀取中央目錄,根據(jù)中央目錄構(gòu)建索引,找到我們要讀取的文件,由于我們是需要讀取嵌套jar,所以也需要對(duì)嵌套jar構(gòu)建索引,所以我們找到嵌套jar后還需要嵌套解析,最后類加載的時(shí)候根據(jù)索引來查找類數(shù)據(jù);

          這里有一個(gè)關(guān)鍵性的問題,當(dāng)我們把索引構(gòu)建完畢后,如果類加載的時(shí)候發(fā)現(xiàn)一個(gè)jar是在嵌套jar中,并且該嵌套jar是以壓縮的方式存儲(chǔ)在ZIP文件中,那么我們就需要對(duì)整個(gè)jar進(jìn)行解壓縮,否則我們是無法定位到嵌套jar中的class數(shù)據(jù)的,此時(shí)解壓嵌套jar就存在兩種策略了:一個(gè)就是我們將嵌套jar解壓縮后緩存到內(nèi)存,下次需要加載該jar中的class的時(shí)候直接從內(nèi)存中取數(shù)據(jù),可能這個(gè)嵌套jar中有幾百個(gè)class,而只有兩三個(gè)class是我們需要的,這樣會(huì)導(dǎo)致內(nèi)存浪費(fèi),還有一個(gè)就是我們每次需要從該嵌套jar中加載class的時(shí)候重新解壓縮該嵌套jar,這就會(huì)導(dǎo)致我們的class加載極為耗CPU,同時(shí)也會(huì)導(dǎo)致類加載比較緩慢;

          那么上述問題該如何解決呢?我們發(fā)現(xiàn),導(dǎo)致上述問題的原因就是因?yàn)榍短譲ar是壓縮存儲(chǔ)在ZIP中的,那么如果嵌套jar是未壓縮的呢?如果嵌套jar只是存儲(chǔ)在ZIP中,但是并未壓縮,那么我們可以直接使用偏移量定位到嵌套jar中的class數(shù)據(jù),就不會(huì)存在上述問題了,而實(shí)際上spring-boot也是這樣解決這個(gè)問題的;

          至此,我們的需求就解決了,而在此過程中我們也學(xué)到了很多?非(并)常(無)有(卵)用(用)?的知識(shí);

          參考文獻(xiàn)

          • ZIP文件格式:https://en.wikipedia.org/wiki/ZIP_(file_format)


          瀏覽 100
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  韩日内射| 69成人视 | 在线看AV的网站 | а√中文在线资源库 | 日日操夜夜撸 |