【Stream—5】MemoryStream相關知識分享

一、簡單介紹一下MemoryStream

MemoryStream是內存流,為系統內存提供讀寫操作,由於MemoryStream是通過無符號字節數組組成的,可以說MemoryStream的性能可以算比較出色,所以它擔當起了一些其他流進行數據交互安時的中間工作,同時可降低應用程序中對臨時緩衝區和臨時文件的需求,其實MemoryStream的重要性不亞於FileStream,在很多場合,我們必須使用它來提高性能

二、MemoryStream和FileStream的區別

前文中也提到了,FileStream主要對文件的一系列操作,屬於比較高層的操作,但是MemoryStream卻很不一樣,他更趨向於底層內存的操作,這樣能夠達到更快速度和性能,也是他們的根本區別,很多時候,操作文件都需要MemoryStream來實際進行讀寫,最後放入相應的FileStream中,不僅如此,在諸如XmlWriter的操作中也需要使用MemoryStream提高讀寫速度

三、分析MemoryStream最常見的OutOfMemory異常

先看一下下面一段簡單的代碼

 1             //測試byte數組 假設該數組容量是256M
 2             var testBytes = new byte[256 * 1024 * 1024];
 3             var ms = new MemoryStream();
 4             using (ms)
 5             {
 6                 for (int i = 0; i < 1000; i++)
 7                 {
 8                     try
 9                     {
10                         ms.Write(testBytes, 0, testBytes.Length);
11                     }
12                     catch
13                     {
14                         Console.WriteLine("該內存流已經使用了{0}M容量的內存,該內存流最大容量為{1}M,溢出時容量為{2}M",
15                             GC.GetTotalMemory(false) / (1024 * 1024),//MemoryStream已經消耗內存量
16                             ms.Capacity / (1024 * 1024), //MemoryStream最大的可用容量
17                             ms.Length / (1024 * 1024));//MemoryStream當前流的長度(容量)
18                         break;
19                     }
20                 }
21             }
22             Console.ReadLine();

輸出結果:

 

 

 從輸出結果來看,MemoryStream默認可用最大容量是1024M,發生異常時正好是其最大容量。

問題來了,假設我們需要操作比較大的文件,該怎麼辦呢?其實有2種方法可以搞定,一種是分段處理,我們將Byte數組分成等份進行處理,還有一種方式便是增加MomoryStream的最大可用容量(字節),我們可以在聲明MomoryStream的構造函數時利用它的重載版本:MemoryStream(int capacity)

到底使用哪種方法比較好呢?其實筆者認為具體項目具體分析,前者分段處理的確能夠解決大數據量操作的問題,但是犧牲了性能和時間(多線程暫時不考慮),後者可以得到性能上的優勢,但是其允許最大容量是 int.Max,所以無法給出一個明確的答案,大家在做項目時,按照需求自己定製即可,最關鍵的還是要取到性能和開銷的最佳點位,還有一種更噁心的溢出方式,往往會讓大家抓狂,就是不定時溢出,就是MemoryStream處理的文件可能只有40M或更小時,也會發生OutOfMemory的異常,關於這個問題,終於在老外的一篇文章中得到了解釋,運氣還不錯,可以看看這篇博文:,由於涉及到windows的內存機制,包括內存也,進程的虛擬地址空間等,比較複雜,所以大家看他的文章前,我先和大家簡單的介紹一下頁和進程的虛擬地址:

內存頁:內存頁分為:文件頁和計算頁

內存中的文件頁是文件緩存區,即文件型的內存頁,用於存放文件數據的內存頁(也稱永久頁),作用在於讀寫文件時可以減少對磁盤的訪問,如果它的大小設置的太小,會引起系統頻繁的訪問磁盤,增加磁盤I/O,設置太大,會浪費內存資源。

內存中的計算頁也稱為計算型的內存頁,主要用於存放程序代碼和臨時使用的數據。

進程的虛擬地址:每一個進程被給予它的非常自由的虛擬地址空間。對於32位的進程,地址空間是4G,因為一個32位指針能夠從0x00000000到0xffffffff之間的任意值,這個範圍允許指針從4294967296個值得一個,覆蓋了一個進程得4G範圍,對於64位進程,地址空間是16eb因為一個64位指針能夠指向18,446,744,073,709,551,616個值中的一個,覆蓋一個進程的16eb範圍,這是十分寬廣的範圍,上述概念都在自windows核心編程這本書,其實這本書對於我們程序員來說很重要,對於內存的操作,本人也是小白。

四、MemoryStream的構造函數

1、MemoryStream()

MemoryStream允許不帶參數的構造

2、MemoryStream(byte[] byte)

Byte數組是包含了一定數據的byte數組,這個構造很重要,初學者或者用的不是很多的程序員會忽略這個構造函數導致後面讀取或寫入數據時發現MemoryStream中沒有byte數據,會導致很鬱悶的感覺,大家注意一下就行,有時候也可能無需這樣,因為很多方法返回值已經是MemoryStream了。

3、MemoryStream(int capacity)

這個是重中之重,為什麼這麼說呢?我在本文探討關於OutMemory異常中也提到了,如果你想額外提高MemoryStream的吞吐量,也只能靠這個方法提升一定的吞吐量,最多也只能到int.Max,這個方法也是解決OutOfMemory的一個可行方案。

4、MemoryStream(byte[] byte,bool writeable)

writeable參數定義該流是否可寫

5、MemoryStream(byte[] byte,int index,int count)

index:參數定義從byte數組中的索引index

count:參數是獲取的數據量的個數

6、MemoryStream(byte[] byte,int index,int count,bool writeable,bool publiclyVisible)

publiclyVisible:參數表示true可以啟用GetBuffer方法,它返回無符號字節數組,流從該數組創建,否則為false。(大家一定覺得這個很難理解,別急,下面的方法中我會詳細的講一下這個東西)

五、MemoryStream的屬性

 Memory的屬性大致都是和其父類很相似,這些功能在我的這篇文章中已經詳細討論過,所以我簡單列舉以下其屬性:

 

 其獨有的屬性:

Capacity:這個前文其實已經提及,它表示該流的可支配容量(字節),非常重要的一個屬性。

六、MemoryStream的方法

對於重寫的方法,這裏不再重複說明,大家可以去看一下

 以下是MemoryStream獨有的方法

1、virtual byte[] GetBuffer()

這個方法使用時需要小心,因為這個方法返回無符號字節數組,也就是說,即使我只輸入幾個字符例如“HellowWorld”我們只希望返回11個數據就行,可是這個方法會把整個緩衝區的數據,包括那些已經分配但是實際上沒有用到的字符數據都返回回來了,如果想啟用這個方法那必須使用上面最後一個構造函數,將publiclyVisible屬性設置成true就行,這也是上面那個構造函數的錯用所在。

2、virtual void WriteTo(Stream stream)

這個方法的目的其實在本文開始的時候討論性能問題時已經指出,MemoryStream常用起中間流的作用,所以讀寫在處理完后將內存吸入其他流中。

七、示例:

1、XmlWriter中使用MemoryStream

 1         public static void UseMemoryStreamInXmlWriter()
 2         {
 3             var ms = new MemoryStream();
 4             using (ms)
 5             {
 6                 //定義一個XmlWriter
 7                 using (XmlWriter writer= XmlWriter.Create(ms))
 8                 {
 9                     //寫入xml頭
10                     writer.WriteStartDocument(true);
11                     //寫入一個元素
12                     writer.WriteStartElement("Content");
13                     //為這個元素增加一個test屬性
14                     writer.WriteStartAttribute("test");
15                     //設置test屬性的值
16                     writer.WriteValue("萌萌小魔王");
17                     //釋放緩衝,這裏可以不用釋放,但是在實際項目中可能要考慮部分釋放對性能帶來的提升
18                     writer.Flush();
19                     Console.WriteLine($"此時內存使用量為:{GC.GetTotalMemory(false)/1024}KB,該MemoryStream已使用容量為:{Math.Round((double)ms.Length,4)}byte,默認容量為:{ms.Capacity}byte");
20                     Console.WriteLine($"重新定位前MemoryStream所在的位置是{ms.Position}");
21                     //將流中所在當前位置往後移動7位,相當於空格
22                     ms.Seek(7, SeekOrigin.Current);
23                     Console.WriteLine($"重新定位后MemoryStream所存在的位置是{ms.Position}");
24                     //如果將流所在的位置設置位如下示的位置,則XML文件會被打亂
25                     //ms.Position = 0;
26                     writer.WriteStartElement("Content2");
27                     writer.WriteStartAttribute("testInner");
28                     writer.WriteValue("萌萌小魔王2");
29                     writer.WriteEndElement();
30                     writer.WriteEndElement();
31                     //再次釋放
32                     writer.Flush();
33                     Console.WriteLine($"此時內存使用量為:{GC.GetTotalMemory(false) / 1024}KB,該MemoryStream已使用容量為:{Math.Round((double)ms.Length, 4)}byte,默認容量為:{ms.Capacity}byte");
34                     //建立一個FileStream 文件創建目的地是f:\test.xml
35                     var fs=new FileStream(@"f:\test.xml",FileMode.OpenOrCreate);
36                     using (fs)
37                     {
38                         //將內存流注入FileStream
39                         ms.WriteTo(fs);
40                         if (ms.CanWrite)
41                         {
42                             //釋放緩衝區
43                             fs.Flush();
44                         }
45                     }
46                     Console.WriteLine();
47                 }
48             }
49         }

運行結果:

 

 咱看一下XML文本是什麼樣的?

 

 2、自定義處理圖片的HttpHandler

有時項目里我們必須將圖片進行一定的操作,例如:水印,下載等,為了方便和管理我們可以自定義一個HttpHandler來負責這些工作

後台代碼:

 1     public class ImageHandler : IHttpHandler
 2     {
 3         /// <summary>
 4         /// 實現IHttpHandler接口中ProcessRequest方法
 5         /// </summary>
 6         /// <param name="context"></param>
 7         public void ProcessRequest(HttpContext context)
 8         {
 9             context.Response.Clear();
10             //得到圖片名
11             var imageName = context.Request["ImageName"] ?? "小魔王";
12             //得到圖片地址
13             var stringFilePath = context.Server.MapPath($"~/Image/{imageName}.jpg");
14             //聲明一個FileStream用來將圖片暫時放入流中
15             FileStream stream=new FileStream(stringFilePath,FileMode.Open);
16             using (stream)
17             {
18                 //通過GetImageFromStream方法將圖片放入Byte數組中
19                 var imageBytes = GetImageFromStream(stream, context);
20                 //上下文確定寫道客戶端時的文件類型
21                 context.Response.ContentType = "image/jpeg";
22                 //上下文將imageBytes中的數組寫到前端
23                 context.Response.BinaryWrite(imageBytes);
24             }
25         }
26 
27         public bool IsReusable => true;
28 
29         /// <summary>
30         /// 將流中的圖片信息放入byte數組后返回該數組
31         /// </summary>
32         /// <param name="stream">文件流</param>
33         /// <param name="context">上下文</param>
34         /// <returns></returns>
35         private byte[] GetImageFromStream(FileStream stream, HttpContext context)
36         {
37             //通過Stream到Image
38             var image = Image.FromStream(stream);
39             //加上水印
40             image = SetWaterImage(image, context);
41             //得到一個ms對象
42             MemoryStream ms = new MemoryStream();
43             using (ms)
44             {
45                 //將圖片保存至內存流
46                 image.Save(ms,ImageFormat.Jpeg);
47                 byte[] imageBytes = new byte[ms.Length];
48                 ms.Position = 0;
49                 //通過內存流放到imageBytes
50                 ms.Read(imageBytes, 0, imageBytes.Length);
51                 //ms.Close();
52                 //返回imageBytes
53                 return imageBytes;
54             }
55         }
56 
57         private Image SetWaterImage(Image image, HttpContext context)
58         {
59             Graphics graphics = Graphics.FromImage(image);
60             Image waterImage = Image.FromFile(context.Server.MapPath("~/Image/logo.jpg"));
61             graphics.DrawImage(waterImage, new Point { X = image.Size.Width - waterImage.Size.Width, Y = image.Size.Height - waterImage.Size.Height });
62             return image;
63         }
64     }

別忘了,還要再web.config中進行配置,如下:

 

 這樣前台就能使用了

 

 讓我們來看一下輸出結果:

 

 哈哈,還不錯。

好了,MemoryStream相關的知識就先分享到這裏了。同志們,再見!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

電動車電池成長,帶動致茂營收倍翻

受惠於電動車動力電池需求增加,致茂電子的七月合併營收創下歷史新高。今年前七個月的累計營收更已與去年全年相當。

致茂電子表示,因電動車動力電池製造的關鍵技術(turnkey solutions)銷售蓬勃,帶動七月營收大漲,合併營收達新台幣16.2億元,不僅較上月成長71%、更較去年七月成長101%,營收數字創下歷史新高。

此外,母公司的7月單月營收也有128%的月增與172%的年增,達新台幣12.6億元。強勁的需求使致茂今年前七個月的合併營收年增27%來到69.1億新台幣;母公司前七個月的累計營收新台幣45億元,也已相當於去年全年水準。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

蘋果傳將攜手韓廠研發電動車用電池

蘋果公司持續擴大業務範圍,除新成立的蘋果能源(Apple Energy)成功取得售電許可外,市場上關注度十足的蘋果電動車專案「泰坦計畫」(Project Titan)也時常傳出新消息。近期傳言蘋果將與南韓某企業合作,運用該南韓公司的電池專利,攜手開發電動車用電池。

根據日本蘋果情報網站iPhone Mania、南韓媒體ET News 等報導,蘋果看上某家南韓廠商所取得的鋰電池專利,打算與該廠合作開發電動車用電池。被看上的鋰電池專利技術,電池中央部位採中空設計,可使空氣流通來冷卻電池,等同降低冷卻系統的需求,騰出更多空間來增加電池容量。

日、韓媒體並未披露可能將與蘋果合作的韓廠名稱,但美國MacRumors 透剁歐洲專利局得知,這款「中央中空」的電池是由一家稱為Orange Power 的南韓廠商取得。該廠係一小廠,員工人數僅33人,其中多數為研發人員。

蘋果電動車計畫自曝光以來即備受矚目,但至今仍然只聞樓梯響。據了解,目前共有約1,000人參與蘋果電動車企劃,且蘋果已在柏林設立秘密開發實驗室。MoneyDJ引述The Information網站的說法,表示蘋果電動車可能在2021年亮相;但也有分析師認為,蘋果電動車可能會步上蘋果電視的後塵,胎死腹中。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

計算機圖形學—— 隱藏線和隱藏面的消除(消隱算法)

 

一、概述

由於投影變換失去了深度信息,往往導致圖形的二義性。要消除二義性,就必須在繪製時消除被遮擋的不可見的線或面,習慣上稱作消除隱藏線和隱藏面(或可見線判定、可見面判定),或簡稱為消隱。經過消隱得到的投影圖稱為物體的真實感圖形。

下面這個圖就很好體現了這種二義性。

消隱后的效果圖:

消隱算法的分類

所有隱藏面消隱算法必須確定:
在沿透視投影的投影中心或沿平行投影的投影方向看過去哪些邊或面是可見的

兩種基本算法

1、以構成圖像的每一個像素為處理單元,對場景中的所有表面,確定相對於觀察點是可見的表面,用該表面的顏色填充該像素.
適於面消隱。

算法步驟:

a.在和投影點到像素連線相交的表面中,找到離觀察點最近的表面;
b.用該表面上交點處的顏色填充該像素;

2、以三維場景中的物體對象為處理單元,在所有對象之間進行比較,除去完全不可見的物體和物體上不可見的部分.適於面消隱也適於線消隱。

算法步驟:
a.判定場景中的所有可見表面;
b.用可見表面的顏色填充相應的像素以構成圖形;

提醒注意

1.假定構成物體的面不能相互貫穿,也不能有循環遮擋的情況。
2.假定投影平面是oxy平面,投影方向為z軸的負方向。

如果構成物體的面不滿足該假定,可以把它們剖分成互不貫穿和不循環遮擋的情況。
例如,用圖b中的虛線便可把原來循環遮擋的三個平面,分割成不存在循環遮擋的四個面。  

二、可見面判斷的有效技術

1、邊界盒

指能夠包含該物體的一個幾何形狀(如矩形/圓/長方體等),該形狀有較簡單的邊界。

 

邊界盒技術用於判斷兩條直線是否相交。

進一步簡化判斷

2、後向面消除(Back-face Removal)

思路:把顯然不可見的面去掉,減少消隱過程中的直線求交數目

 

 

如何判斷:根據定義尋找外(或內)法向,若外法向背離觀察者,或內法向指向觀察者,則該面為後向面。

 

 

 

 

 

 

 

 

 

 

注意:如果多邊形是凸的,則可只取一個三角形計算有向面積sp。如果多邊形不是凸的,只取一個三角形計算有向面積sp可能會出現錯誤,即F所在的面為前向面也可能出現sp≥0的情況,因此,需按上式計算多邊形F的有向面積。如果sp ≥0,則F所在的面為後向面。

3、非垂直投影轉化成垂直投影

物體之間的遮擋關係與投影中心和投影方向有着密切的關係,因此,對物體的可見性判定也和投影方式有密切的關係。

垂直投影的優點:進行投影時可以忽略z值,即:實物的(x,y)可直接做為投影后的二維平面上的坐標(x,y)

上述討論說明,垂直投影比非垂直投影容易實現,並且計算量小。因此在進行消隱工作之前,首先應將非垂直投影轉換成垂直投影,從而降低算法的複雜性,提高運算速度。

如何把透視投影變為垂直投影,其本質是把稜台變成長方體。

三、基於窗口的子分算法(Warnack算法)

是一種分而治之(Divide-Conquer)的算法。

 

1、關係判斷

2、可見性判斷

3、分隔結束條件

4、提高效率的有效的處理技術

5、算法描述

用多邊形的邊界對區域作劃分,其目的是盡量減少對區域劃分的次數--利用裁剪算法

 

四、八叉樹算法

為了生成真實感圖形,關鍵問題之一就是對圖像空間的每一個像素進行處理。從場景中所有的在該像素上有投影的表面中確定相對於觀察點是可見表面。為了提高算法效率,自然是希望從可能在像素上有投影的面片中尋找可見表面。八叉樹算法是快速尋找可見面的一種有效方法,是一種搜索算法。

基本思想:將能夠包含整個場景的立方體,即八叉樹的根結點,按照x,y,z三個方向中的剖面分割成八個子立方體,稱為根結點的八個子結點。對每一個子立方體,如果它包含的表面片少於一個給定的值,則該子立方體為八叉樹的終端結點,否則為非終端結點並將其進一步分割成八個子立方體;重複上述過程,直到每個小立方體所包含的表面片少於一個給定的值,分割即告終止。

 

 

那麼對於上述圖所示,視圖平面法向量(1,1,1)那麼此時它的一個排列是0,1,2,4,3,5,6,7,即最遠的八分體是0,與八分體0共享一個面的三個相鄰八分體是1,2和4,與最近八分體7的3個相鄰八分體是3,5和6。

 

五、Z緩衝器算法

1、算法描述

z緩衝器算法是最簡單的隱藏面消除算法之一。
基本思想:對屏幕上每一個像素點,過像素中心做一條投影線,找到此投影線與所有多邊形交點中離觀察者最近的點,此點的屬性(顏色或灰度)值即為這一屏幕像素點的屬性值。

需要兩個緩衝器數組,即:z緩衝器數組和幀緩衝器數組,分別設為 Zdepth[ ][ ] 與  Frame[ ][ ]
z緩衝器是一組存貯單元,其單元個數和屏幕上像素的個數相同,也和幀緩衝器的單元個數相同,它們之間一一對應。
幀緩衝器每個單元存放對應像素的顏色值;z緩衝器每個單元存放對應像素的深度值;

2、算法實現

 

算法的複雜性正比於m*n*N,在屏幕大小即m*n一定的情況下,算法的計算量只和多邊形個數N成正比

3、優缺點

 z-Buffer算法沒有利用圖形的相關性和連續性,這是z-Buffer算法的嚴重缺陷,更為嚴重的是,該算法是像素級上的消隱算法。

 六、掃描線z緩衝器算法

1、算法描述

將z緩衝器的單元數置為和一條掃描線上的像素數目相同。
從最上面的一條掃描線開始工作,向下對每一條掃描線作如下處理:

 

掃描線算法也屬於圖像空間消隱算法。該算法可以看作是多邊形區域填充里介紹過的邊相關掃描線填充算法的延伸。不同的是在消隱算法中處理的是多個面片,而多邊形填充中是對單個多邊形面進行填充。

2、數據結構

對每個多邊形,檢查它在oxy平面上的投影和當前掃描線是否相交?
若不相交,則不考慮該多邊形。
如果相交,則掃描線和多邊形邊界的交點是成對地出現
每對交點中間的像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較
若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形。
對幀緩衝器每一行的單元都填上相應內容后就得到了整個消隱后的圖。

每處理一條掃描線,都要檢查各多邊形是否和該線相交,還要計算多邊形所在平面上很多點的z值,需要花費很大的計算
為了提高算法效率,採用跟多邊形掃描轉換中的掃描線算法類似的數據結構和算法.

多邊形Y表

 

實際上是一個指針數組 ,每個表的深度和显示屏幕行數相同.將所有多邊形存在多邊形Y表中,根據多邊形頂點中Y坐標最大值,插入多邊形Y表中的相應位置,多邊形Y表中保存多邊形的序號和其頂點的最大y坐標.

邊Y表

 要注意:Δx是下一條掃描線與邊交點的x減去當前的掃描線與邊交點的x。

多邊形活化表

邊對活化表

其實這裏最難理解的就是Δyl和Δxr了,這裏的意思就是當前掃描線所處的y值和與該掃描線相交邊的最小y值的差值。

就比如說掃描線y=6,與第一個三角形有兩個交點,左交點(4,6),右交點(7,6)那麼Δyl=6-3  Δyr=6-3

3、重溫算法目標

 對每一條掃描線,檢查對每個多邊形的投影是否相交,如相交則交點成對出現,對每對交點中間的每個像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較,若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形,對幀緩衝器每一行的單元都填上相應內容后也就得到了整個消隱后的圖。

4、算法步驟

 

算法描述如下

七、優先級排序表算法

1、算法思想

優先級排序表算法按多邊形離觀察者的遠近來建立一個多邊形排序表,距觀察者遠的優先級低,放在表頭;近的優先級高,放在表尾
從優先級低的多邊形開始,依次把多邊形的顏色填入幀緩衝存儲器中
表中距觀察者近的元素覆蓋幀緩衝存儲器中原有的內容
當優先級最高的多邊形的圖形送入幀緩衝器后,整幅圖形就形成了
類似於油畫家繪畫過程,因此又稱為油畫家算法。

 

 

 

 

 

 

2、算法的優缺點

算法的優點:
簡單,容易實現,並且可以作為實現更複雜算法的基礎;
缺點:
只能處理不相交的面,而且深度優先級表中面的順序可能出錯.

該算法不能處理某些特殊情況。

 

 

解決辦法:把P沿Q平面一分為二,從多邊形序列中把原多邊形P去掉,把分割P生成的兩個多邊形加入鏈表中。具體實現時,當離視點最遠的多邊形P和其他多邊形交換時,要對P做一標誌,當有標誌的多邊形再換成離視點最遠的多邊形時,則說明出現了上述的現象,可用分割方法進行處理 。

用來解決動態显示問題時,可大大提高效率

八、光線投射算法

1、算法原理

要處理的場景中有無限多條光線,從採樣的角度講我們僅對穿過像素的光線感興趣,因此,可考慮從像素出發,逆向追蹤射入場景的光線路徑

 

 

 

 

 

2、算法實現

由視點出發穿過觀察平面上一像素向場景發射一條射線
求出射線與場景中各物體表面的交點
離視點最近的交點的顏色即為像素要填的顏色。
光線投射算法對於包含曲面,特別是包含球面的場景有很高的效率。

 

 

 

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

PostgreSQL空間數據庫創建備份恢復(PostGIS vs ArcGIS)

梯子

PostGIS

創建

安裝就不必介紹了,windows下使用安裝工具Application Stack Builder,選擇空間擴展PostGIS即可自動安裝

然後新建庫后,在庫中執行以下語句創建控件擴展

CREATE EXTENSION postgis

也可以創建數據庫時選擇postgis的模板庫創建

create database postgisdb template postgis_template;

新建數據庫后添加postgis擴展後會發現庫內public模式下函數序列觸發器等都會增加一些postgis相關功能
然後就可以通過PostGIS Shapefile and DBF Loader工具導入shp數據

備份

postgersql的備份恢復主要有

  1. 增量備份和基於時間點恢復(RITR)
  2. pg_dump和pg_dumpall進行轉儲,從SQL轉儲文件恢復
  3. 文件系統級別備份

這裏我們使用簡單,容易掌握的pg_dump命令,一般在安裝目錄bin下
pg_dump備份單庫,不導出角色和表空間相關信息

pg_dump -h localhost -U postgres postgisdb > D:\backup\postgisdb.bak

有一些參數選項可以參考(很多,具體不列了,執行help可以查看到)

pg_dump --help
恢復

恢復可以使用psql

psql -h localhost -U postgres -d postgisdb2 < D:\backup\postgisdb.bak

恢復時可以指定不同的數據庫,如果pg_dump時-C創建數據庫,那也可以不用先新建數據庫

postgis庫的恢復備份還是挺簡單的,所有的東西都在public下

ArcGIS

創建

ArcGIS要連接到postgresql,需要將postgresql安裝目錄lib下的libeay32.dll、libiconv-2.dll、libintl-8.dll、libpq.dll 和 ssleay32.dll拷貝到ArcGIS Desktop的安裝目錄bin下
將ArcGIS Desktop目錄DatabaseSupport\PostgreSQL下的st_geometry.dll拷貝到postgresql的lib下

使用ArcGIS工具箱中Create Enterprise Geodatabase工具創建SDE,完事後會在創建一個sde登陸角色並在庫中創建一個sde模式,包含諸多函數序列管理表等。(注意:ArcGIS雖然可以在系統庫postgres中創建SDE擴展,然並連不上,ArcGIS不允許連接訪問系統數據庫

然後ArcGIS可以連接該數據庫,並且進行空間數據管理操作。(注意:默認ArcGIS創建空間數據,只能創建在和登陸用戶同名的模式下

備份

我們可以向上面PostGIS備份恢復一樣,直接備份整個庫

恢復

如果恢復至同名數據庫,像上面恢復是沒有問題的
但如果數據庫改名了,則會有驚喜發生,ArcGIS管理空間報底層gdb_release之類的錯誤,同樣的問題不止恢復庫時,修改數據庫名稱也不像其他庫那麼隨心所欲,以含SDE擴展的庫為模板創建新庫也會有問題

ArcGIS SDE未見文檔介紹內部結構邏輯,只能猜測大概,或不準確,願聞其詳

ArcSDE空間數據創建時會在SDE管理表裡註冊相關信息,比如空間參考,列啊,表的唯一標識等,便於它做數據管理、版本控制

修改庫名后,ArcSDE管理就出問題,主要是一些註冊項,安裝SDE時也會把該庫的信息註冊到SDE管理表中去,所以新庫名,它就不認識了

如果修改了庫名,我們找到以下錶

select * from sde.gdb_items
you need modify : name physicalname path etc...

update sde.sde_table_registry set database_name='testdb';
update sde.sde_column_registry set database_name='testdb';
update sde.sde_geometry_columns set f_table_catalog='testdb';
update sde.sde_raster_columns set database_name='testdb';
update sde.sde_layers set database_name='testdb';

然後就一切正常

當然我們建議不輕易改庫名

這就是商業軟件,足夠強大不夠靈活,封裝和靈活總會互相博弈

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

Fork/Join框架詳解

Fork/Join框架詳解

Fork/Join框架是Java 7提供的一個用於并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。Fork/Join框架要完成兩件事情:

  • 任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割

  • 執行任務併合並結果:分割的子任務分別放到雙端隊列里,然後幾個啟動線程分別從雙端隊列里獲取任務執行。子任務執行完的結果都放在另外一個隊列里,啟動一個線程從隊列里取數據,然後合併這些數據

ForkJoinTask

使用Fork/Join框架,首先需要創建一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接集成ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:

  • RecursiveAction
    用於沒有返回結果的任務
  • RecursiveTask
    用於有返回結果的任務

ForkJoinPool

ForkJoinTask需要通過ForkJoinPool來執行。

任務分割出的子任務會添加到當前工作線程所維護的雙端隊列中,進入隊列的頭部。當一個工作線程的隊列里暫時沒有任務時,它會隨機從其他工作線程的隊列的尾部獲取一個任務(工作竊取算法);

Fork/Join框架的實現原理

ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,ForkJoinTask數組負責將存放程序提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這些任務;

ForkJoinTask的fork方法的實現原理

當我們調用ForkJoinTask的fork方法時,程序會把任務放在ForkJoinWorkerThread的pushTask的workQueue中,異步地執行這個任務,然後立即返回結果,代碼如下:

public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

pushTask方法把當前任務存放在ForkJoinTask數組隊列里。然後再調用ForkJoinPool的signalWork()方法喚醒或創建一個工作線程來執行任務。代碼如下:

final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    if ((a = array) != null) {    // ignore if queue removed
        int m = a.length - 1;     // fenced write for task visibility
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
        U.putOrderedInt(this, QTOP, s + 1);
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);
        }
        else if (n >= m)
            growArray();
    }
}

ForkJoinTask的join方法的實現原理

Join方法的主要作用是阻塞當前線程並等待獲取結果。讓我們一起看看ForkJoinTask的join方法的實現,代碼如下:

public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL){
        reportException(s);
    }
    return getRawResult();
}

它首先調用doJoin方法,通過doJoin()方法得到當前任務的狀態來判斷返回什麼結果,任務狀態有4種:已完成(NORMAL)、被取消(CANCELLED)、信號(SIGNAL)和出現異常(EXCEPTIONAL);
如果任務狀態是已完成,則直接返回任務結果;
如果任務狀態是被取消,則直接拋出CancellationException;
如果任務狀態是拋出異常,則直接拋出對應的異常;
doJoin方法的實現,代碼如下:

private int doJoin() {
    int s;
    Thread t;
    ForkJoinWorkerThread wt;
    ForkJoinPool.WorkQueue w;
    return (s = status) < 0 ? s :
            ((t = Thread.currentThread()) instanceof                                ForkJoinWorkerThread) ? (w = (wt =                                      (ForkJoinWorkerThread)t).workQueue).tryUnpush(this) && (s =                 doExec()) < 0 ? s : wt.pool.awaitJoin(w, this, 0L) :                externalAwaitDone();
}

doExec() :

final int doExec() {
    int s; 
    boolean completed;
    if ((s = status) >= 0) {
        try {
            completed = exec();
        } catch (Throwable rex) {
            return setExceptionalCompletion(rex);
        }
        if (completed){
            s = setCompletion(NORMAL);
        }
    }
    return s;
}

在doJoin()方法里,首先通過查看任務的狀態,看任務是否已經執行完成,如果執行完成,則直接返回任務狀態;如果沒有執行完,則從任務數組裡取出任務並執行。如果任務順利執行完成,則設置任務狀態為NORMAL,如果出現異常,則記錄異常,並將任務狀態設置為EXCEPTIONAL

Fork/Join框架的異常處理

ForkJoinTask在執行的時候可能會拋出異常,但是我們沒辦法在主線程里直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經拋出異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。代碼如下:

if(task.isCompletedAbnormally())
{
    System.out.println(task.getException());
}

getException方法返回Throwable對象,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有拋出異常則返回null:

public final Throwable getException() {
    int s = status & DONE_MASK;
    return ((s >= NORMAL) ? null :
        (s == CANCELLED) ? new CancellationException() :
        getThrowableException());
}

DEMO

需求:求1+2+3+4的結果
分析:Fork/Join框架首先要考慮到的是如何分割任務,如果希望每個子任務最多執行兩個數的相加,那麼我們設置分割的閾值是2,由於是4個数字相加,所以Fork/Join框架會把這個任務fork成兩個子任務,子任務一負責計算1+2,子任務二負責計算3+4,然後再join兩個子任務的結果。因為是有結果的任務,所以必須繼承RecursiveTask,實現代碼如下:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 *
 * @author aikq
 * @date 2018年11月21日 20:37
 */
public class ForkJoinTaskDemo {

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        CountTask task = new CountTask(1,4);
        Future<Integer> result = pool.submit(task);
        try {
            System.out.println("計算結果=" + result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class CountTask extends RecursiveTask<Integer>{
    private static final long serialVersionUID = -7524245439872879478L;

    private static final int THREAD_HOLD = 2;

    private int start;
    private int end;

    public CountTask(int start,int end){
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        //如果任務足夠小就計算
        boolean canCompute = (end - start) <= THREAD_HOLD;
        if(canCompute){
            for(int i=start;i<=end;i++){
                sum += i;
            }
        }else{
            int middle = (start + end) / 2;
            CountTask left = new CountTask(start,middle);
            CountTask right = new CountTask(middle+1,end);
            //執行子任務
            left.fork();
            right.fork();
            //獲取子任務結果
            int lResult = left.join();
            int rResult = right.join();
            sum = lResult + rResult;
        }
        return sum;
    }
}

通過這個例子,我們進一步了解ForkJoinTask,ForkJoinTask與一般任務的主要區別在於它需要實現compute方法,在這個方法里,首先需要判斷任務是否足夠小,如果足夠小就直接執行任務。如果不足夠小,就必須分割成兩個子任務,每個子任務在調用fork方法時,又會進入compute方法,看看當前子任務是否需要繼續分割成子任務,如果不需要繼續分割,則執行當前子任務並返回結果。使用join方法會等待子任務執行完並得到其結果

本文由博客一文多發平台 發布!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Go 多變量賦值時注意事項

說到多變量賦值時,先計算所有相關值,然後再從左到右依次賦值,但是這個規則不適用於python
我們來看一例:

package main

import "fmt"

func main() {
    data, i := [3]string{"喬幫主","慕容復","鳩摩智"}, 0
    i, data[i] = 2, "枯榮大師"
    fmt.Println(i, data)
}

輸出結果:

2 [枯榮大師 慕容復 鳩摩智]  

有的朋友會認為,結果不應該是這樣么?(但是python下輸出的結果卻是下面的)?

2 [喬幫主 慕容復 枯榮大師]

事實並如此,我們來看賦值順序這段的理解:

1     data, i := [3]string{"喬幫主","慕容復","鳩摩智"}, 0
2     i, data[i] = 2, "枯榮大師" //注意原則:先計算所有相關值,然後再從左到右依次賦值
3     // 這裏變量i 的順序其實是(i = 0,因為上一行的變量i是0) -> (然後 i = 2), (data[i] 此時取的值是data[0],而不是data[2],也就是data[0] = 枯榮大師)
4     fmt.Println(i, data) //所以這裏最終 輸出 i=2,[枯榮大師 慕容復 鳩摩智]

同樣的多變量賦值卻不適用於python.

data,i=["喬幫主", "慕容復", "鳩摩智"],0
i, data[i] = 2, "枯榮大師" # 注意這裏data[i] 已經是 data[2]了,即data[2]="枯榮大師"
print(i,data) # 輸出 2 ['喬幫主', '慕容復', '枯榮大師']

另外:我們要注意重新賦值與定義新同名變量的區別:再看一例:

package main

func main() {
    name := "喬幫主"
    println(&name)
    name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同層次的代碼塊中,且有新的變量被定義。
    println(&name, age)    // 通常函數多返回值 err 會被重複使用。
    {
        name, weight := "清風揚", 50 // 定義新同名變量: 不在同層次代碼塊。
        println(&name, weight)
    }
}

輸出:

0xc00002bf78
0xc00002bf78 30
0xc00002bf68 50

注意:因個人機器不同,大家返回的內存引用地址可能和我的不一樣,但是 這步是重點。重點在這裏:
同層級相同變量的賦值,內存地址並不會改變。不同層級相同變量的賦值,其實是定義了一個新同名變量,也就是大家看到的第三行內存地址變了。
接着我們再看有點意思的一段代碼(大家來找茬):

package main

func main() {
    name := "喬幫主"
    println(&name)
    name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同 層次的代碼塊中,且有新的變量被定義。
    println(&name, age)    // 通常函數多返回值 err 會被重複使用。

    name, weight := 100, 50 // 定義新同名變量: 不在同 層次代碼塊。
    println(&name, weight, age)

}

輸出:

cannot use 100 (type int) as type string in assignment

原因很明顯,因為上面:name := “喬幫主” 已經隱試滴申明了name 是字符串,等同於 var name string. 同層級再次賦值100為整形。這是不允許滴,

但是:重點來了,我們稍改下:

package main

func main() {
    name := "喬幫主"
    println(&name)
    name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同 層次的代碼塊中,且有新的變量被定義。
    println(&name, age)    // 通常函數多返回值 err 會被重複使用。
    {
        name, weight := 100, 50 // 定義新同名變量: 不在同層次代碼塊。
        println(&name, weight, age)
    }
}

區別就是層級發生了變化,因為{}裏面的name已經是新的變量了。
好啦,到此介紹結束了。博友們有關golang變量使用中遇到的各種奇怪的“坑”,請留下寶貴滴足跡,歡迎拍磚留言.

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

關於GC(中):Java垃圾回收相關基礎知識

Java內存模型

(圖源: )

區域名 英文名 訪問權限 作用 備註
程序計數器 Program Counter Register 線程隔離 標記待取的下一條執行的指令 執行Native方法時為空; JVM規範中唯一不會發生OutOfMemoryError的區域
虛擬機棧 VM Stack 線程隔離 每個Java方法執行時創建,用於存儲局部變量表,操作棧,動態鏈接,方法出口等信息 方法執行的內存模型
本地方法棧 Native Method Stack 線程隔離 Native方法執行時使用 JVM規範沒有強制規定,如Hotspot將VM和Native兩個方法棧合二為一
Java堆 Java Heap 線程共享 存放對象實例 更好的回收內存 vs 更快的分配內存
方法區 Method Area 線程共享 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據 JVM規範不強制要求做垃圾收集
運行時常量池 Runtime Constant Pool 線程共享 方法區的一部分
直接內存 Direct Memory 堆外內存,通過堆的DirectByteBuffer訪問 不是運行時數據區的一部分,但也可能OutOfMemoryError

對象的創建——new的時候發生了什麼

討論僅限於普通Java對象,不包括數組和Class對象。

  1. 常量池查找類的常量引用,如果沒有先做類加載
  2. 分配內存,視堆內存是否是規整(由垃圾回收器是否具有壓縮功能而定)而使用“指針碰撞”或“空閑列表”模式
  3. 內存空間初始化為零值,可能提前在線程創建時分配TLAB時做初始化
  4. 設置必要信息,如對象是哪個類的示例、元信息、GC分代年齡等
  5. 調用<init>方法

垃圾回收器總結

垃圾回收,針對的都是堆。

分代

  • 新生代:適合使用複製算法, 以下三個區一般佔比為8:1:1
    • Eden 新對象誕生區
    • From Survivor 上一次GC的倖存者(見“GC種類-minor GC”)
    • To Survivor 本次待存放倖存者的區域
  • 老年代:存活時間較久的,大小較大的對象,因此使用標記-整理或標記-清除算法比較合適
  • 永久代:存放類信息和元數據等不太可能回收的信息。Java8中被元空間(Metaspace)代替,不再使用堆,而是物理內存。

分代的原因

  • 不同代的對象生命周期不同,可以針對性地使用不同的垃圾回收算法
  • 不同代可以分開進行回收

回收算法

名稱 工作原理 優點 缺點
標記-清除 對可回收對對象做一輪標記,標記完成后統一回收被標記的對象 易於理解,內存利用率高 效率問題;內存碎片;分配大對象但無空間時提前GC
複製 內存均分兩塊,只使用其中一塊。回收時將這一塊存活對象全部複製到另一塊 效率高 可用空間減少; 空間不夠時需老年代分配擔保
標記-整理 對可回收對對象做一輪標記,標記完成后將存活對象統一左移,清理掉邊界外內存 內存利用率高 效率問題

標記-X算法適用於老年代,複製算法適用於新生代。

GC種類

  • Minor GC,只回收新生代,將Eden和From Survivor區的存活對象複製到To Survivor
  • Major GC,清理老年代。但因為伴隨着新生代的對象生命周期升級到老年代,一般也可認為伴隨着FullGC。
  • FullGC,整個堆的回收
  • Mixed GC,G1特有,可能會發生多次回收,可以參考

垃圾回收器小結

垃圾回收器名稱 特性 目前工作分代 回收算法 可否與Serial配合 可否與ParNew配合 可否與ParallelScavenge配合 可否與SerialOld配合 可否與ParallelOld配合 可否與CMS配合 可否與G1配合
Serial 單線程 新生代 複製 Y N Y N/A
ParNew 多線程 新生代 複製 N N Y N/A
ParallelScavenge 多線程, 更關注吞吐量可調節 新生代 複製 N N Y N/A
SerialOld 單線程 老年代 標記-整理 Y Y N N/A
ParallelOld 多線程 老年代 標記-整理 N N Y N/A
CMS 多線程,併發收集,低停頓。但無法處理浮動垃圾,標記-清除會產生內存碎片較多 老年代 標記-清除 Y Y N Y N/A
G1 并行併發收集,追求可預測但回收時間,整體內存模型有所變化 新生代/老年代 整體是標記-整理,局部(兩Region)複製 N N N N N N

在本系列的上一篇文章中,減少FullGC的方式是使用G1代替CMS,計劃在下一篇文章中對比CMS和G1的區別。

理解GC日誌

只舉比較簡單的例子,具體各項的格式視情況分析,不同回收器也會有差異。

2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure) 2019-11-22T10:28:32.178+0800: 60188.392: [ParNew: 1750382K->2520K(1922432K), 0.0312604 secs] 1945718K->198045K(4019584K), 0.0315892 secs] [Times: user=0.09 sys=0.01, real=0.03 secs]

開始時間-(方括號[)-發生區域(ParNew,命名和GC回收器有關)-回收前大小-回收后大小-(方括號])-GC前堆已使用容量-GC后堆已使用容量大小-回收時間-使用時間詳情(用戶態時間-內核時間-牆上時鐘時間)

注意這裏沒有包括“2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure)”這部分的分析。

可借鑒的編程模式

對象分配的併發控制

對象創建是很頻繁的,在線程共享的堆中會遇到併發的問題。兩種解決辦法:

  1. 同步鎖定:CAS+失敗重試,確保原子性
  2. 堆中預先給每個線程劃分一小塊內存區域——本地線程分配緩衝(TLAB),TLAB使用完並分配新的TLAB時才做同步鎖定。可看作1的優化。

CAS: Conmpare And Swap,用於實現多線程同步的原子指令。 將內存位置的內容與給定值進行比較,只有在相同的情況下,將該內存位置的內容修改為新的給定值。關於CAS可以參考:

對象訪問的定位方式

前提條件:通過上本地變量表的reference訪問中的對象及它在方法區的對象類型數據(類信息)
主流的兩種方式,這兩種方式各有優點,可以看出方式2是方式1的優化,但並不是全面超越方式1,無法完全取代。
這裏可以看到要權衡垃圾回收和訪問速度兩方面。

方式1: 直接指針訪問實例數據

reference直接存放對象實例地址,只需要一次訪問即可,執行效率較高。

方式2: 使用句柄池

reference中地址穩定,對象被移動時只需要改句柄池的地址。相對的,訪問實例需要兩次指針定位。

參考資料

  1. 周志明.著《深入理解JAVA虛擬機》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

附009.Kubernetes永久存儲之GlusterFS獨立部署

一 前期準備

1.1 基礎知識


Heketi提供了一個RESTful管理界面,可以用來管理GlusterFS卷的生命周期。Heketi會動態在集群內選擇bricks構建所需的volumes,從而確保數據的副本會分散到集群不同的故障域內。同時Heketi還支持任意數量的ClusterFS集群。

提示:本實驗基於glusterfs和Kubernetes分開部署,heketi管理glusterfs,Kubernetes使用heketi提供的API,從而實現glusterfs的永久存儲,,而非Kubernetes部署glusterfs。

1.2 架構示意






提示:本實驗Heketi僅管理單zone的glusterfs集群。

1.3 相關規劃

































主機 IP 磁盤 備註
servera 172.24.8.41 sdb glusterfs節點
serverb 172.24.8.42 sdb glusterfs節點
serverc 172.24.8.43 sdb glusterfs節點
heketi 172.24.8.44 Heketi主機










































servera serverb serverc
PV sdb1 sdb1 sdb1
VG vg0 vg0 vg0
LV datalv datalv datalv
bricks目錄 /bricks/data /bricks/data /bricks/data

1.4 其他準備


所有節點NTP配置;

所有節點添加相應主機名解析:

172.24.8.41 servera

172.24.8.42 serverb

172.24.8.43 serverc

172.24.8.44 heketi

注意:若非必要,建議關閉防火牆和SELinux。

二 規劃相應存儲卷

2.1 劃分LVM

  1 [root@servera ~]# fdisk /dev/sdb				#創建lvm的sdb1,過程略
  2 [root@servera ~]# pvcreate /dev/sdb1			#使用/dev/vdb1創建PV
  3 [root@servera ~]# vgcreate vg0 /dev/sdb1			#創建vg
  4 [root@servera ~]# lvcreate -L 15G -T vg0/thinpool		#創建支持thin的lv池
  5 [root@servera ~]# lvcreate -V 10G -T vg0/thinpool -n datalv	#創建相應brick的lv
  6 [root@servera ~]# vgdisplay					#驗證確認vg信息
  7 [root@servera ~]# pvdisplay					#驗證確認pv信息
  8 [root@servera ~]# lvdisplay					#驗證確認lv信息



提示:serverb、serverc類似操作,根據規劃需求創建完所有基於LVM的brick。

三 安裝glusterfs

3.1 安裝相應RPM源

  1 [root@servera ~]# yum -y install centos-release-gluster


提示:serverb、serverc、client類似操作,安裝相應glusterfs源;

安裝相應源之後,會在/etc/yum.repos.d/目錄多出文件CentOS-Storage-common.repo,內容如下:

  1 # CentOS-Storage.repo
  2 #
  3 # Please see http://wiki.centos.org/SpecialInterestGroup/Storage for more
  4 # information
  5 
  6 [centos-storage-debuginfo]
  7 name=CentOS-$releasever - Storage SIG - debuginfo
  8 baseurl=http://debuginfo.centos.org/$contentdir/$releasever/storage/$basearch/
  9 gpgcheck=1
 10 enabled=0
 11 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage


3.2 安裝glusterfs

  1 [root@servera ~]# yum -y install glusterfs-server


提示:serverb、serverc類似操作,安裝glusterfs服務端。

3.3 啟動glusterfs

  1 [root@servera ~]# systemctl start glusterd
  2 [root@servera ~]# systemctl enable glusterd



提示:serverb、serverc類似操作,所有節點啟動glusterfs服務端;

安裝完glusterfs之後建議exit退出終端重新登錄,從而可以補全glusterfs相關命令。

3.4 添加信任池

  1 [root@servera ~]# gluster peer probe serverb
  2 peer probe: success.
  3 [root@servera ~]# gluster peer probe serverc
  4 peer probe: success.
  5 [root@servera ~]# gluster peer status		#查看信任池狀態
  6 [root@servera ~]# gluster pool list			#查看信任池列表




提示:加信任池的操作,只需要在servera、serverb、serverc所有集群節點主機中的任意一台上面執行添加其他三個節點的操作即可。

提示:若未關閉防火牆,在添加信任池之前必須放通防火牆相應規則,操作如下:

  1 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=glusterfs
  2 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=nfs
  3 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=rpc­bind
  4 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=mountd
  5 [root@servera ~]# firewall­cmd ­­permanent ­­add­port=5666/tcp
  6 [root@servera ~]# firewall­cmd ­­reload


四 部署Heketi

4.1 安裝heketi服務

  1 [root@heketi ~]# yum -y install centos-release-gluster
  2 [root@heketi ~]# yum -y install heketi heketi-client


4.2 配置heketi

  1 [root@heketi ~]# vi /etc/heketi/heketi.json
  2 {
  3   "_port_comment": "Heketi Server Port Number",
  4   "port": "8080",					#默認端口
  5 
  6   "_use_auth": "Enable JWT authorization. Please enable for deployment",
  7   "use_auth": true,					#基於安全考慮開啟認證
  8 
  9   "_jwt": "Private keys for access",
 10   "jwt": {
 11     "_admin": "Admin has access to all APIs",
 12     "admin": {
 13       "key": "admin123"					#管理員密碼
 14     },
 15     "_user": "User only has access to /volumes endpoint",
 16     "user": {
 17       "key": "xianghy"					#普通用戶
 18     }
 19   },
 20 
 21   "_glusterfs_comment": "GlusterFS Configuration",
 22   "glusterfs": {
 23     "_executor_comment": [
 24       "Execute plugin. Possible choices: mock, ssh",
 25       "mock: This setting is used for testing and development.",	#用於測試
 26       "      It will not send commands to any node.",
 27       "ssh:  This setting will notify Heketi to ssh to the nodes.",	#ssh方式
 28       "      It will need the values in sshexec to be configured.",
 29       "kubernetes: Communicate with GlusterFS containers over",		#在GlusterFS由kubernetes創建時採用
 30       "            Kubernetes exec api."
 31     ],
 32     "executor": "ssh",
 33 
 34     "_sshexec_comment": "SSH username and private key file information",
 35     "sshexec": {
 36       "keyfile": "/etc/heketi/heketi_key",
 37       "user": "root",
 38       "port": "22",
 39       "fstab": "/etc/fstab"
 40     },
 41 ……
 42 ……
 43     "loglevel" : "warning"
 44   }
 45 }


4.3 配置免秘鑰

  1 [root@heketi ~]# ssh-keygen -t rsa -q -f /etc/heketi/heketi_key -N ""
  2 [root@heketi ~]# chown heketi:heketi /etc/heketi/heketi_key
  3 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@servera
  4 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverb
  5 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverc


4.4 啟動heketi

  1 [root@heketi ~]# systemctl enable heketi.service
  2 [root@heketi ~]# systemctl start heketi.service
  3 [root@heketi ~]# systemctl status heketi.service
  4 [root@heketi ~]# curl http://localhost:8080/hello		#測試訪問
  5 Hello from Heketi


4.5 配置Heketi拓撲


       拓撲信息用於讓Heketi確認可以使用的存儲節點、磁盤和集群,必須自行確定節點的故障域。故障域是賦予一組節點的整數值,這組節點共享相同的交換機、電源或其他任何會導致它們同時失效的組件。必須確認哪些節點構成一個集群,Heketi使用這些信息來確保跨故障域中創建副本,從而提供數據冗餘能力,Heketi支持多個Gluster存儲集群。

配置Heketi拓撲注意以下幾點:

  • 可以通過topology.json文件定義組建的GlusterFS集群;
  • topology指定了層級關係:clusters –> nodes –> node/devices –> hostnames/zone;
  • node/hostnames字段的manage建議填寫主機ip,指管理通道,注意當heketi服務器不能通過hostname訪問GlusterFS節點時不能填寫hostname;
  • node/hostnames字段的storage建議填寫主機ip,指存儲數據通道,與manage可以不一樣,生產環境管理網絡和存儲網絡建議分離;
  • node/zone字段指定了node所處的故障域,heketi通過跨故障域創建副本,提高數據高可用性質,如可以通過rack的不同區分zone值,創建跨機架的故障域;
  • devices字段指定GlusterFS各節點的盤符(可以是多塊盤),必須是未創建文件系統的裸設備。

  1 [root@heketi ~]# vi /etc/heketi/topology.json
  2 {
  3     "clusters": [
  4         {
  5             "nodes": [
  6                 {
  7                     "node": {
  8                         "hostnames": {
  9                             "manage": [
 10                                 "172.24.8.41"
 11                             ],
 12                             "storage": [
 13                                 "172.24.8.41"
 14                             ]
 15                         },
 16                         "zone": 1
 17                     },
 18                     "devices": [
 19                         "/dev/mapper/vg0-datalv"
 20                     ]
 21                 },
 22                 {
 23                     "node": {
 24                         "hostnames": {
 25                             "manage": [
 26                                 "172.24.8.42"
 27                             ],
 28                             "storage": [
 29                                 "172.24.8.42"
 30                             ]
 31                         },
 32                         "zone": 1
 33                     },
 34                     "devices": [
 35                         "/dev/mapper/vg0-datalv"
 36                     ]
 37                 },
 38                 {
 39                     "node": {
 40                         "hostnames": {
 41                             "manage": [
 42                                 "172.24.8.43"
 43                             ],
 44                             "storage": [
 45                                 "172.24.8.43"
 46                             ]
 47                         },
 48                         "zone": 1
 49                     },
 50                     "devices": [
 51                         "/dev/mapper/vg0-datalv"
 52                     ]
 53                 }
 54             ]
 55         }
 56     ]
 57 }
 58 
 59 [root@heketi ~]# echo "export HEKETI_CLI_SERVER=http://heketi:8080" >> /etc/profile.d/heketi.sh
 60 [root@heketi ~]# echo "alias heketi-cli='heketi-cli --user admin --secret admin123'" >> .bashrc
 61 [root@heketi ~]# source /etc/profile.d/heketi.sh
 62 [root@heketi ~]# source .bashrc
 63 [root@heketi ~]# echo $HEKETI_CLI_SERVER
 64 http://heketi:8080
 65 [root@heketi ~]# heketi-cli --server $HEKETI_CLI_SERVER --user admin --secret admin123 topology load --json=/etc/heketi/topology.json




4.6 集群管理

  1 [root@heketi ~]# heketi-cli cluster list					#集群列表
  2 [root@heketi ~]# heketi-cli cluster info aa83b0045fafa362bfc7a8bfee0c24ad	#集群詳細信息
  3 Cluster id: aa83b0045fafa362bfc7a8bfee0c24ad
  4 Nodes:
  5 189ee41572ebf0bf1e297de2302cfb39
  6 46429de5666fc4c6cc570da4b100465d
  7 be0209387384299db34aaf8377c3964c
  8 Volumes:
  9 
 10 Block: true
 11 
 12 File: true
 13 [root@heketi ~]# heketi-cli topology info aa83b0045fafa362bfc7a8bfee0c24ad	#查看拓撲信息




  1 [root@heketi ~]# heketi-cli node list						#卷信息
  2 Id:189ee41572ebf0bf1e297de2302cfb39     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
  3 Id:46429de5666fc4c6cc570da4b100465d     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
  4 Id:be0209387384299db34aaf8377c3964c     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
  5 [root@heketi ~]# heketi-cli node info 189ee41572ebf0bf1e297de2302cfb39		#節點信息
  6 [root@heketi ~]# heketi-cli volume create --size=2 --replica=2			#默認為3副本的replica模式



  1 [root@heketi ~]# heketi-cli volume list						#卷信息
  2 [root@heketi ~]# heketi-cli volume info 7da55685ebeeaaca60708cd797a5e391
  3 [root@servera ~]# gluster volume info						#通過glusterfs節點查看


4.7 測試驗證

  1 [root@heketi ~]# yum -y install centos-release-gluster
  2 [root@heketi ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse
  3 [root@heketi ~]# mount -t glusterfs 172.24.8.41:vol_7da55685ebeeaaca60708cd797a5e391 /mnt



  1 [root@heketi ~]# umount /mnt
  2 [root@heketi ~]# heketi-cli volume delete 7da55685ebeeaaca60708cd797a5e391	#驗證完畢刪除



參考:https://www.jianshu.com/p/1069ddaaea78

https://www.cnblogs.com/panwenbin-logs/p/10231859.html

五 Kubernetes動態掛載glusterfs

5.1 StorageClass動態存儲


kubernetes共享存儲provider模式:

靜態模式(Static):集群管理員手工創建PV,在定義PV時設置後端存儲的特性;

動態模式(Dynamic):集群管理員不需要手工創建PV,而是通過StorageClass的設置對後端存儲進行描述,標記為某種”類型(Class)”;此時要求PVC對存儲的類型進行說明,系統將自動完成PV的創建及與PVC的綁定;PVC可以聲明Class為””,說明PVC禁止使用動態模式。

基於StorageClass的動態存儲供應整體過程如下圖所示:


  1. 集群管理員預先創建存儲類(StorageClass);
  2. 用戶創建使用存儲類的持久化存儲聲明(PVC:PersistentVolumeClaim);
  3. 存儲持久化聲明通知系統,它需要一個持久化存儲(PV: PersistentVolume);
  4. 系統讀取存儲類的信息;
  5. 系統基於存儲類的信息,在後台自動創建PVC需要的PV;
  6. 用戶創建一個使用PVC的Pod;
  7. Pod中的應用通過PVC進行數據的持久化;
  8. 而PVC使用PV進行數據的最終持久化處理。


提示:關於Kubernetes的部署參考《附003.Kubeadm部署Kubernetes》。

5.2 定義StorageClass


關鍵字說明:

  • provisioner:表示存儲分配器,需要根據後端存儲的不同而變更;
  • reclaimPolicy: 默認即”Delete”,刪除pvc后,相應的pv及後端的volume,brick(lvm)等一起刪除;設置為”Retain”時則保留數據,若需刪除則需要手工處理;
  • resturl:heketi API服務提供的url;
  • restauthenabled:可選參數,默認值為”false”,heketi服務開啟認證時必須設置為”true”;
  • restuser:可選參數,開啟認證時設置相應用戶名;
  • secretNamespace:可選參數,開啟認證時可以設置為使用持久化存儲的namespace;
  • secretName:可選參數,開啟認證時,需要將heketi服務的認證密碼保存在secret資源中;
  • clusterid:可選參數,指定集群id,也可以是1個clusterid列表,格式為”id1,id2”;
  • volumetype:可選參數,設置卷類型及其參數,如果未分配卷類型,則有分配器決定卷類型;如”volumetype: replicate:3”表示3副本的replicate卷,”volumetype: disperse:4:2”表示disperse卷,其中‘4’是數據,’2’是冗餘校驗,”volumetype: none”表示distribute卷


提示:關於glusterfs各種不同類型的卷見《004.RHGS-創建volume》。

  1 [root@k8smaster01 ~]# kubectl create ns heketi		#創建命名空間
  2 [root@k8smaster01 ~]# echo -n "admin123" | base64		#將密碼轉換為64位編碼
  3 YWRtaW4xMjM=
  4 [root@k8smaster01 ~]# mkdir -p heketi
  5 [root@k8smaster01 ~]# cd heketi/
  6 [root@k8smaster01 ~]# vi heketi-secret.yaml			#創建用於保存密碼的secret
  7 apiVersion: v1
  8 kind: Secret
  9 metadata:
 10   name: heketi-secret
 11   namespace: heketi
 12 data:
 13   # base64 encoded password. E.g.: echo -n "mypassword" | base64
 14   key: YWRtaW4xMjM=
 15 type: kubernetes.io/glusterfs
 16 [root@k8smaster01 heketi]# kubectl create -f heketi-secret.yaml	#創建heketi
 17 [root@k8smaster01 heketi]# kubectl get secrets -n heketi
 18 NAME                  TYPE                                  DATA   AGE
 19 default-token-5sn5d   kubernetes.io/service-account-token   3      43s
 20 heketi-secret         kubernetes.io/glusterfs               1      5s
 21 [root@kubenode1 heketi]# vim gluster-heketi-storageclass.yaml	#正式創建StorageClass
 22 apiVersion: storage.k8s.io/v1
 23 kind: StorageClass
 24 metadata:
 25   name: gluster-heketi-storageclass
 26 parameters:
 27   resturl: "http://172.24.8.44:8080"
 28   clusterid: "aa83b0045fafa362bfc7a8bfee0c24ad"
 29   restauthenabled: "true"					#若heketi開啟認證此處也必須開啟auth認證
 30   restuser: "admin"
 31   secretName: "heketi-secret"				#name/namespace與secret資源中定義一致
 32   secretNamespace: "heketi"
 33   volumetype: "replicate:3"
 34 provisioner: kubernetes.io/glusterfs
 35 reclaimPolicy: Delete
 36 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-storageclass.yaml



注意:storageclass資源創建后不可變更,如修改只能刪除后重建。

  1 [root@k8smaster01 heketi]# kubectl get storageclasses		#查看確認
  2 NAME                          PROVISIONER               AGE
  3 gluster-heketi-storageclass   kubernetes.io/glusterfs   85s
  4 [root@k8smaster01 heketi]# kubectl describe storageclasses gluster-heketi-storageclass




5.3 定義PVC

  1 [root@k8smaster01 heketi]# cat gluster-heketi-pvc.yaml
  2 apiVersion: v1
  3 metadata:
  4   name: gluster-heketi-pvc
  5   annotations:
  6     volume.beta.kubernetes.io/storage-class: gluster-heketi-storageclass
  7 spec:
  8   accessModes:
  9   - ReadWriteOnce
 10   resources:
 11     requests:
 12       storage: 1Gi



注意:accessModes可有如下簡寫:

  • ReadWriteOnce:簡寫RWO,讀寫權限,且只能被單個node掛載;
  • ReadOnlyMany:簡寫ROX,只讀權限,允許被多個node掛載;
  • ReadWriteMany:簡寫RWX,讀寫權限,允許被多個node掛載。

  1 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pvc.yaml
  2 [root@k8smaster01 heketi]# kubectl get pvc
  3 [root@k8smaster01 heketi]# kubectl describe pvc gluster-heketi-pvc
  4 [root@k8smaster01 heketi]# kubectl get pv
  5 [root@k8smaster01 heketi]# kubectl describe pv pvc-5f7420ef-082d-11ea-badf-000c29fa7a79




  1 [root@k8smaster01 heketi]# kubectl describe endpoints glusterfs-dynamic-5f7420ef-082d-11ea-badf-000c29fa7a79




提示:由上可知:PVC狀態為Bound,Capacity為1G。查看PV詳細信息,除容量,引用storageclass信息,狀態,回收策略等外,同時可知GlusterFS的Endpoint與path。EndpointsName為固定格式:glusterfs-dynamic-PV_NAME,且endpoints資源中指定了掛載存儲時的具體地址。

5.4 確認查看


通過5.3所創建的信息:

  • volume與brick已經創建;
  • 主掛載點(通信)在172.24.8.41節點,其餘兩個節點備選;
  • 三副本的情況下,所有節點都會創建brick。

  1 [root@heketi ~]# heketi-cli topology info			#heketi主機查看
  2 [root@serverb ~]# lsblk						#glusterfs節點查看
  3 [root@serverb ~]# df -hT					#glusterfs節點查看
  4 [root@servera ~]# gluster volume list				#glusterfs節點查看
  5 [root@servera ~]# gluster volume info vol_e4c948687239df9833748d081ddb6fd5




5.5 Pod掛載測試

  1 [root@xxx ~]# yum -y install centos-release-gluster
  2 [root@xxx ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse



提示:所有需要使用glusterfs volume的Kubernetes節點都必須安裝glusterfs-fuse以便於正常掛載,同時版本需要和glusterfs節點一致。

  1 [root@k8smaster01 heketi]# vi gluster-heketi-pod.yaml
  2 kind: Pod
  3 apiVersion: v1
  4 metadata:
  5   name: gluster-heketi-pod
  6 spec:
  7   containers:
  8   - name: gluster-heketi-container
  9     image: busybox
 10     command:
 11     - sleep
 12     - "3600"
 13     volumeMounts:
 14     - name: gluster-heketi-volume			#必須和volumes中name一致
 15       mountPath: "/pv-data"
 16       readOnly: false
 17   volumes:
 18   - name: gluster-heketi-volume
 19     persistentVolumeClaim:
 20       claimName: gluster-heketi-pvc			#必須和5.3創建的PVC中的name一致
 21 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pod.yaml -n heketi		#創建Pod


5.6 確認驗證

  1 [root@k8smaster01 heketi]# kubectl get pod -n heketi | grep gluster
  2 gluster-heketi-pod          1/1     Running   0          2m43s
  3 [root@k8smaster01 heketi]# kubectl exec -it gluster-heketi-pod /bin/sh		#進入Pod寫入測試文件
  4 / # cd /pv-data/
  5 /pv-data # echo "This is a file!" >> a.txt
  6 /pv-data # echo "This is b file!" >> b.txt
  7 /pv-data # ls
  8 a.txt  b.txt
  9 [root@servera ~]# df -hT					#在glusterfs節點查看Kubernetes節點的測試文件
 10 [root@servera ~]# cd /var/lib/heketi/mounts/vg_47c90d90e03de79696f90bd94cfccdde/brick_721243c3e0cf8a2372f05d5085a4338c/brick/
 11 [root@servera brick]# ls
 12 [root@servera brick]# cat a.txt
 13 [root@servera brick]# cat b.txt




5.7 刪除資源

  1 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pod.yaml
  2 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pvc.yaml
  3 [root@k8smaster01 heketi]# kubectl get pvc
  4 [root@k8smaster01 heketi]# kubectl get pv
  5 [root@servera ~]# gluster volume list
  6 No volumes present in cluster



本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

類加載器 – ClassLoader詳解

獲得ClassLoader的途徑

  • 獲得當前類的ClassLoader
    • clazz.getClassLoader()
  • 獲得當前線程上下文的ClassLoader
    • Thread.currentThread().getContextClassLoader();
  • 獲得系統的ClassLoader
    • ClassLoader.getSystemClassLoader()
  • 獲得調用者的ClassLoader
    • DriverManager.getCallerClassLoader

ClassLoader源碼解析

概述

類加載器是用於加載類的對象,ClassLoader是一個抽象類。如果我們給定了一個類的二進制名稱,類加載器應嘗試去定位或生成構成定義類的數據。一種典型的策略是將給定的二進制名稱轉換為文件名,然後去文件系統中讀取這個文件名所對應的class文件。

每個Class對象都會包含一個定義它的ClassLoader的一個引用。

數組類的Class對象,不是由類加載器去創建的,而是在Java運行期JVM根據需要自動創建的。對於數組類的類加載器來說,是通過Class.getClassLoader()返回的,與數組當中元素類型的類加載器是一樣的;如果數組當中的元素類型是一個原生類型,數組類是沒有類加載器的【代碼一】。

應用實現了ClassLoader的子類是為了擴展JVM動態加載類的方式。

類加載器典型情況下時可以被安全管理器所使用去標識安全域問題。

ClassLoader類使用了委託模型來尋找類和資源,ClassLoader的每一個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類或者資源的時候,ClassLoader實例在自身嘗試尋找類或者資源之前會委託它的父類加載器去完成。虛擬機內建的類加載器,稱之為啟動類加載器,是沒有父加載器的,但是可以作為一個類加載器的父類加載器【雙親委託機制】。

支持併發類加載的類加載器叫做并行類加載器,要求在初始化期間通過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類默認被註冊為可以并行,但是如果它的子類也是并行加載的話需要單獨去註冊子類。

在委託模型不是嚴格的層次化的環境下,類加載器需要并行,否則類加載會導致死鎖,因為加載器的鎖在類加載過程中是一直被持有的。

通常情況下,Java虛擬機以平台相關的形式從本地的文件系統中加載類,比如在UNIX系統,虛擬機從CLASSPATH環境所定義的目錄加載類。
然而,有些類並不是來自於文件;它們是從其它來源得到的,比如網絡,或者是由應用本身構建【動態代理】。定義類(defineClass )方法會將字節數組轉換為Class的實例,這個新定義類的實例可以由Class.newInstance創建。

由類加載器創建的對象的方法和構造方法可能引用其它的類,為了確定被引用的類,Java虛擬機會調用最初創建類的類加載器的loadClass方法。

二進制名稱:以字符串參數的形式向CalssLoader提供的任意一個類名,必須是一個二進制的名稱,包含以下四種情況

  • “java.lang.String” 正常類
  • “javax.swing.JSpinner$DefaultEditor” 內部類
  • “java.security.KeyStore\(Builder\)FileBuilder$1″ KeyStore的內部類Builder的內部類FileBuilder的第一個匿名內部類
  • “java.net.URLClassLoader$3$1” URLClassLoader類的第三個匿名內部類的第一個匿名內部類

代碼一:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 運行結果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 運行結果:null
    }
}

loadClass方法

loadClass的源碼如下, loadClass方法加載擁有指定的二進制名稱的Class,默認按照如下順序尋找類:

  • 調用findLoadedClass(String)檢查這個類是否被加載
  • 調用父類加載器的loadClass方法,如果父類加載器為null,就會調用啟動類加載器
  • 調用findClass(String)方法尋找

使用上述步驟如果類被找到且resolve為true,就會去調用resolveClass(Class)方法

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name);
      if (c == null) {
          long t0 = System.nanoTime();
          try {
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  c = findBootstrapClassOrNull(name);
              }
          } catch (ClassNotFoundException e) {
              // ClassNotFoundException thrown if class not found
              // from the non-null parent class loader
          }

          if (c == null) {
              // If still not found, then invoke findClass in order
              // to find the class.
              long t1 = System.nanoTime();
              c = findClass(name);

              // this is the defining class loader; record the stats
              sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
              sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
              sun.misc.PerfCounter.getFindClasses().increment();
          }
      }
      if (resolve) {
          resolveClass(c);
      }
      return c;
  }
}

findClass方法

findClass的源碼如下,findClass尋找擁有指定二進制名稱的類,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委託機制,該方法會在檢查完父類加載器之後被loadClass方法調用,默認返回ClassNotFoundException異常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源碼如下,defineClass方法將一個字節數組轉換為Class的實例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定義類加載器

/**
 * 繼承了ClassLoader,這是一個自定義的類加載器
 * @author 夜的那種黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上為測試代碼---------------------------------

    /**
     * 類加載器名稱,標識作用
     */
    private String classLoaderName;

    /**
     * 從磁盤讀物字節碼文件的擴展名
     */
    private String fileExtension = ".class";

    /**
     * 創建一個類加載器對象,將系統類加載器當做該類加載器的父加載器
     * @param classLoaderName 類加載器名稱
     */
    private ClassLoaderTest(String classLoaderName) {
        // 將系統類加載器當做該類加載器的父加載器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 創建一個類加載器對象,显示指定該類加載器的父加載器
     * 前提是需要有一個類加載器作為父加載器
     * @param parent 父加載器
     * @param classLoaderName 類加載器名稱
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定該類加載器的父加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 尋找擁有指定二進制名稱的類,重寫ClassLoader類的同名方法,需要自定義加載器遵循雙親委託機制
     * 該方法會在檢查完父類加載器之後被loadClass方法調用
     * 默認返回ClassNotFoundException異常
     * @param className 類名
     * @return Class的實例
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通過defineClass方法將字節數組轉換為Class
         * defineClass:將一個字節數組轉換為Class的實例,在使用這個Class之前必須要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根據類名找到對應文件,返回class文件的二進制信息
     * @param className 類名
     * @return class文件的二進制信息
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定義類加載器的代碼,我們執行這段代碼

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看見,這段代碼中進行類加載的類加載器還是系統類加載器(AppClassLoader)。這是因為jvm的雙親委託機製造成的,private ClassLoaderTest(String classLoaderName)將系統類加載器當做我們自定義類加載器的父加載器,jvm的雙親委託機制使自定義類加載器委託系統類加載器完成加載。

改造以下代碼,添加一個path屬性用來指定類加載位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上為測試代碼---------------------------------

    /**
     * 從指定路徑加載
     */
    private String path;

    ......
    
    /**
     * io操作,根據類名找到對應文件,返回class文件的二進制信息
     * @param className 類名
     * @return class文件的二進制信息
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

運行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下測試代碼,並刪除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

運行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的兩塊代碼,第一塊代碼中加載類的是系統類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是因為ClassLoaderTest會委託他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader可以加載到,而第二塊代碼的path在桌面目錄下,所以AppClassLoader無法加載到,然後ClassLoaderTest自身嘗試加載並成功加載到。如果第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那麼依然是AppClassLoader加載。

再來測試一塊代碼

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

結果顯而易見,類由系統類加載器加載,並且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

在改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

運行結果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是顯而易見,但是clazz和clazz2是不同的,這是因為類加載器的命名空間的原因。

我們可以通過設置父類加載器來讓loader和loader2處於同一命名空間

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

運行結果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

擴展:命名空間

  • 每個類加載器都有自己的命名空間,命名空間由該加載器及所有的父加載器所加載的類組成
  • 在同一命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類
  • 在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益