Java String類詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
引題
在Java語(yǔ)言的所有數(shù)據(jù)類型中,String類型是比較特殊的一種類型,同時(shí)也是面試的時(shí)候經(jīng)常被問(wèn)到的一個(gè)知識(shí)點(diǎn),本文結(jié)合Java內(nèi)存分配深度分析關(guān)于String的許多令人迷惑的問(wèn)題。下面是本文將要涉及到的一些問(wèn)題,如果讀者對(duì)這些問(wèn)題都了如指掌,則可忽略此文。
1、Java內(nèi)存具體指哪塊內(nèi)存?這塊內(nèi)存區(qū)域?yàn)槭裁匆M(jìn)行劃分?是如何劃分的?劃分之后每塊區(qū)域的作用是什么?如何設(shè)置各個(gè)區(qū)域的大小?
2、String類型在執(zhí)行連接操作時(shí),效率為什么會(huì)比StringBuffer或者StringBuilder低?StringBuffer和StringBuilder有什么聯(lián)系和區(qū)別?
3、Java中常量是指什么?String s = "s" 和 String s = new String("s") 有什么不一樣?
本文經(jīng)多方資料的收集整理和歸納,最終撰寫成文,如果有錯(cuò)誤之處,請(qǐng)多多指教!
Java內(nèi)存分配
1、JVM簡(jiǎn)介
Java虛擬機(jī)(Java Virtual Machine 簡(jiǎn)稱JVM)是運(yùn)行所有Java程序的抽象計(jì)算機(jī),是Java語(yǔ)言的運(yùn)行環(huán)境,它是Java 最具吸引力的特性之一。Java虛擬機(jī)有自己完善的硬體架構(gòu),如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。
一個(gè)運(yùn)行時(shí)的Java虛擬機(jī)實(shí)例的天職是:負(fù)責(zé)運(yùn)行一個(gè)java程序。當(dāng)啟動(dòng)一個(gè)Java程序時(shí),一個(gè)虛擬機(jī)實(shí)例也就誕生了。當(dāng)該程序關(guān)閉退出,這個(gè)虛擬機(jī)實(shí)例也就隨之消亡。如果同一臺(tái)計(jì)算機(jī)上同時(shí)運(yùn)行三個(gè)Java程序,將得到三個(gè)Java虛擬機(jī)實(shí)例。每個(gè)Java程序都運(yùn)行于它自己的Java虛擬機(jī)實(shí)例中。
如下圖所示,JVM的體系結(jié)構(gòu)包含幾個(gè)主要的子系統(tǒng)和內(nèi)存區(qū):
垃圾回收器(Garbage Collection):負(fù)責(zé)回收堆內(nèi)存(Heap)中沒有被使用的對(duì)象,即這些對(duì)象已經(jīng)沒有被引用了。
類裝載子系統(tǒng)(Classloader Sub-System):除了要定位和導(dǎo)入二進(jìn)制class文件外,還必須負(fù)責(zé)驗(yàn)證被導(dǎo)入類的正確性,為類變量分配并初始化內(nèi)存,以及幫助解析符號(hào)引用。
執(zhí)行引擎(Execution Engine):負(fù)責(zé)執(zhí)行那些包含在被裝載類的方法中的指令。
運(yùn)行時(shí)數(shù)據(jù)區(qū)(Java Memory Allocation Area):又叫虛擬機(jī)內(nèi)存或者Java內(nèi)存,虛擬機(jī)運(yùn)行時(shí)需要從整個(gè)計(jì)算機(jī)內(nèi)存劃分一塊內(nèi)存區(qū)域存儲(chǔ)許多東西。例如:字節(jié)碼、從已裝載的class文件中得到的其他信息、程序創(chuàng)建的對(duì)象、傳遞給方法的參數(shù),返回值、局部變量等等。
2、Java內(nèi)存分區(qū)
從上節(jié)知道,運(yùn)行時(shí)數(shù)據(jù)區(qū)即是java內(nèi)存,而且數(shù)據(jù)區(qū)要存儲(chǔ)的東西比較多,如果不對(duì)這塊內(nèi)存區(qū)域進(jìn)行劃分管理,會(huì)顯得比較雜亂無(wú)章。程序喜歡有規(guī)律的東西,最討厭雜亂無(wú)章的東西。 根據(jù)存儲(chǔ)數(shù)據(jù)的不同,java內(nèi)存通常被劃分為5個(gè)區(qū)域:程序計(jì)數(shù)器(Program Count Register)、本地方法棧(Native Stack)、方法區(qū)(Methon Area)、棧(Stack)、堆(Heap)。
程序計(jì)數(shù)器(Program Count Register):又叫程序寄存器。JVM支持多個(gè)線程同時(shí)運(yùn)行,當(dāng)每一個(gè)新線程被創(chuàng)建時(shí),它都將得到它自己的PC寄存器(程序計(jì)數(shù)器)。如果線程正在執(zhí)行的是一個(gè)Java方法(非native),那么PC寄存器的值將總是指向下一條將被執(zhí)行的指令,如果方法是 native的,程序計(jì)數(shù)器寄存器的值不會(huì)被定義。 JVM的程序計(jì)數(shù)器寄存器的寬度足夠保證可以持有一個(gè)返回地址或者native的指針。
棧(Stack):又叫堆棧。JVM為每個(gè)新創(chuàng)建的線程都分配一個(gè)棧。也就是說(shuō),對(duì)于一個(gè)Java程序來(lái)說(shuō),它的運(yùn)行就是通過(guò)對(duì)棧的操作來(lái)完成的。棧以幀為單位保存線程的狀態(tài)。JVM對(duì)棧只進(jìn)行兩種操作:以幀為單位的壓棧和出棧操作。我們知道,某個(gè)線程正在執(zhí)行的方法稱為此線程的當(dāng)前方法。我們可能不知道,當(dāng)前方法使用的幀稱為當(dāng)前幀。當(dāng)線程激活一個(gè)Java方法,JVM就會(huì)在線程的 Java堆棧里新壓入一個(gè)幀,這個(gè)幀自然成為了當(dāng)前幀。在此方法執(zhí)行期間,這個(gè)幀將用來(lái)保存參數(shù)、局部變量、中間計(jì)算過(guò)程和其他數(shù)據(jù)。從Java的這種分配機(jī)制來(lái)看,堆棧又可以這樣理解:棧(Stack)是操作系統(tǒng)在建立某個(gè)進(jìn)程時(shí)或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個(gè)線程建立的存儲(chǔ)區(qū)域,該區(qū)域具有先進(jìn)后出的特性。其相關(guān)設(shè)置參數(shù):
• -Xss --設(shè)置方法棧的最大值
本地方法棧(Native Stack):存儲(chǔ)本地方方法的調(diào)用狀態(tài)。
方法區(qū)(Method Area):當(dāng)虛擬機(jī)裝載一個(gè)class文件時(shí),它會(huì)從這個(gè)class文件包含的二進(jìn)制數(shù)據(jù)中解析類型信息,然后把這些類型信息(包括類信息、常量、靜態(tài)變量等)放到方法區(qū)中,該內(nèi)存區(qū)域被所有線程共享,如下圖所示。本地方法區(qū)存在一塊特殊的內(nèi)存區(qū)域,叫常量池(Constant Pool),這塊內(nèi)存將與String類型的分析密切相關(guān)。
堆(Heap):Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域。在此區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都是在這里分配內(nèi)存,但是這個(gè)對(duì)象的引用卻是在棧(Stack)中分配。因此,執(zhí)行String s = new String("s")時(shí),需要從兩個(gè)地方分配內(nèi)存:在堆中為String對(duì)象分配內(nèi)存,
在棧中為引用(這個(gè)堆對(duì)象的內(nèi)存地址,即指針)分配內(nèi)存,如下圖所示。
JAVA虛擬機(jī)有一條在堆中分配新對(duì)象的指令,卻沒有釋放內(nèi)存的指令,正如你無(wú)法用Java代碼區(qū)明確釋放一個(gè)對(duì)象一樣。虛擬機(jī)自己負(fù)責(zé)決定如何以及何時(shí)釋放不再被運(yùn)行的程序引用的對(duì)象所占據(jù)的內(nèi)存,通常,虛擬機(jī)把這個(gè)任務(wù)交給垃圾收集器(Garbage Collection)。其相關(guān)設(shè)置參數(shù):
• -Xms -- 設(shè)置堆內(nèi)存初始大小
• -Xmx -- 設(shè)置堆內(nèi)存最大值
• -XX:MaxTenuringThreshold -- 設(shè)置對(duì)象在新生代中存活的次數(shù)
• -XX:PretenureSizeThreshold -- 設(shè)置超過(guò)指定大小的大對(duì)象直接分配在舊生代中
Java堆是垃圾收集器管理的主要區(qū)域,因此又稱為“GC 堆”(Garbage Collectioned Heap)?,F(xiàn)在的垃圾收集器基本都是采用的分代收集算法,所以Java堆還可以細(xì)分為:新生代(Young Generation)和老年代(Old Generation),如下圖所示。分代收集算法的思想:第一種說(shuō)法,用較高的頻率對(duì)年輕的對(duì)象(young generation)進(jìn)行掃描和回收,這種叫做minor collection,而對(duì)老對(duì)象(old generation)的檢查回收頻率要低很多,稱為major collection。這樣就不需要每次GC都將內(nèi)存中所有對(duì)象都檢查一遍,以便讓出更多的系統(tǒng)資源供應(yīng)用系統(tǒng)使用;另一種說(shuō)法,在分配對(duì)象遇到內(nèi)存不足時(shí),先對(duì)新生代進(jìn)行GC(Young GC);當(dāng)新生代GC之后仍無(wú)法滿足內(nèi)存空間分配需求時(shí), 才會(huì)對(duì)整個(gè)堆空間以及方法區(qū)進(jìn)行GC(Full GC)。
在這里可能會(huì)有讀者表示疑問(wèn):記得還有一個(gè)什么永久代(Permanent Generation)的啊,難道它不屬于Java堆?親,你答對(duì)了!其實(shí)傳說(shuō)中的永久代就是上面所說(shuō)的方法區(qū),存放的都是jvm初始化時(shí)加載器加載的一些類型信息(包括類信息、常量、靜態(tài)變量等),這些信息的生存周期比較長(zhǎng),GC不會(huì)在主程序運(yùn)行期對(duì)PermGen Space進(jìn)行清理,所以如果你的應(yīng)用中有很多CLASS的話,就很可能出現(xiàn)PermGen Space錯(cuò)誤。其相關(guān)設(shè)置參數(shù):
• -XX:PermSize --設(shè)置Perm區(qū)的初始大小
• -XX:MaxPermSize --設(shè)置Perm區(qū)的最大值
新生代(Young Generation)又分為:Eden區(qū)和Survivor區(qū),Survivor區(qū)有分為From Space和To Space。Eden區(qū)是對(duì)象最初分配到的地方;默認(rèn)情況下,F(xiàn)rom Space和To Space的區(qū)域大小相等。JVM進(jìn)行Minor GC時(shí),將Eden中還存活的對(duì)象拷貝到Survivor區(qū)中,還會(huì)將Survivor區(qū)中還存活的對(duì)象拷貝到Tenured區(qū)中。在這種GC模式下,JVM為了提升GC效率, 將Survivor區(qū)分為From Space和To Space,這樣就可以將對(duì)象回收和對(duì)象晉升分離開來(lái)。新生代的大小設(shè)置有2個(gè)相關(guān)參數(shù):
• -Xmn -- 設(shè)置新生代內(nèi)存大小。
• -XX:SurvivorRatio -- 設(shè)置Eden與Survivor空間的大小比例
老年代(Old Generation): 當(dāng) OLD 區(qū)空間不夠時(shí), JVM 會(huì)在 OLD 區(qū)進(jìn)行 major collection ;完全垃圾收集后,若Survivor及OLD區(qū)仍然無(wú)法存放從Eden復(fù)制過(guò)來(lái)的部分對(duì)象,導(dǎo)致JVM無(wú)法在Eden區(qū)為新對(duì)象創(chuàng)建內(nèi)存區(qū)域,則出現(xiàn)"Out of memory錯(cuò)誤" 。
三、String類型的深度解析
讓我們從Java數(shù)據(jù)類型開始說(shuō)起吧!Java數(shù)據(jù)類型通常(分類方法多種多樣)從整體上可以分為兩大類:基礎(chǔ)類型和引用類型,基礎(chǔ)類型的變量持有原始值,引用類型的變量通常表示的是對(duì)實(shí)際對(duì)象的引用,其值通常為對(duì)象的內(nèi)存地址。
1、String的本質(zhì)
打開String的源碼,類注釋中有這么一段話“Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。這句話總結(jié)歸納了String的一個(gè)最重要的特點(diǎn):String是值不可變(immutable)的常量,是線程安全的(can be shared)。
接下來(lái),String類使用了final修飾符,表明了String類的第二個(gè)特點(diǎn):String類是不可繼承的。
下面是String類的成員變量定義,從類的實(shí)現(xiàn)上闡明了String值是不可變的(immutable)。
private final char value[]; private final int count;
因此,我們看String類的concat方法。實(shí)現(xiàn)該方法第一步要做的肯定是擴(kuò)大成員變量value的容量,擴(kuò)容的方法重新定義一個(gè)大容量的字符數(shù)組buf。第二步就是把原來(lái)value中的字符copy到buf中來(lái),再把需要concat的字符串值也copy到buf中來(lái),這樣子,buf中就包含了concat之后的字符串值。下面就是問(wèn)題的關(guān)鍵了,如果value不是final的,直接讓value指向buf,然后返回this,則大功告成,沒有必要返回一個(gè)新的String對(duì)象。但是。。??上?。。。由于value是final型的,所以無(wú)法指向新定義的大容量數(shù)組buf,那怎么辦呢?“return new String(0, count + otherLen, buf);”,這是String類concat實(shí)現(xiàn)方法的最后一條語(yǔ)句,重新new一個(gè)String對(duì)象返回。這下真相大白了吧!
總結(jié):String實(shí)質(zhì)是字符數(shù)組,兩個(gè)特點(diǎn):1、該類不可被繼承;2、不可變性(immutable)。
2、String的定義方法
在討論String的定義方法之前,先了解一下常量池的概念,前面在介紹方法區(qū)的時(shí)候已經(jīng)提到過(guò)了。下面稍微正式的給一個(gè)定義吧。
常量池(constant pool)指的是在編譯期被確定,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。它包括了關(guān)于類、方法、接口等中的常量,也包括字符串常量。常量池還具備動(dòng)態(tài)性,運(yùn)行期間可以將新的常量放入池中,String類的intern()方法是這一特性的典型應(yīng)用。不懂嗎?后面會(huì)介紹intern方法的。虛擬機(jī)為每個(gè)被裝載的類型維護(hù)一個(gè)常量池,池中為該類型所用常量的一個(gè)有序集合,包括直接常量(string、integer和float常量)和對(duì)其他類型、字段和方法的符號(hào)引用(與對(duì)象引用的區(qū)別?讀者可以自己去了解)。
String的定義方法歸納起來(lái)總共為三種方式:
• 使用關(guān)鍵字new,如:String s1 = new String("myString");
• 直接定義,如:String s1 = "myString";
• 串聯(lián)生成,如:String s1 = "my" + "String";這種方式比較復(fù)雜,這里就不贅述了。
第一種方式通過(guò)關(guān)鍵字new定義過(guò)程:在程序編譯期,編譯程序先去字符串常量池檢查,是否存在“myString”,如果不存在,則在常量池中開辟一個(gè)內(nèi)存空間存放“myString”;如果存在的話,則不用重新開辟空間,保證常量池中只有一個(gè)“myString”常量,節(jié)省內(nèi)存空間。然后在內(nèi)存堆中開辟一塊空間存放new出來(lái)的String實(shí)例,在棧中開辟一塊空間,命名為“s1”,存放的值為堆中String實(shí)例的內(nèi)存地址,這個(gè)過(guò)程就是將引用s1指向new出來(lái)的String實(shí)例。各位,最模糊的地方到了!堆中new出來(lái)的實(shí)例和常量池中的“myString”是什么關(guān)系呢?等我們分析完了第二種定義方式之后再回頭分析這個(gè)問(wèn)題。
第二種方式直接定義過(guò)程:在程序編譯期,編譯程序先去字符串常量池檢查,是否存在“myString”,如果不存在,則在常量池中開辟一個(gè)內(nèi)存空間存放“myString”;如果存在的話,則不用重新開辟空間。然后在棧中開辟一塊空間,命名為“s1”,存放的值為常量池中“myString”的內(nèi)存地址。常量池中的字符串常量與堆中的String對(duì)象有什么區(qū)別呢?為什么直接定義的字符串同樣可以調(diào)用String對(duì)象的各種方法呢?
帶著諸多疑問(wèn),我和大家一起探討一下堆中String對(duì)象和常量池中String常量的關(guān)系,請(qǐng)大家記住,僅僅是探討,因?yàn)楸救藢?duì)這塊也比較模糊。
第一種猜想:因?yàn)橹苯佣x的字符串也可以調(diào)用String對(duì)象的各種方法,那么可以認(rèn)為其實(shí)在常量池中創(chuàng)建的也是一個(gè)String實(shí)例(對(duì)象)。String s1 = new String("myString");
先在編譯期的時(shí)候在常量池創(chuàng)建了一個(gè)String實(shí)例,然后clone了一個(gè)String實(shí)例存儲(chǔ)在堆中,引用s1指向堆中的這個(gè)實(shí)例。此時(shí),池中的實(shí)例沒有被引用。當(dāng)接著執(zhí)行String s1 = "myString";時(shí),因?yàn)槌刂幸呀?jīng)存在“myString”的實(shí)例對(duì)象,則s1直接指向池中的實(shí)例對(duì)象;否則,在池中先創(chuàng)建一個(gè)實(shí)例對(duì)象,s1再指向它。如下圖所示:
這種猜想認(rèn)為:常量池中的字符串常量實(shí)質(zhì)上是一個(gè)String實(shí)例,與堆中的String實(shí)例是克隆關(guān)系。
第二種猜想也是目前網(wǎng)上闡述的最多的,但是思路都不清晰,有些問(wèn)題解釋不通。下面引用《JAVA String對(duì)象和字符串常量的關(guān)系解析》一段內(nèi)容。
在解析階段,虛擬機(jī)發(fā)現(xiàn)字符串常量"myString",它會(huì)在一個(gè)內(nèi)部字符串常量列表中查找,如果沒有找到,那么會(huì)在堆里面創(chuàng)建一個(gè)包含字符序列[myString]的String對(duì)象s1,然后把這個(gè)字符序列和對(duì)應(yīng)的String對(duì)象作為名值對(duì)( [myString], s1 )保存到內(nèi)部字符串常量列表中。如下圖所示:
如果虛擬機(jī)后面又發(fā)現(xiàn)了一個(gè)相同的字符串常量myString,它會(huì)在這個(gè)內(nèi)部字符串常量列表內(nèi)找到相同的字符序列,然后返回對(duì)應(yīng)的String對(duì)象的引用。維護(hù)這個(gè)內(nèi)部列表的關(guān)鍵是任何特定的字符序列在這個(gè)列表上只出現(xiàn)一次。
例如,String s2 = "myString",運(yùn)行時(shí)s2會(huì)從內(nèi)部字符串常量列表內(nèi)得到s1的返回值,所以s2和s1都指向同一個(gè)String對(duì)象。
這個(gè)猜想有一個(gè)比較明顯的問(wèn)題,紅色字體標(biāo)示的地方就是問(wèn)題的所在。證明方式很簡(jiǎn)單,下面這段代碼的執(zhí)行結(jié)果,javaer都應(yīng)該知道。
String s1 = new String("myString"); String s2 = "myString"; System.out.println(s1 == s2); //按照上面的推測(cè)邏輯,那么打印的結(jié)果為true;而實(shí)際上真實(shí)的結(jié)果是false,因?yàn)閟1指向的是堆中String對(duì)象,而s2指向的是常量池中的String常量。
雖然這段內(nèi)容不那么有說(shuō)服力,但是文章提到了一個(gè)東西——字符串常量列表,它可能是解釋這個(gè)問(wèn)題的關(guān)鍵。
文中提到的三個(gè)問(wèn)題,本文僅僅給出了猜想,具體請(qǐng)自己考證!
• 堆中new出來(lái)的實(shí)例和常量池中的“myString”是什么關(guān)系呢?
• 常量池中的字符串常量與堆中的String對(duì)象有什么區(qū)別呢?
• 為什么直接定義的字符串同樣可以調(diào)用String對(duì)象的各種方法呢?
3、String、StringBuffer、StringBuilder的聯(lián)系與區(qū)別
上面已經(jīng)分析了String的本質(zhì)了,下面簡(jiǎn)單說(shuō)說(shuō)StringBuffer和StringBuilder。
StringBuffer和StringBuilder都繼承了抽象類AbstractStringBuilder,這個(gè)抽象類和String一樣也定義了char[] value和int count,但是與String類不同的是,它們沒有final修飾符。因此得出結(jié)論:String、StringBuffer和StringBuilder在本質(zhì)上都是字符數(shù)組,不同的是,在進(jìn)行連接操作時(shí),String每次返回一個(gè)新的String實(shí)例,而StringBuffer和StringBuilder的append方法直接返回this,所以這就是為什么在進(jìn)行大量字符串連接運(yùn)算時(shí),不推薦使用String,而推薦StringBuffer和StringBuilder。那么,哪種情況使用StringBuffe?哪種情況使用StringBuilder呢?
關(guān)于StringBuffer和StringBuilder的區(qū)別,翻開它們的源碼,下面貼出append()方法的實(shí)現(xiàn)。
上面第一張圖是StringBuffer中append()方法的實(shí)現(xiàn),第二張圖為StringBuilder對(duì)append()的實(shí)現(xiàn)。區(qū)別應(yīng)該一目了然,StringBuffer在方法前加了一個(gè)synchronized修飾,起到同步的作用,可以在多線程環(huán)境使用。為此付出的代價(jià)就是降低了執(zhí)行效率。因此,如果在多線程環(huán)境可以使用StringBuffer進(jìn)行字符串連接操作,單線程環(huán)境使用StringBuilder,它的效率更高。
- Java中char數(shù)組(字符數(shù)組)與字符串String類型的轉(zhuǎn)換方法
- java對(duì)象轉(zhuǎn)換String類型的三種方法
- java string類的常用方法詳細(xì)介紹
- Java中String類(字符串操作)的10個(gè)常見問(wèn)題和解決方法
- java string類型轉(zhuǎn)換boolean類型的方法
- Java String類的常用方法匯總
- java String類常用方法練習(xí)小結(jié)
- Java進(jìn)階教程之String類
- Java中字符數(shù)組、String類、StringBuffer三者之間相互轉(zhuǎn)換
- java源碼解析之String類的compareTo(String otherString)方法
- java String類功能、原理與應(yīng)用案例【統(tǒng)計(jì)、判斷、轉(zhuǎn)換等】
- Java String類簡(jiǎn)單用法實(shí)戰(zhàn)示例【字符串輸出、比較】
相關(guān)文章
詳解MyBatisPlus如何實(shí)現(xiàn)分頁(yè)和查詢操作
這篇文章主要為大家詳細(xì)介紹了MyBatisPlus是如何實(shí)現(xiàn)分頁(yè)和查詢操作的,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定的幫助,需要的可以參考一下2022-05-05java實(shí)現(xiàn)同步回調(diào)的示例代碼
同步回調(diào)是一種在調(diào)用代碼中同步執(zhí)行回調(diào)函數(shù)的編程模式,在Java中,通過(guò)定義和實(shí)現(xiàn)接口來(lái)構(gòu)建同步回調(diào),本文就來(lái)介紹一下如何實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-09-09springboot config 攔截器使用方法實(shí)例詳解
本文介紹Spring-Boot中使用攔截器的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05Java讀取Properties文件的七種方法的總結(jié)
這篇文章主要介紹了Java讀取Properties文件的七種方法的總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-07-07java實(shí)現(xiàn)301跳轉(zhuǎn)和重定向的方法
301跳轉(zhuǎn)和重定向是做項(xiàng)目的時(shí)候經(jīng)常需要用到的,本文給大家分享的是在java中301跳轉(zhuǎn)和重定向的方法,需要的小伙伴參考下吧。2015-03-03如何通過(guò)java將doc文件轉(zhuǎn)換為docx文件詳解
在數(shù)字化時(shí)代文檔處理成為了我們?nèi)粘9ぷ骱蛯W(xué)習(xí)中不可或缺的一部分,其中doc和docx作為兩種常見的文檔格式,各自具有不同的特點(diǎn)和優(yōu)勢(shì),這篇文章主要給大家介紹了關(guān)于如何通過(guò)java將doc文件轉(zhuǎn)換為docx文件的相關(guān)資料,需要的朋友可以參考下2024-07-07IDEA如何修改maven的JVM啟動(dòng)內(nèi)存參數(shù)
這篇文章主要介紹了IDEA如何修改maven的JVM啟動(dòng)內(nèi)存參數(shù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09快速解決commons-fileupload組件無(wú)法處理自定義head信息的bug
問(wèn)題在于fileupload組件解析完自定義的head節(jié)點(diǎn)后,卻忘記傳遞到FileItemStreamImpl中了,稍作修訂,即可修正該bug2013-08-08