### 集合元素在內存如何存放
數據元素在內存中,主要有2種存儲方式:
1、順序存儲,Random Access(或直接存儲,Direct Access):
這種方式,相鄰的數據元素存放于相鄰的內存地址中,整塊內存地址是連續的。可以根據元素的位置直接計算出內存地址,直接進行讀取。讀取一個特定位置元素的平均時間復雜度為O(1)。這種數據結構插入和刪除時比較麻煩,查詢比較方便。正常來說,只有基于數組實現的集合,才有這種特性。Java中以ArrayList為代表;
2、鏈式存儲,Sequential Access:
這種方式是將數據元素放在獨立的空間中,在內存中每個元素的內存地址都不要求是相鄰的。所以每個元素中需要保存下一個元素的索引,即下一個元素的內存地址。所以這種數據結構插入和刪除比較方便,但是查找很麻煩,要從第一個開始遍歷,因為它無法根據元素的位置直接計算出內存地址,只能按順序讀取元素。讀取一個特定位置元素的平均時間復雜度為O(n)。主要以鏈表為代表。Java中以LinkedList為代表;
### Java集合遍歷方式
1)傳統的for循環遍歷基于計數器
遍歷者自己在集合外部維護一個計數器,然后依次讀取每一個位置的元素,直到讀取到最后一個元素后。主要就是需要按元素的位置來讀取元素,適合于遍歷順序存儲的數據結構;
```
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
```
2)迭代器遍歷Iterator
Iterator本來是OO的一個設計模式,主要目的就是屏蔽不同數據集合的特點,統一遍歷集合的接口。從結構上可以看出,迭代器模式在客戶與容器之間加入了迭代器角色。迭代器角色的加入,就可以很好的避免容器內部細節的暴露,而且也使得設計符號“單一職責原則”;
每一個具體實現的數據集合,一般都需要提供相應的Iterator。相比于傳統for循環,Iterator取代了顯式的遍歷計數器。所以基于順序存儲集合的Iterator可以直接按位置訪問數據。而基于鏈式存儲集合的Iterator,正常的實現都是需要保存當前遍歷的位置,然后根據當前位置來向前或者向后移動指針
迭代器模式的寫法如下:
```
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
```
3)foreach循環遍歷
foreach內部也是采用了Iterator的方式實現,根據反編譯的字節碼可以發現,foreach內部也是采用了Iterator的方式實現,只不過Java編譯器幫我們生成了這些代碼
優點:代碼簡潔,不易出錯
缺點:只能做簡單的遍歷,不能在遍歷過程中操作(刪除、替換)數據集合
```
for (ElementType element : list) {
}
```
### 遍歷性能
1)傳統的for循環遍歷基于計數器
ArrayList按位置讀取的代碼:直接按元素位置讀取
```
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
```
LinkedList按位置讀取的代碼:如果index小于容量的一半,則從頭開始查找,否則從尾部開始查找
```
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
```
2)迭代器遍歷Iterator
對于RandomAccess類型的集合來說,如ArrayList迭代器遍歷反而因為一些額外的操作,還會增加額外的運行時間;
對于Sequential Access的集合來說,因為Iterator內部維護了當前遍歷的位置,所以每次遍歷,讀取下一個位置并不需要從集合的第一個元素開始查找,只要把指針向后移一位就行了,這樣一來遍歷整個集合的時間復雜度就降低為O(n);以LinkedList的迭代器為例,其內部實現,就是維護當前遍歷的位置,然后操作指針移動就可以了
3)foreach循環遍歷
分析Java字節碼可知,foreach內部實現原理是通過Iterator實現的,只不過這個Iterator是Java編譯器幫我們生成的,所以我們不需要再手動去編寫。但是因為每次都要做類型轉換檢查,所以花費的時間比Iterator略長,而時間復雜度和Iterator是一樣
### 使用場景
1)傳統的for循環遍歷基于計數器
適用于遍歷順序存儲集合,讀取性能比較高如ArrayList;不適用于遍歷鏈式存儲的集合如LinkedList,時間復雜度太大;
2)迭代器遍歷Iterator
對于順序存儲的數據結構,如果不是太在意時間,推薦選擇此方式,畢竟代碼更加簡潔,也防止了Off-By-One的問題。
鏈式存儲:推薦此種遍歷方式,平均時間復雜度降為O(n)
3)foreach循環遍歷
foreach讓代碼更加簡潔,缺點就是遍歷過程中不能操作數據集合(刪除等),所以有些場合不使用。而且它本身就是基于Iterator實現的,但是由于類型轉換的問題,所以會比直接使用Iterator慢一點,性能上相差不大
- java演變
- JDK各個版本的新特性
- JDK1.5新特性
- JDK1.6新特性
- JDK1.7新特性
- JDK1.8新特性
- JAVA基礎
- 面向對象特性
- 多態
- 方法重載
- 方法重寫
- class
- 常量
- 訪問修飾符
- 類加載路徑
- java-equals
- 局部類
- java-hashCode
- Java類初始化順序
- java-clone方法
- JAVA對象實例化的方法
- 基礎部分
- JAVA基礎特性
- JAVA關鍵字
- javabean
- static
- 日期相關
- final
- interface
- 函數式接口
- JAVA異常
- 異常屏蔽
- try-with-resource資源泄露
- JAVA引用
- WeakReference
- SoftReference
- PhantomReference
- 位運算符
- try-with-resource語法糖
- JDK冷知識
- JAVA包裝類
- JAVA基本類型與包裝類
- java.lang.Boolean
- java.lang.Integer
- java.lang.Byte
- java.lang.Short
- java.lang.Long
- java.lang.Float
- java.lang.Double
- java.lang.Character
- 日期相關
- TemporalAdjusters
- String
- 字符串常量池
- String拼接
- String編譯期優化
- StringBuilder&StringBuffer
- intern
- 注解
- java標準注解
- 內置注解
- 元注解
- 自定義注解
- 注解處理器
- JVM注解
- Java8 Annotation新特性
- 反射-Reflective
- Reflection
- Class
- Constructor
- Method
- javabean-property
- MethodHandles
- 泛型
- 類型擦除
- bridge-method
- Accessor&Mutator方法
- enum
- JAVA數組
- finalize方法
- JAR文件
- JAVA高級編程
- CORBA
- JMX
- SPI
- Java SPI使用約定
- ServiceLoader
- 實際應用
- IO
- 工具類
- JDK常用工具類
- Objects
- System
- Optional
- Throwable
- Collections
- Array
- Arrays
- System
- Unsafe
- Number
- ClassLoader
- Runtime
- Object
- Comparator
- VarHandle
- 數據結構
- 棧-Stack
- 隊列(Queue)
- Deque
- PriorityQueue
- BlockingQueue
- SynchronousQueue
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- ConcurrentLinkedQueue
- 列表
- 迭代器
- KV鍵值對數據類型
- HashMap
- TreeMap
- Hash沖突
- ConcurrentHashMap
- JDK1.7 ConcurrentHashMap結構
- jdk7&jdk8區別
- 集合
- Vector
- Stack
- HashSet
- TreeSet
- ArrayList
- LinkedList
- ArrayList && LinkedList相互轉換
- 線程安全的集合類
- 集合類遍歷性能
- 并發容器
- CopyOnWriteArrayList
- ConcurrentHashMap
- 同步容器
- BitMap
- BloomFilter
- SkipList
- 設計模式
- 設計模式六大原則
- 單例模式
- 代理模式
- 靜態代理
- 動態代理
- JDK動態代理
- cglib動態代理
- spring aop
- 策略模式
- SpringAOP策略模式的運用
- 生產者消費者模式
- 迭代器模式
- 函數式編程
- 方法引用
- 性能問題
- Lambda
- Lambda類型檢查
- Stream
- findFirst和findAny
- reduce
- 原始類型流特化
- 無限流
- 收集器
- 并行流
- AOP
- 靜態織入
- aspect
- aspect的定義
- AspectJ與SpringAOP
- 動態織入
- 靜態代理
- 動態代理
- JDK動態代理
- CGLib動態代理
- Spring AOP
- SpringAOP五種通知類型
- @Before
- @AfterReturning
- @AfterThrowing
- @After
- @Around
- Aspect優先級
- SpringAOP切點表達式
- within
- execution
- 嵌套調用
- 系統優化與重構
- 重疊構造器模式
- 工具類構造器優化
- 常見面試題
- new Object()到底占用幾個字節
- 訪問修飾符
- cloneable接口實現原理
- 異常分類以及處理機制
- wait和sleep的區別
- 數組在內存中如何分配
- 類加載為什么要使用雙親委派模式,有沒有什么場景是打破了這個模式
- 類的實例化順序
- 附錄
- JAVA術語
- FAQ
- 墨菲定律
- 康威定律
- 軟件設計原則
- 阿姆達爾定律
- 字節碼工具
- OSGI