Java NIO 基礎知識

2021-01-08 zero丶丶丶

前言

前言部分是科普,讀者可自行選擇是否閱讀這部分內容。

為什麼我們需要關心 NIO?我想很多業務猿都會有這個疑問。

我在工作的前兩年對這個問題也很不解,因為那個時候我認為自己已經非常熟悉 IO 操作了,讀寫文件什麼的都非常溜了,IO 包無非就是 File、RandomAccessFile、字節流、字符流這些,感覺沒什麼好糾結的。最混亂的當屬 InputStream/OutputStream 一大堆的類不知道誰是誰,不過了解了裝飾者模式以後,也都輕鬆破解了。

在 Java 領域,一般性的文件操作確實只需要和 java.io 包打交道就可以了,尤其對於寫業務代碼的程式設計師來說。不過,當你寫了兩三年代碼後,你的業務代碼可能已經寫得很溜了,蒙著眼睛也能寫增刪改查了。這個時候,也許你會想要開始了解更多的底層內容,包括並發、JVM、分布式系統、各個開源框架源碼實現等,處於這個階段的程式設計師會開始認識到 NIO 的用處,因為系統間通訊無處不在。

可能很多人不知道 Netty 或 Mina 有什麼用?和 Tomcat 有什麼區別?為什麼我用 HTTP 請求就可以解決應用間調用的問題卻要使用 Netty?

當然,這些問題的答案很簡單,就是為了提升性能。那意思是 Tomcat 性能不好?當然不是,它們的使用場景就不一樣。當初我也不知道 Nginx 擺在 Tomcat 前面有什麼用,也是經過實踐慢慢領悟到了那麼些意思。

Nginx 是 web 伺服器,Tomcat/Jetty 是應用伺服器,Netty 是通訊工具。

也許你現在還不知道 NIO 有什麼用,但是一定不要放棄學習它。

緩衝區操作

緩衝區是 NIO 操作的核心,本質上 NIO 操作就是緩衝區操作。

寫操作是將緩衝區的數據排乾,如將數據從緩衝區持久化到磁碟中。

讀操作是將數據填充到緩衝區中,以便應用程式後續使用數據。

當然,我們這裡說的緩衝區是指用戶空間的緩衝區。

.

簡單分析下上圖。應用程式發出讀操作後,內核向磁碟控制器發送命令,要求磁碟返回相應數據,磁碟控制器通過 DMA 直接將數據發送到內核緩衝區。一旦內核緩衝區滿了,內核即把數據拷貝到請求數據的進程指定的緩衝區中。

DMA: Direct Memory AccessWikipedia:直接內存訪問是計算機科學中的一種內存訪問技術。它允許某些電腦內部的硬體子系統(電腦外設),可以獨立地直接讀寫系統內存,而不需中央處理器(CPU)介入處理 。在同等程度的處理器負擔下,DMA 是一種快速的數據傳送方式。很多硬體的系統會使用 DMA,包含硬碟控制器、繪圖顯卡、網卡和音效卡。也就是說,磁碟控制器可以在不用 CPU 的幫助下就將數據從磁碟寫到內存中,畢竟讓 CPU 等待 IO 操作完成是一種浪費

很容易看出來,數據先到內核,然後再從內核複製到用戶空間緩衝區的做法並不高效,下面簡單說說為什麼需要這麼設計。

首先,用戶空間運行的代碼是不可以直接訪問硬體的,需要由內核空間來負責和硬體通訊,內核空間由作業系統控制。其次,磁碟存儲的是固定大小的數據塊,磁碟按照扇區來組織數據,而用戶進程請求的一般都是任意大小的數據塊,所以需要由內核來負責協調,內核會負責組裝、拆解數據。

內核空間會對數據進行緩存和預讀取,所以,如果用戶進程需要的數據剛好在內核空間中,直接拷貝過來就可以了。如果內核空間沒有用戶進程需要的數據的話,需要掛起用戶進程,等待數據準備好。

虛擬內存

這個概念大家都懂,這裡就繼續囉嗦一下了,虛擬內存是計算機系統內存管理的一種技術。前面說的緩存區操作看似簡單,但是具體到底層細節,還是蠻複雜的。

下面的描述,我儘量保證準確,但是不會展開得太具體,因為虛擬內存還是蠻複雜的,要完全介紹清楚,恐怕需要很大的篇幅,如果讀者對這方面的內容感興趣的話,建議讀者尋找更加專業全面的介紹資料,如《深入理解計算機系統》。

物理內存被組織成一個很大的數組,每個單元是一個字節大小,然後每個字節都有一個唯一的物理地址,這應該很好理解。

虛擬內存是對物理內存的抽象,它使得應用程式認為它自己擁有連續可用的內存(一個連續完整的地址空間),而實際上,應用程式得到的全部內存其實是一個假象,它通常會被分隔成多個物理內存碎片(後面說的頁),還有部分暫時存儲在外部磁碟存儲器上,在需要時進行換入換出。

舉個例子,在 32 位系統中,每個應用程式能訪問到的內存是 4G(32 位系統的最大尋址空間 2^32),這裡的 4G 就是虛擬內存,每個程序都以為自己擁有連續的 4G 空間的內存,即使我們的計算機只有 2G 的物理內存。也就是說,對於機器上同時運行的多個應用程式,每個程序都以為自己能得到連續的 4G 的內存。這中間就是使用了虛擬內存。

我們從概念上看,虛擬內存也被組織成一個很大的數組,每個單元也是一個字節大小,每個字節都有唯一的虛擬地址。它被存儲於磁碟上,物理內存是它的緩存。

物理內存作為虛擬內存的緩存,當然不是以字節為單位進行組織的,那樣效率太低了,它們之間是以頁(page)進行緩存的。虛擬內存被分割為一個個虛擬頁,物理內存也被分割為一個個物理頁,這兩個頁的大小應該是一致的,通常是 4KB - 2MB。

舉個例子,看下圖:

.

進程 1 現在有 8 個虛擬頁,其中有 2 個虛擬頁緩存在主存中,6 個還在磁碟上,需要的時候再讀入主存中;進程 2 有 7 個虛擬頁,其中 4 個緩存在主存中,3 個還在磁碟上。

在 CPU 讀取內存數據的時候,給出的是虛擬地址,將一個虛擬地址轉換為物理地址的任務我們稱之為地址翻譯。在主存中的查詢表存放了虛擬地址到物理地址的映射關係,表的內容由作業系統維護。CPU 需要訪問內存時,CPU 上有一個叫做內存管理單元的硬體會先去查詢真實的物理地址,然後再到指定的物理地址讀取數據。

上面說的那個查詢表,我們稱之為頁表,虛擬內存系統通過頁表來判斷一個虛擬頁是否已經緩存在了主存中。如果是,頁表會負責到物理頁的映射;如果不命中,也就是我們經常會見到的概念缺頁,對應的英文是 page fault,系統首先判斷這個虛擬頁存放在磁碟的哪個位置,然後在物理內存中選擇一個犧牲頁,並將虛擬頁從磁碟複製到內存中,替換這個犧牲頁。

在磁碟和內存之間傳送頁的活動叫做交換(swapping)或者頁面調度(paging)。

下面,簡單介紹下虛擬內存帶來的好處。

SRAM緩存:表示位於 CPU 和主存之間的 L1、L2 和 L3 高速緩存。

DRAM緩存:表示虛擬內存系統的緩存,緩存虛擬頁到主存中。

物理內存訪問速度比高速緩存要慢 10 倍左右,而磁碟要比物理內存慢大約 100000 倍。所以,DRAM 的緩存不命中比 SRAM 緩存不命中代價要大得多,因為 DRAM 緩存一旦不命中,就需要到磁碟加載虛擬頁。而 SRAM 緩存不命中,通常由 DRAM 的主存來服務。而從磁碟的一個扇區讀取第一個字節的時間開銷比起讀這個扇區中連續的字節要慢大約 100000 倍。

了解 Kafka 的讀者應該知道,消息在磁碟中的順序存儲對於 Kafka 的性能至關重要。

結論就是,IO 的性能主要是由 DRAM 的緩存是否命中決定的。

內存映射文件

英文名是 Memory Mapped Files,相信大家也都聽過這個概念,在許多對 IO 性能要求比較高的 java 應用中會使用到,它是作業系統提供的支持,後面我們在介紹 NIO Buffer 的時候會碰到的 MappedByteBuffer 就是用來支持這一特性的。

是什麼:

我們可以認為內存映射文件是一類特殊的文件,我們的 Java 程序可以直接從內存中讀取到文件的內容。它是通過將整個文件或文件的部分內容映射到內存頁中實現的,作業系統會負責加載需要的頁,所以它的速度是非常快的。

優勢:

一旦我們將數據寫入到了內存映射文件,即使我們的 JVM 掛掉了,作業系統依然會幫助我們將這部分內存數據持久化到磁碟上。當然了,如果是斷電的話,還是有可能會丟失數據的。另外,它比較適合於處理大文件,因為作業系統只會在我們需要的頁不在內存中時才會去加載頁數據,而用其處理大量的小文件反而可能會造成頻繁的缺頁。另一個重要的優勢就是內存共享。我們可以在多個進程中同時使用同一個內存映射文件,也算是一種進程間協作的方式吧。想像下進程間的數據通訊平時我們一般採用 Socket 來請求,而內存共享至少可以帶來 10 倍以上的性能提升。

我們還沒有接觸到 NIO 的 Buffer,下面就簡單地示意一下:

import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class MemoryMappedFileInJava { private static int count = 10485760; //10 MB public static void main(String[] args) throws Exception { RandomAccessFile memoryMappedFile = new RandomAccessFile("largeFile.txt", "rw"); // 將文件映射到內存中,map 方法 MappedByteBuffer out = memoryMappedFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, count); // 這一步的寫操作其實是寫到內存中,並不直接操作文件 for (int i = 0; i < count; i++) { out.put((byte) 'A'); } System.out.println("Writing to Memory Mapped File is completed"); // 這一步的讀操作讀的是內存 for (int i = 0; i < 10 ; i++) { System.out.print((char) out.get(i)); } System.out.println("Reading from Memory Mapped File is completed"); }}

我們需要注意的一點就是,用於加載內存映射文件的內存是堆外內存。

參考資料:Why use Memory Mapped File or MapppedByteBuffer in Java

分散/聚集 IO

scatter/gather IO,個人認為這個看上去很酷炫,實踐中比較難使用到。

分散/聚集 IO(另一種說法是 vectored I/O 也就是向量 IO)是一種可以在單次操作中對多個緩衝區進行輸入輸出的方法,可以把多個緩衝區的數據寫到單個數據流,也可以把單個數據流讀到多個緩衝區中。

.

.

這個功能是作業系統提供的支持,Java NIO 包中已經給我們提供了操作接口 。這種操作可以提高一定的性能,因為一次操作相當於多次的線性操作,同時這也帶來了原子性的支持,因為如果用多線程來操作的話,可能存在對同一文件的操作競爭。

非阻塞 IO

相信讀者在很多地方都看到過說 NIO 其實不是代表 New IO,而是 Non-Blocking IO,我們這裡不糾結這個。我想之所以會有這個說法,是因為在 Java 1.4 第一次推出 NIO 的時候,提供了 Non-Blocking IO 的支持。

在理解非阻塞 IO 前,我們首先要明白,它的對立面 阻塞模式為什麼不好。

比如說 InputStream.read 這個方法,一旦某個線程調用這個方法,那麼就將一直阻塞在這裡,直到數據傳輸完畢,返回 -1,或者由於其他錯誤拋出了異常。

我們再拿 web 伺服器來說,阻塞模式的話,每個網絡連接進來,我們都需要開啟一個線程來讀取請求數據,然後到後端進行處理,處理結束後將數據寫回網絡連接,這整個流程需要一個獨立的線程來做這件事。那就意味著,一旦請求數量多了以後,需要創建大量的線程,大量的線程必然帶來創建線程、切換線程的開銷,更重要的是,要給每個線程都分配一部分內存,會使得內存迅速被消耗殆盡。我們說多線程是性能利器,但是這就是過多的線程導致系統完全消化不了了。

通常,我們可以將 IO 分為兩類:面向數據塊(block-oriented)的 IO 和面向流(stream-oriented)的 IO。比如文件的讀寫就是面向數據塊的,讀取鍵盤輸入或往網絡中寫入數據就是面向流的。

注意,這節混著用了流和通道這兩個詞,提出來這點是希望不會對讀者產生困擾。

面向流的 IO 往往是比較慢的,如網絡速度比較慢、需要一直等待用戶新的輸入等。

這個時候,我們可以用一個線程來處理多個流,讓這個線程負責一直輪詢這些流的狀態,當有的流有數據到來後,進行相應處理,也可以將數據交給其他子線程來處理,這個線程繼續輪詢。

問題來了,不斷地輪詢也會帶來資源浪費呀,尤其是當一個線程需要輪詢很多的數據流的時候。

現代作業系統提供了一個叫做 readiness selection 的功能,我們讓作業系統來監控一個集合中的所有的通道,當有的通道數據準備好了以後,就可以直接到這個通道獲取數據。當然,作業系統不會通知我們,但是我們去問作業系統的時候,它會知道告訴我們通道 N 已經準備好了,而不需要自己去輪詢(後面我們會看到,還要自己輪詢的 select 和 poll)。

後面我們在介紹 Java NIO 的時候會說到 Selector,對應類 java.nio.channels.Selector,這個就是 java 對 readiness selection 的支持。這樣一來,我們的一個線程就可以更加高效地管理多個通道了。

.

上面這張圖我想大家也都可能看過,就是用一個 Selector 來管理多個 Channel,實現了一個線程管理多個連接。說到底,其實就是解決了我們前面說的阻塞模式下線程創建過多的問題。

在 Java 中,繼承自 SelectableChannel 的子類就是實現了非阻塞 IO 的,我們可以看到主要有 socket IO 中的 DatagramChannel 和 SocketChannel,而 FileChannel 並沒有繼承它。所以,文件 IO 是不支持非阻塞模式的。

在系統實現上,POSIX 提供了 select 和 poll 兩種方式。它們兩個最大的區別在於持有句柄的數量上,select 最多只支持到 FD_SETSIZE(一般常見的是 1024),顯然很多場景都會超過這個數量。而 poll 我們想創建多少就創建多少。它們都有一個共同的缺點,那就是當有任務完成後,我們只能知道有幾個任務完成了,而不知道具體是哪幾個句柄,所以還需要進行一次掃描。

正是由於 select 和 poll 的不足,所以催生了以下幾個實現。BSD& OS X 中的 kqueue,Solaris 中的 /dev/poll,還有 Linux 中的 epoll。

Windows 沒有提供額外的實現,只能使用 select。

在不同的作業系統上,JDK 分別選擇相應的系統支持的非阻塞實現方式。

異步 IO

我們知道 Java 1.4 引入了 New IO,從 Java 7 開始,就不再是 New IO 了,而是 More New IO 來臨了,我們也稱之為 NIO2。

Java7 在 NIO 上帶來的最大的變化應該就屬引入了 Asynchronous IO(異步 IO)。本來吧,異步 IO 早就提上日程了,可是大佬們沒有時間完成,所以才一直拖到了 java 7 的。廢話不多說,簡單來看看異步 IO 是什麼。

要說異步 IO 是什麼,當然還得從 Non-Blocking IO 沒有解決的問題入手。非阻塞 IO 很好用,它解決了阻塞式 IO 的等待問題,但是它的缺點是需要我們去輪詢才能得到結果。

而異步 IO 可以解決這個問題,線程只需要初始化一下,提供一個回調方法,然後就可以幹其他的事情了。當數據準備好以後,系統會負責調用回調方法。

異步 IO 最主要的特點就是回調,其實回調在我們日常的代碼中也是非常常見的。

最簡單的方法就是設計一個線程池,池中的線程負責完成一個個阻塞式的操作,一旦一個操作完成,那麼就調用回調方法。比如 web 伺服器中,我們前面已經說過不能每來一個請求就新開一個線程,我們可以設計一個線程池,在線程池外用一個線程來接收請求,然後將要完成的任務交給線程池中的線程並提供一個回調方法,這樣這個線程就可以去幹其他的事情了,如繼續處理其他的請求。等任務完成後,池中的線程就可以調用回調方法進行通知了。

另外一種方式就是自己不設計線程池,讓作業系統幫我們實現。流程也是基本一樣的,提供給作業系統回調方法,然後就可以幹其他事情了,等操作完成後,作業系統會負責回調。這種方式的缺點就是依賴於作業系統的具體實現,不過也有它的一些優勢。

首先,我們自己設計處理任務的線程池的話,我們需要掌握好線程池的大小,不能太大,也不能太小,這往往需要憑我們的經驗;其次,讓作業系統來做這件事情的話,作業系統可以在一些場景中幫助我們優化性能,如文件 IO 過程中幫助更快找到需要的數據。

作業系統對異步 IO 的實現也有很多種方式,主要有以下 3 中:

Linux AIO:由 Linux 內核提供支持POSIX AIO:Linux,Mac OS X(現在該叫 Mac OS 了),BSD,solaris 等都支持,在 Linux 中是通過 glibc 來提供支持的。Windows:提供了一個叫做 completion ports 的機制。

這篇文章 asynchronous disk I/O 的作者表示,在類 unix 的幾個系統實現中,限制太多,實現的質量太差,還不如自己用線程池進行管理異步操作。

而 Windows 系統下提供的異步 IO 的實現方式有點不一樣。它首先讓線程池中的線程去自旋調用 GetQueuedCompletionStatus.aspx) 方法,判斷是否就緒。然後,讓任務跑起來,但是需要提供特定的參數來告訴執行任務的線程,讓線程執行完成後將結果通知到線程池中。一旦任務完成,作業系統會將線程池中阻塞在 GetQueuedCompletionStatus 方法的線程喚醒,讓其進行後續的結果處理。

Windows 智能地喚醒那些執行 GetQueuedCompletionStatus 方法的線程,以讓線程池中活躍的線程數始終保持在合理的水平。這樣就不至於創建太多的線程,降低線程切換的開銷。

Java 7 在異步 IO 的實現上,如果是 Linux 或者其他類 Unix 系統上,是採用自建線程池實現的,如果是 Windows 系統上,是採用系統提供的 completion ports 來實現的。

所以,在非阻塞 IO 和異步 IO 之間,我們應該怎麼選擇呢?

如果是文件 IO,我們沒得選,只能選擇異步 IO。

如果是 Socket IO,在類 unix 系統下我們應該選擇使用非阻塞 IO,Netty 是基於非阻塞模式的;在 Windows 中我們應該使用異步 IO。

當然了,Java 的存在就是為了實現平臺無關化,所以,其實不需要我們選擇,了解這些權當讓自己漲點知識吧。

總結

和其他幾篇文章一樣,也沒什麼好總結的,要說的都在文中了,希望讀者能學到點東西吧。

如果哪裡說得不對了,我想也是正常的,我這些年寫的都是 Java,對於底層了解得愈發的少了,所以如果讀者發現有什麼不合理的內容,非常希望讀者可以提出來。

相關焦點

  • aio-enhance v1.0.2 發布,Java AIO 內核增強類庫
    lt;/artifactId> <version>1.0.2</version></dependency>步驟二:啟動可以通過硬編碼的方式設置系統屬性,如下:System.setProperty("java.nio.channels.spi.AsynchronousChannelProvider
  • Java基礎學習:java中的基本數據類型
    +=運算符是java語言規定的,編譯器會對它進行識別處理,因此可以正確編譯。 三、Float和Dubble 1、基礎概念
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。但是java是一門面向對象的高級語言,它不僅語法非常複雜,抽象程度也非常高,並不能直接運行在計算機硬體機器上。 Java虛擬機(Java Virtual Machine 簡稱JVM)是運行所有Java程序的抽象計算機,是Java語言的運行環境。
  • 零基礎java入門教程函數function實例化格式案例void返回值說明
    java基礎自學入門:函數:定義在類中的具有特定功能的一段獨立小程序有時候我們函數也稱為方法,平時我們聽到的函數也就是方法,方法也是函數,每個人的叫法不同,所以這裡要切記。老程式設計師別露餡喲。傳遞給形參的具體數值;return:用於結束函數;返回值:該值會返回給調用者;java
  • Java基礎,類的繼承,粗略的理解希望對大家有所幫助!
    Java基礎:繼承讀者老爺們大家好,今天圖圖帶大家了解一下java基礎裡的繼承。首先確定一下我們今天的學習的三個目標。第一個是類的繼承;第二個是訪問控制權限;第三個方法重寫(覆蓋)override。好滴,話不多說,讓我們快樂的投身知識的海洋裡吧!
  • Java基礎教程:java反射機制教程
    這時候java語言在設計的時候為我們提供了一個機制,就是反射機制,他能夠很方便的去解決我們的問題。 二、深入分析java反射機制 1、獲取Class類 在java中萬事萬物皆對象,Useruser=newUser()一行代碼我們知道了user是User類的實例對象,通過Studentstu=newStudent()我們知道了
  • 提升java編程性能優化知識 程式設計師必看這幾點
    對於學習java的學子也是如此,那麼java程式設計師如何提高編程性能呢,有哪些小知識或者技巧呢,怎麼樣才能在編程性能優化方面有所提升呢?  1.儘量在合適的場合使用單例  使用單例可以減輕加載的負擔,縮短加載的時間,提高加載的效率,但並不是所有地方都適用於單例,簡單來說,單例主要適用於以下三個方面:
  • Java面試高頻考點:反射機制使用大全
    例如Spring的IOC實現機制,其底層都是依賴於java的反射機制,因此,這是一個非常重要的知識點。對於初學java的同學來說,掌握其使用方法很有必要。什麼是java中的反射?反射機制操作屬性有些同學會疑問,這個代碼看起來更加複雜,的確,反射機制的一系列操作會消耗一定的資源,如果不需要動態地創建一個對象就不要用反射,在一些基礎的代碼框架或業務上
  • 開發崗位這麼多,為什麼選Java?你學Java了嗎-開課吧
    其他程式語言與Java相比,Java語法相對簡單,並且是很多計算機語言的基礎。提到C++語言,很多人發現在使用過程中最容易出現的錯誤就是內存管理,而java有自動垃圾回收器,不用擔心內存。java工程師工資一般多少?java自學容易嗎?
  • JAVA反序列化—FastJson抗爭的一生
    本文需要前置知識:JNDI注入,7u21利用鏈,可以戳我往期的文章。文章內容如下:1.fastjson組件基礎介紹及使用(三種反序列化形式等)2.fastjson組件的@type標識的特性說明(默認調用setter、getter方法條件等)。
  • Java基礎-instanceof用法揭秘
    main(String[] args) {Point p = new Point();//Element e = new Element();p = (Point) new Object();System.out.println(p instanceof Point);}}猛一看,沒事問題,編譯也沒有問題,可是運行時報錯:Exception in thread "main" java.lang.ClassCastException
  • 學java可以做什麼?大數據前景和就業方向又是什麼樣的呢?
    (2) Java可以做安卓軟體:安卓是一個手機系統基於Linux的作業系統,其中原始碼java,市面上所有的安卓手機,包括小米,OPPO,華為手機系統都是修改java運行的,java做安卓也不只是能做系統,對於更多的程式設計師開發人員來說,更多的時間是花在APP上,也就是手機上的各種軟體都是用java寫的。
  • Java字符串地查找操作
    代碼如下:/** * @Title: StringSearchSample.java * @Packageunit * @Description: Java基礎知識課程案例* @author編程訓練營 * @date* @versionV1.0 */ packageunit;
  • java軟體工程師的這些要求你有嗎?
    如今java是IT界數一數二的語言,許多程式設計師都想要成為java軟體工程師,那大家知道企業對於java軟體工程師的技術要求有哪些嗎?java軟體工程師的技術要求一.java技術要求:1.具有良好的Java語言基礎,面向對象編程基礎2.熟悉Struts、Hibernate、Spring等主流技術框架3.熟悉XML解析、Excel導出、文件上傳、發送E-mail等常見業務的實現二.資料庫技術要求1.掌握、運用SQLServer
  • 跟我學java編程—認識java語言的字符類型
    用記事本打開「CharSample.java」文件,輸入以下代碼:編譯「CharSample.java」文件,在命令行窗口輸入「javac CharSample.java」並執行命令,編譯通過後,在命令行窗口輸入「java CharSample」運行Java程序,命令行窗口顯示如下信息:
  • JAVA校招題基礎知識點複習第六天(一張圖搞懂所有集合特點)
    集合其實和數組一樣都是java中提供的一種容器,可以用來存儲多個數據。既然集合和數組都容器,那麼他們有什麼區別呢?1、數組的長度是固定的,集合的長度是可變的。在JAVA中,集合按照其存儲結構可以分為兩大類,分別是單列集合java.util.Collection和雙列集合java.util.Map。Collection:單列集合類的根接口,用於存儲一系列符合某種規則的元素,它有兩個重要的子接口,分別是java.util.List和java.util.Set。
  • 2020年Java基礎高頻面試題匯總
    最後一種用法就是靜態導包,即 import static.import static是在JDK 1.5之後引入的新特性,可以用來指定導入某個類中的靜態資源,並且不需要使用類名,可以直接使用資源名,比如:import static java.lang.Math.
  • 跟我學java編程—認識java的整數類型
    示例2:int類型的溢出在D盤Java目錄下,新建「OverFlow.java」文件。用記事本打開「OverFlow.java」文件,輸入以下代碼:編譯「OverFlow.java」文件,在命令行窗口輸入「javac OverFlow.java」並執行命令,編譯器顯示如下信息:編譯器給出過大的整數錯誤信息,num的數值明顯超出的int所能表示的最大值。
  • 如何使用JAVA反射/JAVA反射實例
    內容輸出,調用toString() }};3、如何使用反射包package com.test.instancedemo.instancedemo ;import java.lang.reflect.Constructor