java內存存儲數據
Ⅰ java數組在內存中是如何存放的
數組是一種引用數據類型,數組引用變數只是一個引用,數組元素和數組變數在內存里是分開存放的.
實際的數組元素被存儲在堆(heap)內存中;數組引用變數是一個引用類型的變數,被存儲在棧(stack)內存中.
Ⅱ 如何在java里長期存儲數據 不要資料庫的那種
長期存儲數據,即把數據(如內存中的)保存到可永久保存的存儲設備中(如硬碟、U盤),也就是人們常說的持久化。
常用持久化的方案有資料庫、XML文件和文件存儲。
資料庫是按照數據結構來存儲和管理數據的倉庫,後文不再做詳細介紹。
XML是可擴展標記語言,最早是為了簡化Internet的文檔數據傳輸,它提供統一的語法格式來描述數據的結構,通常XML文件用於一些少量且無特殊類型要求的文本存儲。示例代碼使用W3C標準的介面生成XML:
importjava.io.FileOutputStream;
importjava.io.PrintWriter;
importjavax.xml.parsers.DocumentBuilderFactory;
importjavax.xml.transform.OutputKeys;
importjavax.xml.transform.Transformer;
importjavax.xml.transform.TransformerFactory;
importjavax.xml.transform.dom.DOMSource;
importjavax.xml.transform.stream.StreamResult;
importorg.w3c.dom.Document;
importorg.w3c.dom.Element;
publicclass${
publicstaticvoidmain(String[]args)throwsException{
Documentdocument=DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
//創建根節點為students的XML文件
Elementstudents=document.createElement("students");
document.appendChild(students);
//在根節點下創建一個子節點學生
Elementstudent=document.createElement("student");
students.appendChild(student);
//創建節點學生姓名,值為張三
Elementname=document.createElement("name");
name.appendChild(document.createTextNode("張三"));
student.appendChild(name);
//創建節點學生年齡,值為18
Elementage=document.createElement("age");
age.appendChild(document.createTextNode("18"));
student.appendChild(age);
//創建節點學生編號,值為150101
Elementnumber=document.createElement("number");
number.appendChild(document.createTextNode("150101"));
student.appendChild(number);
//在根節點下創建第二個子節點學生
student=document.createElement("student");
students.appendChild(student);
//創建節點學生姓名,值為李四
name=document.createElement("name");
name.appendChild(document.createTextNode("李四"));
student.appendChild(name);
//創建節點學生年齡,值為20
age=document.createElement("age");
age.appendChild(document.createTextNode("20"));
student.appendChild(age);
//創建節點學生編號,值為150102
number=document.createElement("number");
number.appendChild(document.createTextNode("150102"));
student.appendChild(number);
//將XML文件保存到硬碟
Transformertransformer=TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING,"utf-8");
transformer.setOutputProperty(OutputKeys.INDENT,"yes");
PrintWriterwriter=newPrintWriter(newFileOutputStream("/home/test.xml"));
transformer.transform(newDOMSource(document),newStreamResult(writer));
}
}
無論是資料庫還是XML文件,它們都使用了能讓數據快速方便進出的標准規范。其它文件如propeties、json,都可以使用類似XML的方式來打包數據,然後通過Java豐富的io流介面保存到磁碟中。
Ⅲ java 怎麼把數據存到內存中
這里你採納與否沒關系,給你說說編程與內存的關系。
你定義的任何變數,常量,類,方法等等,其實都在內存中,沒有所謂的把數據存內存中,這概念,你可以想一下電腦重啟或關機後,內存中的所有數據,都會丟失,除非你保存到磁碟中去。
在內存中的數據有兩塊,第一、緩沖,一般寫數據到磁碟的時候開辟出來的內存空間;第二、緩存,一般是從磁碟讀數據到內存中開辟出來的內存空間。會這么使用,原因很簡單,磁碟讀寫數據速度與內存不一致(磁碟的存取效率遠遠小於內存的存取效率),為了提高數據的存取效率,才會這么乾的。
一般而言,java中的所謂數據,大部分都是類,從自動引用計數的概念來分析,你想把對象長久的放在內存中,不會被垃圾回收機制釋放,注意制葯有一個對象在使用/引用你的數據,這條數據就會存在內存中。所以,想servlet中的全局配置參數,隨時可以取到還是唯一一份,你可以參考一下。
另外內存使用分堆與棧,堆在面向對象編程中存儲對象的,棧是方法或函數執行的時候臨時開辟的存儲空間,方法或函數執行完畢就會釋放。
希望我的回復能幫助到你,採納與否沒關系。有更好的答案,我就隱藏我的回復。
Ⅳ java中數據在內存中是如何存儲的
堆:首先堆是動態分配的,JVM並不會自動釋放這部分內存。只用等待系統的gc來進行內存回收。
棧:是在類載入中有系統靜態分配的,而且分配時按照內存的高低地址分配。這部分內存系統會自動進行釋放。
Ⅳ 深入Java對象及元素的存儲區域
在JAVA平台上開發應用程序的時候,有一個很大的特點就是其是在應用程序運行的時候才建立對象。換句話說,在程序運行的時候,才會最終確定對象的歸屬,即對象應該存儲在什麼地方。由於存儲在不同的區域,其在性能上會有所不同。為此作為Java程序開發人員需要了解各個存儲區域的特點以及對性能的影響。然後再根據需要來調整應用程序的區域分配。總的來說,在操作系統中有五個地方可以用來保存應用程序運行中的數據。這類區域的特點以及對性能的影響分析如下。
存儲區域一:寄存器
雖然同在內存中,但是不同的區域由於用途不同,其性能也有所不同。如就拿Java應用程序來說,寄存器由於其處於處理器的內部,為此這個區域存取數據最快。跟內存中的其他存儲區域有著天壤之別。那麼我們把所有對象都放到這個區域內,不就可以提高Java應用程序的性能了嗎?理論上是如此,但是在現實中是行不通的。因為這個寄存器的數量是非常有限的。在內存中的寄存器區域是由編譯器根據需要來分配的。我們程序開發人員不能夠通過代碼來控制這個寄存器的分配。所以說,這第一個存儲區域寄存器,我們只能夠看看,而不能夠對其產生任何的影響。
存儲區域二:堆棧
對象的創建有兩種方式,一是在應用程序開發的過程中就創建對象;二是在程序運行的過程中要用到對象的時候再來創建對象。前者比後者性能要高,而後者比前者要靈活。這主要是因為前者創建對象的時候,就是這個堆棧中創建的。雖然其創建的對象沒有保存在寄存器中,但是通過這個對象的推棧指針可以直接從處理器哪裡獲得相關的支持。如堆棧指針往上移動的時候,則釋放原有對象佔用的內存;如堆棧指針向下移動時,則為對象分配新的內存。所以,如果把對象存放在這個堆棧中,雖然性能沒有像存放在寄存器中那麼理想,但是仍然比存儲在其他地方要好的多。
由於Java程序是在程序運行過程中才根據需要來創建對象。為此對象就不能夠保存在這個堆棧中。不過Java應用程序也不能夠白白的浪費這個寶貴的空間。為此雖然Java對象本身沒有保存在這個堆棧中(不是不保存而是這里沒有他的容身之地),但是還是應該把一些可以放的內容放到這個堆棧中,以提高應用程序的性能。如可以把一些對象引用存放在這個堆棧中。
另外對於一些基本的數據類型對象,Java程序也往往把他們放置在堆棧中,以提高數據處理的性能。如一些整數型、字元型的數據對象,這些對象有些共同的特點,如對象比較小、是Java程序提供的標准對象等等。對於這些對象由於每個應用程序基本上都需要用到,而且我們程序開發人員只能夠引用這些對象,而不能夠對其進行更改弊衡胡。為此Java程序在處理的時候,往往一開始就創建了對象(即直接在堆棧中創建對象並保存),而不像其他對象一樣,在需要的時候才創建。只所以在堆棧中創建這些對象,還有一個重要的原因。因為如果在堆棧中創建對象的話,Java編輯器必須知道存儲在堆棧內所有數據的確切大小和生命周期。為了得到這些信息,必須產生相關的代碼來獲得這些信息,以便其操作堆棧指針。普通的對象大小、生命周期等等難以預先獲得,為此在堆棧中創建普通的對象,對於Java應用程序來說並不是很合適。相反,這些Java編譯器預定義的對象大小並不會隨著機器硬體架構的變化和用戶需求的變化而變化;而且這些對象往往從始之終都會存在的,所以也不存在生命周期的問題。所以把這些對象放置在堆棧中是合理的,也是可實現的。如此處理,不僅不會影響到對象的靈活性,而且還可以提供比較好的性能。
存儲區域三:堆
堆雖然跟堆棧一樣,都是隨機訪問存儲器中的區域,但是兩者有很大的不同。因為在堆中,沒有堆棧指針,為此也就無法直接從處理器那邊獲得支持。為此其性能跟堆棧比起來,就有一定的差距。通常情況下,除上面所說的一些預定義對象之外,其他的對象都是保存在這個堆中的。或者說,利用new關鍵字創建的對象都是保存在堆中的。保存在堆中其好處也是顯而易見的。如Java編譯器不需要知道從堆里需要分配多少存儲區域,也不必知道存儲的數據在堆里會存活多長時間。所以在攔磨堆里分配存儲有很大的靈活性。當需要對象時,我們可以使用New關鍵字建立一個對象。然後系統會自動給這個對象在堆中分配一個區域讓其作為歸宿。不過其最大的不足之處,就是在堆中創建租攔對象與分配存儲區域,要比在堆棧中慢許多。魚與熊掌不能兼得呀。
存儲區域四:靜態存儲區域與常量存儲區域
在Java對象中有一些特殊的元素。如有些元素是比較特別的(如利用關鍵字Static定義的變數)。這些變數對於其他對象來說,可能就是靜態的。為了更好的管理這些變數,Java在內存中專門劃分了一個靜態存儲區域來管理這些元素。這里的靜態存儲區域就是指在固定的位置存放應用程序運行時一直存在的數據。這里需要明確的一點就是,Java對象是不保存在這個地方的,而只是把對象中的一些特殊元素放置這里。由於位置固定,所以下次調用的時候就省去了查找的麻煩。為此其對於提供應用程序的性能是有利的。作為我們程序開發人員來說,在書寫代碼的時候,就需要靈活應用Static這個關鍵字。筆者的意見是,能用則用;不能用的時候也要想著法兒用。特別是有些元素用不用Static關鍵字時對於程序功能沒有影響,此時我們要理直氣壯的在元素前面加上Static關鍵字。
在Java對象中還有一類特殊的元素,我們叫做常量。由於常量的值是穩定不變的,如圓周率。為此把他們放在代碼的內部是可行的。不過有些時候,在進行一些嵌入式系統開發的時候,我們往往不這么做。而是會把常量元素跟代碼分開來保存。如我們會根據情況把常量的值存放在一些只讀存儲器中。這主要是為了一些特殊的功能考慮的。如出於版權控制的需要。如在列印機上為了保護原裝耗材的版權,往往把常量跟代碼分開存放。
存儲區域五:非RAM存儲
有時候,有些程序運行所需要的數據我們還會放置在其他地方。如在一些系統中需要用到流對象,這個對象的數據並沒有保存在上面所談到的任何一個存儲區域,這個對象直接被轉為為位元組流,發送到其他的主機上去了。另外有一種叫做持久化的對象,其是被存儲在硬碟中的。這些對象平時在應用程序開發過程中用到的並不是很多,大家只需要了解有這些對象的存在即可。等到需要用到的時候,再去深入研究也不遲。
從上面的分析中我們可以看到,對象的歸屬我們程序開發人員很難控制。寄存器是編譯器來管理的。而堆與堆棧又基本上受到開發平台的限制,我們程序人員也沒有這個能耐來干涉他們。其實我們主要能夠調整與控制的就是第四個存儲區域,即靜態存儲與常量存儲。筆者的建議是,對於非嵌入式程序,能夠利用靜態存儲來實現的,就盡量採用靜態存儲。而對於常量來說,需要根據需要實現的功能來判斷是否需要把常量存儲在只讀存儲器中。有時候對於版權的保護等等需要用到這個只讀存儲器。
Ⅵ java 怎麼把數據存到內存中
這里你採納與否沒關系,給你說說編程與內存的關系。
你定義的任何變數,常量,類,方法等等,其實都在內存中,沒有所謂的把數據存內存中,這概念,你可以想一下電腦重啟或關機後,內存中的所有數據,都會丟失,除非你保存到磁碟中去。
在內存中的數據有兩塊,第一、緩沖,一般寫數據到磁碟的時候開辟出來的內存空間;第二、緩存,一般是從磁碟讀數據到內存中開辟出來的內存空間。會這么使用,原因很簡單,磁碟讀寫數據速度與內存不一致(磁碟的存取效率遠遠小於內存的存取效率),為了提高數據的存取效率,才會這么乾的。
一般而言,java中的所謂數據,大部分都是類,從自動引用計數的概念來分析,你想把對象長久的放在內存中,不會被垃圾回收機制釋放,注意制葯有一個對象在使用/引用你的數據,這條數據就會存在內存中。所以,想servlet中的全局配置參數,隨時可以取到還是唯一一份,你可以參考一下。
另外內存使用分堆與棧,堆在面向對象編程中存儲對象的,棧是方法或函數執行的時候臨時開辟的存儲空間,方法或函數執行完畢就會釋放。
希望我的回復能幫助到你,採納與否沒關系。有更好的答案,我就隱藏我的回復。
Ⅶ 哪位能描述一下 java 中內存的分區情況和各類變數在內存中的存貯情況。
Java內存分配與管理是Java的核心技術之一,一般Java在內存分配時會涉及到以下區域:
◆寄存器:我們在程序中無法控制
◆棧:存放基本類型的數據和對象的引用,但對象本身不存放在棧中,而是存放在堆中
◆堆:存放用new產生的數據
◆靜態域:存放在對象中用static定義的靜態成員
◆常量池:存放常量
◆非RAM存儲:硬碟等永久存儲空間
Java內存分配中的棧
在函數中定義的一些基本類型的變數數據和對象的引用變數都在函數的棧內存中分配。
當在一段代碼塊定義一個變數時,Java就在棧中為這個變數分配內存空間,當該變數退出該作用域後,Java會自動釋放掉為該變數所分配的內存空間,該內存空間可以立即被另作他用。
Java內存分配中的堆
堆內存用來存放由new創建的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
在堆中產生了一個數組或對象後,還可以在棧中定義一個特殊的變數,讓棧中這個變數的取值等於數組或對象在堆內存中的首地址,棧中的這個變數就成了數組或對象的引用變數。引用變數就相當於是為數組或對象起的一個名稱,以後就可以在程序中使用棧中的引用變數來訪問堆中的數組或對象。引用變數就相當於是為數組或者對象起的一個名稱。
引用變數是普通的變數,定義時在棧中分配,引用變數在程序運行到其作用域之外後被釋放。而數組和對象本身在堆中分配,即使程序運行到使用new產生數組或者對象的語句所在的代碼塊之外,數組和對象本身占據的內存不會被釋放,數組和對象在沒有引用變數指向它的時候,才變為垃圾,不能在被使用,但仍然占據內存空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是Java比較占內存的原因。
實際上,棧中的變數指向堆內存中的變數,這就是Java中的指針!
常量池(constantpool)
常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,比如:
◆類和介面的全限定名;
◆欄位的名稱和描述符;
◆方法和名稱和描述符。
虛擬機必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和floatingpoint常量)和對其他類型,欄位和方法的符號引用。
對於String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的,對於String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字元串值,注意:該表只存儲文字字元串值,不存儲符號引用。說到這里,對常量池中的字元串值的存儲位置應該有一個比較明了的理解了。
在程序執行的時候,常量池會儲存在MethodArea,而不是堆中。
堆與棧
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配內存,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變數數據(int,short,long,byte,float,double,boolean,char)和對象句柄(引用)。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
1. inta=3;
2. intb=3;
編譯器先處理inta=3;首先它會在棧中創建一個變數為a的引用,然後查找棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3。接著處理intb=3;在創建完b的引用變數後,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。
這時,如果再令a=4;那麼編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改並不會影響到b,它是由編譯器完成的,它有利於節省空間。而一個對象引用變數修改了這個對象的內部狀態,會影響到另一個對象引用變數。
String是一個特殊的包裝類數據。可以用:
Stringstr=newString("abc");
Stringstr="abc";
兩種的形式來創建,第一種是用new()來新建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。而第二種是先在棧中創建一個對String類的對象引用變數str,然後通過符號引用去字元串常量池裡找有沒有"abc",如果沒有,則將"abc"存放進字元串常量池,並令str指向」abc」,如果已經有」abc」則直接令str指向「abc」。
比較類裡面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。
1.Stringstr1="abc";
2.Stringstr2="abc";
3.System.out.println(str1==str2);//true
可以看出str1和str2是指向同一個對象的。
1.Stringstr1=newString("abc");
2.Stringstr2=newString("abc");
3.System.out.println(str1==str2);//false
用new的方式是生成不同的對象。每一次生成一個。
因此用第二種方式創建多個」abc」字元串,在內存中其實只存在一個對象而已.這種寫法有利與節省內存空間.同時它可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於Stringstr=newString("abc");的代碼,則一概在堆中創建新對象,而不管其字元串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。
另一方面,要注意:我們在使用諸如Stringstr="abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!而可能只是指向一個先前已經創建的對象。只有通過new()方法才能保證每次都創建一個新的對象。
由於String類的immutable性質,當String變數需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。
1.首先String不屬於8種基本數據類型,String是一個對象。因為對象的默認值是null,所以String的默認值也是null;但它又是一種特殊的對象,有其它對象沒有的一些特性。
2.newString()和newString(」")都是申明一個新的空字元串,是空串不是null;
3.Stringstr=」kvill」;Stringstr=newString(」kvill」)的區別
示例:
1.Strings0="kvill";
2.Strings1="kvill";
3.Strings2="kv"+"ill";
4.System.out.println(s0==s1);
5.System.out.println(s0==s2);
結果為:
true
true
首先,我們要知結果為道Java會確保一個字元串常量只有一個拷貝。
因為例子中的s0和s1中的」kvill」都是字元串常量,它們在編譯期就被確定了,所以s0==s1為true;而」kv」和」ill」也都是字元串常量,當一個字元串由多個字元串常量連接而成時,它自己肯定也是字元串常量,所以s2也同樣在編譯期就被解析為一個字元串常量,所以s2也是常量池中」kvill」的一個引用。所以我們得出s0==s1==s2;用newString()創建的字元串不是常量,不能在編譯期就確定,所以newString()創建的字元串不放入常量池中,它們有自己的地址空間。
示例:
6.Strings0="kvill";
7.Strings1=newString("kvill");
8.Strings2="kv"+newString("ill");
9.System.out.println(s0==s1);
10.System.out.println(s0==s2);
11.System.out.println(s1==s2);
結果為:
false
false
false
例2中s0還是常量池中"kvill」的應用,s1因為無法在編譯期確定,所以是運行時創建的新對象」kvill」的引用,s2因為有後半部分newString(」ill」)所以也無法在編譯期確定,所以也是一個新創建對象」kvill」的應用;明白了這些也就知道為何得出此結果了。
4.String.intern():
再補充介紹一點:存在於.class文件中的常量池,在運行期被JVM裝載,並且可以擴充。String的intern()方法就是擴充常量池的一個方法;當一個String實例str調用intern()方法時,Java查找常量池中是否有相同Unicode的字元串常量,如果有,則返回其的引用,如果沒有,則在常量池中增加一個Unicode等於str的字元串並返回它的引用;看示例就清楚了
示例:
1.Strings0="kvill";
2.Strings1=newString("kvill");
3.Strings2=newString("kvill");
4.System.out.println(s0==s1);
5.System.out.println("**********");
6.s1.intern();
7.s2=s2.intern();//把常量池中"kvill"的引用賦給s2
8.System.out.println(s0==s1);
9.System.out.println(s0==s1.intern());
10.System.out.println(s0==s2);
結果為:
false
false//雖然執行了s1.intern(),但它的返回值沒有賦給s1
true//說明s1.intern()返回的是常量池中"kvill"的引用
true
最後我再破除一個錯誤的理解:有人說,「使用String.intern()方法則可以將一個String類的保存到一個全局String表中,如果具有相同值的Unicode字元串已經在這個表中,那麼該方法返回表中已有字元串的地址,如果在表中沒有相同值的字元串,則將自己的地址注冊到表中」如果我把他說的這個全局的String表理解為常量池的話,他的最後一句話,」如果在表中沒有相同值的字元串,則將自己的地址注冊到表中」是錯的:
示例:
1.Strings1=newString("kvill");
2.Strings2=s1.intern();
3.System.out.println(s1==s1.intern());
4.System.out.println(s1+""+s2);
5.System.out.println(s2==s1.intern());
結果:
1.false
2.kvillkvill
3.true
在這個類中我們沒有聲名一個」kvill」常量,所以常量池中一開始是沒有」kvill」的,當我們調用s1.intern()後就在常量池中新添加了一個」kvill」常量,原來的不在常量池中的」kvill」仍然存在,也就不是「將自己的地址注冊到常量池中」了。
s1==s1.intern()為false說明原來的」kvill」仍然存在;s2現在為常量池中」kvill」的地址,所以有s2==s1.intern()為true。
5.關於equals()和==:
這個對於String簡單來說就是比較兩字元串的Unicode序列是否相當,如果相等返回true;而==是比較兩字元串的地址是否相同,也就是是否是同一個字元串的引用。
6.關於String是不可變的
這一說又要說很多,大家只要知道String的實例一旦生成就不會再改變了,比如說:Stringstr=」kv」+」ill」+」「+」ans」;就是有4個字元串常量,首先」kv」和」ill」生成了」kvill」存在內存中,然後」kvill」又和」」生成「kvill「存在內存中,最後又和生成了」kvillans」;並把這個字元串的地址賦給了str,就是因為String的」不可變」產生了很多臨時變數,這也就是為什麼建議用StringBuffer的原因了,因為StringBuffer是可改變的。
下面是一些String相關的常見問題:
String中的final用法和理解
finalStringBuffera=newStringBuffer("111");
finalStringBufferb=newStringBuffer("222");
a=b;//此句編譯不通過
finalStringBuffera=newStringBuffer("111");
a.append("222");//編譯通過
可見,final只對引用的"值"(即內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至於它所指向的對象的變化,final是不負責的。
String常量池問題的幾個例子
下面是幾個常見例子的比較分析和理解:
Stringa="a1";
Stringb="a"+1;
System.out.println((a==b));//result=true
Stringa="atrue";
Stringb="a"+"true";
System.out.println((a==b));//result=true
Stringa="a3.4";
Stringb="a"+3.4;
System.out.println((a==b));//result=true
分析:JVM對於字元串常量的"+"號連接,將程序編譯期,JVM就將常量字元串的"+"連接優化為連接後的值,拿"a"+1來說,經編譯器優化後在class中就已經是a1。在編譯期其字元串常量的值就確定下來,故上面程序最終的結果都為true。
Stringa="ab";
Stringbb="b";
Stringb="a"+bb;
System.out.println((a==b));//result=false
分析:JVM對於字元串引用,由於在字元串的"+"連接中,有字元串引用存在,而引用的值在程序編譯期是無法確定的,即"a"+bb無法被編譯器優化,只有在程序運行期來動態分配並將連接後的新地址賦給b。所以上面程序的結果也就為false。
Stringa="ab";
finalStringbb="b";
Stringb="a"+bb;
System.out.println((a==b));//result=true
分析:和[3]中唯一不同的是bb字元串加了final修飾,對於final修飾的變數,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的位元組碼流中。所以此時的"a"+bb和"a"+"b"效果是一樣的。故上面程序的結果為true。
Stringa="ab";
finalStringbb=getBB();
Stringb="a"+bb;
System.out.println((a==b));//result=false
privatestaticStringgetBB(){
return"b";
}
分析:JVM對於字元串引用bb,它的值在編譯期無法確定,只有在程序運行期調用方法後,將方法的返回值和"a"來動態連接並分配地址為b,故上面程序的結果為false。
通過上面4個例子可以得出得知:
Strings="a"+"b"+"c";
就等價於Strings="abc";
Stringa="a";
Stringb="b";
Stringc="c";
Strings=a+b+c;
這個就不一樣了,最終結果等於:
1.StringBuffertemp=newStringBuffer();
2.temp.append(a).append(b).append(c);
3.Strings=temp.toString();
由上面的分析結果,可就不難推斷出String採用連接運算符(+)效率低下原因分析,形如這樣的代碼:
publicclassTest{
publicstaticvoidmain(Stringargs[]){
Strings=null;
for(inti=0;i<100;i++){
s+="a";
}
}
}
每做一次+就產生個StringBuilder對象,然後append後就扔掉。下次循環再到達時重新產生個StringBuilder對象,然後append字元串,如此循環直至結束。如果我們直接採用StringBuilder對象進行append的話,我們可以節省N-1次創建和銷毀對象的時間。所以對於在循環中要進行字元串連接的應用,一般都是用StringBuffer或StringBulider對象來進行append操作。
String對象的intern方法理解和分析:
1.publicclassTest4{
2.privatestaticStringa="ab";
3.publicstaticvoidmain(String[]args){
4.Strings1="a";
5.Strings2="b";
6.Strings=s1+s2;
7.System.out.println(s==a);//false
8.System.out.println(s.intern()==a);//true
9.}
10.}
這里用到Java裡面是一個常量池的問題。對於s1+s2操作,其實是在堆裡面重新創建了一個新的對象,s保存的是這個新對象在堆空間的的內容,所以s與a的值是不相等的。而當調用s.intern()方法,卻可以返回s在常量池中的地址值,因為a的值存儲在常量池中,故s.intern和a的值相等。
總結
棧中用來存放一些原始數據類型的局部變數數據和對象的引用(String,數組.對象等等)但不存放對象內容
堆中存放使用new關鍵字創建的對象.
字元串是一個特殊包裝類,其引用是存放在棧里的,而對象內容必須根據創建方式不同定(常量池和堆).有的是編譯期就已經創建好,存放在字元串常量池中,而有的是運行時才被創建.使用new關鍵字,存放在堆中。
Ⅷ java程序中怎樣用文件存儲數據
對於一些小文件,我們可以一次性讀取它的所有位元組,然後一次提交到資料庫
///
/// 這個方法演示了如何一次提交所有的位元組。這樣導致的結果是:應用程序立即需要申請等同於文件大小的內存
static void SubmitFileByOnce() {
string file = @"F:\功夫熊貓.rmvb";//文件大小為519MB
byte[] buffer = File.ReadAllBytes(file);
using (SqlConnection conn = new SqlConnection("server=(local);database=demo;integrated security=true")) {
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "INSERT INTO Files(FileName,FileContents) VALUES(@fileName,@fileContents)";
cmd.Parameters.AddRange(
new[]
{
new SqlParameter("@fileName",file),
new SqlParameter("@fileContents",buffer)
});
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
}
}
}
但是,上面的方法有幾個問題,主要體現在如果文件比較大的話
它需要一次性很大的內存,具體數據等同於文件大小。因為File.ReadAllBytes方法是將所有位元組全部讀入到內存。
它會導致提交失敗,就是因為數據太大了。資料庫也會拒絕。
那麼,我就對這個方法做了一下改進,將文件拆分為5MB一段,也就是說,此時每次申請的內存只有5MB。這就大大地提高了可用性。
/// 這個方法是將文件切分為5MB的塊,每次只是提交5MB,所以可能多次提交,但內存佔用就比較小
static void SubmitFileStepByStep() {
string file = @"F:\功夫熊貓.rmvb";//以這個文件為例,大小為519MB,一共需要的時間大約94秒。還是有點慢的,所以還可能需要進行壓縮
FileStream fs = new FileStream(file, FileMode.Open);
byte[] buffer = new byte[5 * 1024 * 1024];
int readCount;
using (SqlConnection conn = new SqlConnection("server=(local);database=demo;integrated security=true"))
{
conn.Open();
while ((readCount = fs.Read(buffer, 0, buffer.Length)) > 0)
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "INSERT INTO Files(FileName,FileContents) VALUES(@fileName,@fileContents)";
cmd.Parameters.AddRange(
new[]
{
new SqlParameter("@fileName",file),
new SqlParameter("@fileContents",buffer)
});
cmd.ExecuteNonQuery();
}
}
conn.Close();
}
}
這樣的話,有一個後果就是一個文件,可能在資料庫中會有多條記錄。所以在讀取的時候,我們需要對其進行合並
static void DownloadFile() {
string file = @"F:\功夫熊貓.rmvb";
string destfile = @"E:\Temp\Temp.wmv";
using (SqlConnection conn = new SqlConnection("server=(local);database=demo;integrated security=true"))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT FileContents FROM Files WHERE FileName=@fileName";
cmd.Parameters.AddRange(
new[]
{
new SqlParameter("@fileName",file),
});
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
FileStream fs = new FileStream(destfile, FileMode.Append, FileAccess.Write);
while (reader.Read())
{
byte[] buffer = (byte[])reader[0];
fs.Write(buffer, 0, buffer.Length);
}
fs.Close();
reader.Close();
conn.Close();
}
}
}