Java集合框架全解
Collection 集合
集合接口有2个基本方法:
public interface Collection<E>
{
//向集合中添加元素。如果添加元素确实改变了集合就返回 true, 如果集合没有发生变化就返回 false
boolean add(E element);
//返回一个实现了Iterator接口的对象
Iterator<E> iterator();
}
Iterator接口包含4个方法:
public interface Iterator<E>
{
//返回将要访问的下一个对象。如果已经到达了集合的尾部,将拋出一个 NoSuchElementException。
E next();
//如果存在可访问的元素,返回 true。
boolean hasNext();
//删除上次访问的对象。这个方法必须紧跟在访问一个元素之后执行。如果上次访问之后,集合已经发生了变化,这个方法将抛出一个 IllegalStateException。
default void remove();
//为每个剩余元素执行给定的操作,直到处理完所有元素或操作引发异常为止
default void forEachRemaining(Consumer<? super E> action)
}
集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下,但支持多线程的集合类位于java.util.concurrent包下。
图片出处:冰湖一角的博客
List 表
1. LinkedList 链表
- 有头节点和尾结点的双向链表。
- 实现了
Deque
接口,可以作为双端队列。即既可以当做栈使用,也可以当做队列使用。 LinkedList
和ArrayList
一样也不是线程安全的,如果在对线程下面访问可以自己重写LinkedList,然后在需要同步的方法上面加上同步关键字synchronized
。listIterator()
方法返回一个实现了ListIterator
接口的对象。ListIterator
接口有两个方法可以用于反向遍历链表:E previous();
boolean hasPrevious();
LinkedList
的遍历方法:public class Test {
public static void main(String[] args) {
List<String> list=new LinkedList<>();
list.add("Hello");
list.add("World");
list.add("Are");
list.add("you");
list.add("OK?");
// 1. 转换到数组
String[] strArray=new String[list.size()];
list.toArray(strArray);
for(String str:strArray){
System.out.println(str);
}
// 2. foreach循环
for(String str:list){
System.out.println(str);
}
// 3. 迭代器
Iterator itr = list.iterator();
while(itr.hasNext()) {
System.out.println(itr.next());
}
}
}
2. ArrayList 动态数组
- 允许任何符合规则的元素插入甚至包括null。
- 如果在初始化
ArrayList
的时候没有指定初始化长度的话,默认的长度为10。 ArrayList
在增加新元素的时候如果超过了原始的容量的话,扩容方法public void ensureCapacity(int minCapacity)
的方案为oldCapacity * 3/2 + 1
,并保证满足参数minCapacity。ArrayList
是线程不安全的,在多线程的情况下可以使用Vector
,Vector
是同步的。Vector
是允许设置默认的增长长度,Vector的默认扩容方式为原来的2倍。ArrayList
的遍历方法:public class Test {
public static void main(String[] args) {
List<String> array=new ArrayList<String>();
array.add("Hello");
array.add("World");
array.add("Are");
array.add("you");
array.add("OK?");
// 1. 转换到数组
String[] strArray=new String[array.size()];
array.toArray(strArray);
for(String str:strArray){ //或是 for(int i=0;i<strArray.length;i++)
System.out.println(str);
}
// 2. foreach循环
for(String str:array){
System.out.println(str);
}
// 3. 迭代器
Iterator itr = array.iterator();
while(itr.hasNext()) {
System.out.println(itr.next());
}
}
}
Set 集
1. HashSet 无序散列表
- 集不允许重复元素。判断依据是
equals()
。 - 实现了一个哈希表。内部基于
HashMap
实现,因为Map里的Key不允许重复,天然就是一个Set。 - “如果存放自定义类,就要负责实现这个类的
hashCode()
方法。注意,自己实现的hashCode()
方法应该与equals()
方法兼容,即如果a.equals(b)为true,a与b必须具有相同的散列码。”(出自《Java核心技术卷一》P.363)
“HashSet集合判断两个元素相等的标准是(1)两个对象通过equals()方法比较返回true;(2)两个对象的hashCode()方法返回值相等。因此,如果(1)和(2)有一个不满足条件,则认为这两个对象不相等,可以添加成功。”(出自冰湖一角的博客)
验证代码:public class Test {
public static void main(String[] args) {
Set<A> test = new HashSet<A>();
A a1 = new A(9);
A a2 = new A(19);
System.out.println(a1.equals(a2)); // true
System.out.println(a1.hashCode()==a2.hashCode());// false
test.add(a1);
test.add(a2);
System.out.println(test.size());// 2
}
}
- 如果两个对象的
hashCode()
方法返回值相等,但是两个对象通过equals()
方法比较返回false,HashSet
会以链式结构将两个对象保存在同一位置,这将导致性能下降,因此在编码时应避免出现这种情况。 HashSet
中仅允许存入一个null
值。HashSet
不是线程同步的。HashSet
的遍历方法:public class Test {
public static void main(String[] args) {
Set<String> hs=new HashSet<String>();
hs.add("Hello");
hs.add("World");
hs.add("Are");
hs.add("you");
hs.add("OK?");
// 1. 转换到数组
String[] strArray=new String[hs.size()];
hs.toArray(strArray);
for(String str:strArray){ //或是 for(int i=0;i<strArray.length;i++)
System.out.println(str);
}
// 2. foreach循环
for(String str:hs){
System.out.println(str);
}
// 3. 迭代器
Iterator itr = hs.iterator();
while(itr.hasNext()) {
System.out.println(itr.next());
}
}
}
2. LinkedHashSet
- 扩展(extends)了HashSet类。记住元素项的插入顺序。这样就可以避免在散列表中的项从表面上看是随机排列的。当条目插入到表中时,就会并入到双向链表中,如下图所示。由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时由很好的性能。
(访问顺序可用于实现高速缓存的“ 最近最少使用” 原则,此处不详细展开。)
- 内部基于LinkedHashMap实现。在父类
HashSet
中,专为LinkedHashSet
提供的构造方法如下,该方法为包访问权限,并未对外公开。(出自smile2it的博客)/**
* 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。
* 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。
*
* 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。
* @param initialCapacity 初始容量。
* @param loadFactor 加载因子。
* @param dummy 标记。
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}
3. TreeSet 有序树集
TreeSet
内部基于TreeMap
实现,原因前面讲过了。而TreeMap
采用红黑树的数据结构来存储集合元素。- 元素的排序方法为:①默认是自然排序;②构造函数传入实现了Comparator接口的比较器(该比较器重写
int compare(T o1,T o2)
)。
第①种情况下:所有插入到TreeSet
的元素必须实现Comparable
接口(实现了int compareTo(Object obj)
方法),否则会抛出java.lang.ClassCastException
异常;TreeSet
会调用集合元素的compareTo
方法来比较元素的大小关系(如果返回0则相等),然后将元素按照升序排列。 - Java常用类中已经实现了Comparable接口的类有以下几个:
- BigDecimal、BigDecimal以及所有数值型对应的包装类:按照它们对应的数值大小进行比较。
- Charchter:按照字符的unicode值进行比较。
- Boolean:true对应的包装类实例大于false对应的包装类实例。
- String:按照字符串中的字符的unicode值进行比较。
- Date、Time:后面的时间、日期比前面的时间、日期大。
TreeSet
的遍历方法:同HashSet
。
4. EnumSet 枚举集
EmimSet
是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet
内部用位序列实现。如果对应的值在集中,则相应的位被置为 1。EnumSet
的集合元素也是有序的,EnumSet
以枚举值在Enum类内的定义顺序来决定集合元素的顺序。EnumSet
集合不允许加入null元素,如果试图插入null元素,EnumSet
将抛出NullPointerException
异常。EnumSet
类没有公共的构造器。可以使用静态工厂方法构造这个集:enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
EnumSet<Weekday> always = EnumSet.allOf(Weekday.class);
EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class);
EnumSet<Weekday> workday = EnumSet.range(Weekday.MONDAY, Weekday.FRIDAY);
EnumSet<Weekday> mwf = EnumSet.of(Weekday.MONDAY, Weekday.WEDNESDAY, Weekday.FRIDAY);
- 可以使用 Set 接口的常用方法来修改 EnumSet。
Queue 队列
Throws exception | Returns special value | |
Insert | add(e) |
offer(e) |
Remove | remove() |
poll() |
Examine | element() |
peek() |
1. Deque 双端队列
Deque
支持同时从两端添加或移除元素,因此Deque
接口的实现可以被当作FIFO队列使用,也可以当作LIFO队列(栈)来使用。官方也是推荐使用 Deque 的实现来替代 Stack。LinkedList
是双端队列的链表实现。ArrayDeque
是双端队列的可变数组实现。ArrayDeque
没有容量限制,可根据需求自动进行扩容。ArrayDeque
不支持值为null
的元素。ArrayDeque
当做栈使用时可能比Stack
要快,当做队列使用时可能比LinkedList
要快。其大部分操作都是平摊常数时间。ArrayDeque
的初始容量:- 默认是16;
- 构造函数传入
int numElements
,如果numElements≤8,则初始容量为8;如果numElements>8,则初始容量为大于8的某个2^n; - 构造函数传入
Collection<? extends E> c
,初始容量为≥c.size()
的某个2^n。
ArrayDeque
有三个重要数据域:Object[] elements
int head
int tail
- elements: 循环数组,大小总会是 2^n;
- head: 双端队列的头,出队或出栈时的元素位置,加入双端队列头端元素位置,表示当前头元素位置,初始为0;
- tail: 双端队列尾的下一个,入队或进栈时的元素位置,加入双端队列尾端的下个元素的索引,tail位总是空的,初始为0;
- isEmpty() 判空条件:head == tail;
- (tail = (tail + 1) & (elements.length - 1)) == head 时为满。满时自动容量翻倍,并重新组织元素到新数组头部。
理解:(出自Kyrie lrving 的博客)
length = 2^n,二进制表示为:第 n 位为1,低位 (n-1位) 全为0;
length - 1 = 2^n-1,二进制表示为:低位(n-1位)全为1;
如果 tail + 1 <= length - 1,则 位与 后低 (n-1) 位保持不变,高位全为0,此时tail和head都在数组中部相遇;
如果 tail + 1 = length,则位与后低 n 全为0,高位也全为0,结果为 0,此时tail和head都在数组下标0处相遇。
- 作为栈使用:
void push(E e)
,E pop()
,E peek()
- 作为队列使用:
boolean offer(E e)
,E poll()
,E peek()
boolean add(E e)
,E remove()
,E element()
Deque
方法总结与对比。
First Element (Head) | Last Element (Tail) | |||
Throws exception | Special value | Throws exception | Special value | |
Insert | addFirst(e) |
offerFirst(e) |
addLast(e) |
offerLast(e) |
Remove | removeFirst() |
pollFirst() |
removeLast() |
pollLast() |
Examine | getFirst() |
peekFirst() |
getLast() |
peekLast() |
Queue Method |
Equivalent Deque Method |
add(e) |
addLast(e) |
offer(e) |
offerLast(e) |
remove() |
removeFirst() |
poll() |
pollFirst() |
element() |
getFirst() |
peek() |
peekFirst() |
Stack Method | Equivalent Deque Method |
push(e) |
addFirst(e) |
pop() |
removeFirst() |
peek() |
peekFirst() |
2. PriorityQueue 优先队列
- 基于priority heap,也就是说可以当做最小堆或最大堆来使用。
- 将堆(完全二叉树)存于数组内。默认的Capacity为11。
- 不允许
null
。 - 默认自然排序,或者传入Comparator。(前面在
TreeSet
已经介绍过) - 结构破坏时,调用私有方法
siftUp()
和siftDown()
维护结构。 PriorityQueue
的peek()
和element()
操作是常数时间,add()
、offer()
、无参数的remove()
以及poll()
方法的时间复杂度都是log(N)。
Map 映射
Map是集合框架的一部分,但不继承Collection接口。
1. HashMap
- 默认情况下:初始容量为16,负载因子为0.75。
- 容量总会是 2^n。
- 位置索引的计算方法是哈希码(hashCode)对哈希表长求余。但是计算方法是位运算:
在length = 2^n 的情况下,有 h & (length-1) == h % length ,位运算比模运算快得多。 - 计算哈希码:
HashMap
中的条目类Node<K,V>
有自己的hashCode()方法,不过好像并未使用过。- 计算哈希值是使用
HashMap
的int hash(key)
方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
至于相关解释,可以看看这篇博客。(稍微有些晦涩,下次我整理一篇出来)
HashMap
可以允许你在列表中放一个key值为null
的元素,并且可以有任意多value为null
。而Hashtable
不允许键或者值为null
。HashMap
的遍历方法:(有一些奇奇怪怪的方法就不写了)public class Test {
public static void main(String[] args) {
String[] str = { "I love you", "You love he", "He love her", "hello" };
Map<Integer, String> m = new HashMap<Integer, String>();
for (int i = 0; i < str.length; i++) {
m.put(i, str[i]);
}
// 0. 直接打印查看结果。
System.out.println(m.toString()); // 1. 使用keySet():
// 迭代器
Set<Integer> s1 = m.keySet();
Iterator<Integer> it1 = s1.iterator();
while (it1.hasNext()) {
int key = it1.next();
System.out.println("Key = " + key + ", Value = " + m.get(key));
}
// foreach
for (Object key : m.keySet()) {
System.out.println("Key = " + key + ", Value = " + m.get(key));
} // 2. 使用entrySet():
// 迭代器
Set<Entry<Integer, String>> s2 = m.entrySet();
Iterator<Map.Entry<Integer, String>> it2 = s2.iterator();
while (it2.hasNext()) {
Map.Entry<Integer, String> entry = it2.next();
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
// foreach
for (Map.Entry<Integer, String> entry : m.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
} // 3. 使用values():
// 迭代器
Collection<String> c = m.values();
Iterator<String> it3 = c.iterator();
while (it3.hasNext()) {
System.out.println(it3.next());
}
// foreach
for(String s : m.values()) {
System.out.println(s);
}
}
}
2. TreeMap
TreeMap
是一个红黑树的数据结构,每个key-value对作为红黑树的一个节点。TreeMap
存储key-value对时,需要根据key对节点进行排序。TreeMap
实现了SortedMap
- 排序方式:
- 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则会抛出ClassCastException。
- 定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。
TreeMap
的遍历方法参考HashMap
。
3. 其他
LinkedHashMap
(链接散列映射),使用双向链表记住元素项的插入顺序。WeakHashMap
(弱散列映射),使用弱引用(weak references)保存键。解决了有关不再引用的键的垃圾回收问题。EnumMap
(枚举映射),是一个键类型为枚举类型的映射。它可以直接且高效地用一个值数组实现。在使用时,需要在构造器中指定键类型:EnumMap<Weekday, Employee> personInCharge = new EnumMap<>(Weekday.class);
IdentityHashMap
(标识散列映射)。
集合框架的遗留类
Vector
作用与ArrayList一
样,但Vector
是线程安全的。Stack
继承了Vector
类,是线程安全的栈实现。Hashtable
作用与HashMap
一样,但HashTable
是线程安全的。Properties
类(属性映射)继承了Hashtable
类,它相当于一个key、value都是String类型的Map,主要用于读取配置文件。Enumeration
接口:Hashtable
、Vector
的element()
方法产生一个用于描述表中各个枚举值的对象。
BitSet 位集
- Java平台的
BitSet
类用于存放一个位序列(它不是数学上的集,称为位向量或位数组更为合适)。 - 如果需要高效地存储位序列(例如,标志)就可以使用位集。由于位集将位包装在字节里,所以,使用位集要比使用
Boolean
对象的ArrayList
更加高效。
(个人测试,速度上:boolean[] > BitSet > ArrayList) BitSet
类提供了一个便于读取、设置或清除各个位的接口。使用这个接口可以避免屏蔽和其他麻烦的位操作。如果将这些位存储在int
或long
变量中就必须进行这些繁琐的操作。
Java集合框架全解的更多相关文章
- java集合框架详解
java集合框架详解 一.Collection和Collections直接的区别 Collection是在java.util包下面的接口,是集合框架层次的父接口.常用的继承该接口的有list和set. ...
- Java集合框架详解(全)
一.Java集合框架概述 集合可以看作是一种容器,用来存储对象信息.所有集合类都位于java.util包下,但支持多线程的集合类位于java.util.concurrent包下. 数组与集合的区别如下 ...
- Java—集合框架详解
一.描述Java集合框架 集合,在Java语言中,将一系类的对象看成一个整体. 首先查看jdk中的Collection类的源码后会发现Collection是一个接口类,其继承了java迭代接口Iter ...
- java的集合框架详解
前言:数据结构对程序设计有着深远的影响,在面向过程的C语言中,数据库结构用struct来描述,而在面向对象的编程中,数据结构是用类来描述的,并且包含有对该数据结构操作的方法. 在Java语言中,Jav ...
- 浅入深出之Java集合框架(下)
Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...
- Java IO编程全解(六)——4种I/O的对比与选型
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7804185.html 前面讲到:Java IO编程全解(五)--AIO编程 为了防止由于对一些技术概念和术语 ...
- (转)Java集合框架:HashMap
来源:朱小厮 链接:http://blog.csdn.net/u013256816/article/details/50912762 Java集合框架概述 Java集合框架无论是在工作.学习.面试中都 ...
- 【JAVA集合框架之工具类】
一.概述 JAVA集合框架中有两个很重要的工具类,一个是Collections,另一个是Arrays.分别封装了对集合的操作方法和对数组的操作方法,这些操作方法使得程序员的开发更加高效. public ...
- Java集合框架:HashMap
转载: Java集合框架:HashMap Java集合框架概述 Java集合框架无论是在工作.学习.面试中都会经常涉及到,相信各位也并不陌生,其强大也不用多说,博主最近翻阅java集合框架的源码以 ...
随机推荐
- H5播放需要解密的m3u8音频文件
<audio ref="audio"></audio> import CryptoJS from "crypto-js"; import ...
- 飞(fly)(数学推导,liu_runda的神题)
大概看了两三个小时的题解,思考量很大,实现简单........ 20分: 明显看出,每个点的贡献是x*(x-1)/2;即组合数C(x,2),从x个线段中选出2个的方案数,显然每次相交贡献为1,n^2枚 ...
- Windows10无线能连上但没有网络
解决思路:重置widows10网络配置 步骤: 1.win+X键 2.在命令提示符窗口中,输入命令行:netsh winsock reset 3.然后按下回车键,这时就会提示重置Winsock目录成功 ...
- 自定义Nuget包的技巧一二
背景: 在项目中, 通常会拆分成核心库(Core)和应用(App)两个部分.核心库由专人维护, 不同的App是不同的团队,但都引用了核心库.当核心库需要升级更新时,有的应用会更新,有的不会--可能是没 ...
- UGUI ScrollRect 鼠标滑动灵敏度调节
- OpenMVG 系列 (1):入门简介
1 OpenMVG 简介 全称 Open Multiple View Geometry,是法国人 Pierre Moulon 读博期间开源的一个 C++ 库 最早版本 OpenMVG 0.1 是 ...
- js实现文字分割动画
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>& ...
- Linux:修改Ubuntu环境变量后bash找不到
1.问题显示 修改环境变量,导致命令全部失效 2.解决办法 #使用备份bin export PATH=/usr/bin:/bin 之后使用命令就可以了. 3.找到修改的环境变量 一般修改的环境变量 ...
- awk中printf的用法
printf函数 打印输出时,可能需要指定字段间的空格数,从而把列排整齐.在print函数中使用制表符并不能保证得到想要的输出,因此,可以用printf函数来格式化特别的输出. printf函数返 ...
- XCTF simple js
思路分析: 进入靶场, 随便输入,肯定是错误的,f12看下源码,结合题目说js,把js代码单独拿出来看看. function dechiffre(pass_enc){ var pass = " ...