Java 集合
数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。如下:
1:数组能存放基本数据类型和对象,而集合类存放的都是对象的引用,而非对象本身!
2:数组容易固定无法动态改变,集合类容量动态改变。
3:数组无法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()可以确切知道元素的个数
4:集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式
5:集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率
集合框架被设计来满足几个目标
• 框架需要是高性能的。基础集合(动态数组,链表,数和哈希表)是高效的。
• 框架需要允许不同的集合类型以类似的方式和高度的互操作性工作。
• 扩展或者调整集合必须是简单的。
一个集合框架是一个统一的体系结构表示和操作集合。所有的集合框架包含以下:
• 接口: 这些是代表集合的抽象数据类型。接口允许集合独立操作它们表示的细节。在面向对象的语言中,接
口通常形成一个层次结构。
• 实现,即类: 这些是集合接口的具体实施。从本质上说,它们是可重用的数据结构。
• 算法: 这些是在实现集合接口的对象上进行有用计算的方法,比如搜索和排序。算法被称为多态的,那就是
说,同一个方法能被用在许多不同的合适的集合接口的实现上。
除了集合,框架定义了几个 map 接口和类 Maps 存储键值对。尽管 maps 不是正确使用集合的术语,但是他们
完全由集合整合起来。
List 接口
List 接口为 Collection 直接接口。List 所代表的是有序的 Collection,即它用某种特定的插入顺序来维护元素顺
序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位
置)访问元素,并搜索列表中的元素。实现 List 接口的集合主要有:ArrayList、LinkedList、Vector、Stac
k。
ArrayList
ArrayList 是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括 null。每一个 Arr
ayList 都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着
增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确
public void ensureCapacity(int minCapacity) {
//修改计时器
modCount++;
//ArrayList容量大小
int oldCapacity = elementData.length;
/*
* 若当前需要的长度大于当前数组的长度时,进行扩容操 作
*/
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
//计算新的容量大小,为当前容量的1.5倍
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
//数组拷贝,生成新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
} }
所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运
行,也就是说,添加 n 个元素需要 O(n) 时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间
开销那样简单)。
ArrayList 擅长于随机访问。同时 ArrayList 是非同步的。
1)ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。
如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。
3)ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。
另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。
1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
LinkedList
同样实现 List 接口的 LinkedList 与 ArrayList 不同,ArrayList是一个动态数组,而 LinkedList 是一个双向链
表。所以它除了有 ArrayList 的基本操作方法外还额外提供了 get,remove,insert 方法在 LinkedList 的首部或尾部。
由于实现的方式不同,LinkedList 不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在 List 中进行插入和删除操作。
与 ArrayList 一样,LinkedList 也是非同步的。如果多个线程同时访问一个 List,则必须自己实现访问同步。一种解决方法是在创建 List 时构造一个同步的 List
基于链表实现的方式使得 LinkedList 在插入和删除时更优于 ArrayList,而随机访问则
比 ArrayList 逊色些。
在 LinkedList 中提供了两个基本属性 size、header。
其中 size 表示的 LinkedList 的大小,header 表示链表的表头,Entry 为节点对象。
LinkedList() 构造一个空列表。里面没有任何元素,仅仅只是将 header 节点的前一个元素、后一个元素都指向自身。
LinkedList 还提供了其他的方法:
add(int index, E element):在此列表中指定的位置插入指定的元素。
addAll(Collection<? extends E> c):添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。
addAll(int index, Collection<? extends E> c):将指定 collection 中的所有元素从指定位置开始插入此列表。
AddFirst(E e): 将指定元素插入此列表的开头。
addLast(E e): 将指定元素添加到此列表的结尾。
clear(): 从此列表中移除所有元素。
remove():获取并移除此列表的头(第一个元素)。
remove(int index):移除此列表中指定位置处的元素。
remove(Objec o):从此列表中移除首次出现的指定元素(如果存在)。
removeFirst():移除并返回此列表的第一个元素。
removeFirstOccurrence(Object o):从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
removeLast():移除并返回此列表的最后一个元素。
removeLastOccurrence(Object o):从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
get(int index):返回此列表中指定位置处的元素。
getFirst():返回此列表的第一个元素。
getLast():返回此列表的最后一个元素。
indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
Vector
Vector是矢量队列,是JDK1.0版本添加的类,他继承于AbstractList,实现了接口库List,RandomAccess,和Cloneable。
Vector实现了List,所以它能够为队列提供:增加,删除,修改,遍历等操作。
Vector实现RandomAccess接口,他使队列能够快速访问(即通过索引值就能访问得到)。
Vector实现了Cloneable接口,他使队列能够被克隆。
Vector和ArrayList不同,他的操作是线程安全的。
Stack
Stack 继承自 Vector,实现一个后进先出的堆栈。Stack 提供 5 个额外的方法使得 Vector 得以被当作堆栈使用。基本的 push 和 pop 方法,还有 peek 方法得到栈顶的元素,empty 方法测试堆栈是否为空,search 方法检测一个元素在堆栈中的位置。Stack 刚创建后是空栈。
(01) Stack实际上也是通过数组去实现的。
执行push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。
执行peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。
执行pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。
(02) Stack继承于Vector,意味着Vector拥有的属性和功能,Stack都拥有。
Set 接口
Set 是一种不包括重复元素的 Collection。它维持它自己的内部排序,所以随机访问没有任何意义。与 List 一样,它同样运行 null 的存在但是仅有一个。由于 Set 接口的特殊性,所有传入 Set 集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。实现了 Set 接口的集合有:EnumSet、HashSet、TreeSet。
EnumSet
EnumSet 是一个专为枚举设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。
EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。尤其是进行批量操作(如调用containsAll()和retainAll()方法)时,如果其参数也是EnumSet集合,则该批量操作的执行速度也非常快。
EnumSet集合不允许加入null元素,如果试图插入null元素,EnumSet将抛出NullPointerException异常。
EnumSet类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建EnumSet对象。
如果只是想判断EnumSet是否包含null元素或试图删除null元素都不会抛出异常,只是删除操作将返回false,因为没有任何null元素被删除
HashSet
HashSet 继承 AbstractSet 类,实现 Set、Cloneable、Serializable 接口。其中 AbstractSet 提供 Set 接口
的骨干实现,从而最大限度地减少了实现此接口所需的工作。Set 接口是一种不包括重复元素的 Collection,它
维持它自己的内部排序,所以随机访问没有任何意义。
TreeSet
与hashset采用hash算法决定元素的存储位置,TreeSet采用红黑树的数据结构(待跟进)来存储集合元素。
自然排序(默认情况)。
调用TreeSet的无参的构造器,并实现Comparable接口,实现里的 compareTo(Object obj)方法,来比较集合元素的大小,然后按照compareTo里的排序。
该种方法注意:
向TreeSet里添加元素时,只有第一个无须实现Comparable接口,后面的必须实现。(第二个及后面的会调用compareTo方法,与集合里的其他元素比较,要求了比较的两个元素都是一个类的实例,否则会ClassCastException)
Map 接口
Map 与 List、Set 接口不同,它是由一系列键值对组成的集合,提供了 key 到 Value 的映射。同时它也没有继承 Collection。在 Map 中它保证了 key 与 value 之间的一一对应关系。也就是说一个 key 对应一个 value,所以它不能存在相同的 key 值,当然 value 值可以相同。实现 map 的有:HashMap、TreeMap、HashTable、Properties、EnumMap。
HashMap
遍历演示:
import java.util.*;
public class Main {
public static void main(String[] args) {
HashMap< String, String> hMap =
new HashMap< String, String>();
hMap.put("1", "1st");
hMap.put("2", "2nd");
hMap.put("3", "3rd");
Collection cl = hMap.values();
Iterator itr = cl.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
HashMap在Java1.7与1.8中的区别
JDK1.7:
如果所有元素节点都在一个链表上,put/get的时间复杂度会退化到O(n)。
JDK1.8:
为了解决Hash Collision DoS攻击造成巨大的开销,即使所有元素节点都在一个链表上(由于引入了红黑树),时间复杂度最差只有O(log n),但是需要key的对象正确的实现了Compare接口。
TreeMap
它存储类似于HashMap的键值对。
它只允许不同的键。无法复制密钥。
它不能有null密钥但可以有多个null值。
它按排序顺序(自然顺序)或按Comparator地图创建时提供的密钥存储密钥。
它提供了有保证的log(n)的时间成本,为containsKey,get,put和remove操作。
它不同步。用于Collections.synchronizedSortedMap(new TreeMap())在并发环境中工作。
该iterator方法返回的迭代器是快速失败的。
TreeMap方法:
void clear():它从地图中删除所有键值对。
void size():返回此映射中存在的键值对的数量。
void isEmpty():如果此映射不包含键 - 值映射,则返回true。
boolean containsKey(Object key):'true’如果地图中存在指定的键,则返回。
布尔的containsValue(对象键):它返回’true’如果一个指定的值被映射到地图上的至少一个键。
Object get(Object key):它检索value指定的映射key,如果此映射不包含键的映射,则返回null。
Object remove(Object key):它从地图中删除指定键的键值对(如果存在)。
比较器比较器():它返回用于对此映射中的键进行排序的比较器,如果此映射使用其键的自然顺序,则返回null。
Object firstKey():它返回树映射中当前的第一个(最少)键。
Object lastKey():它返回树映射中当前的最后一个(最大)键。
Object ceilingKey(Object key):返回大于或等于给定键的最小键,如果没有这样的键则返回null。
Object higherKey(Object key):返回严格大于指定键的最小键。
NavigableMap descendingMap():它返回此映射中包含的映射的逆序视图。
HashTable
也是以哈希表数据结构实现的,解决冲突时与 HashMap 也一样也是采用了散列链表的形式,不过性能比 HashMap 要低