数组是java语言内置的数据类型,他是一个线性的序列,所有可以快速访问其他的元素,数组和其他语言不同,当你创建了一个数组时,他的容量是不变的,而且在生命周期也是不能改变的,还有JAVA数组会做边界检查,如果发现有越界现象,会报RuntimeException异常错误,当然检查边界会以效率为代价。
JAVA还提供其他集合,list,map,set,他们处理对象的时候就好像这些对象没有自己的类型一样,而是直接归根于Object,这样只需要创建一个集合,把对象放进去,取出时转换成自己的类型就行了。
一、集合的由来
数组长度是固定,当添加的元素超过了数组的长度时需要对数组重新定义,太麻烦,java内部给我们提供了集合类,能存储任意对象,长度是可以改变的,随着元素的增加而增加,随着元素的减少而减少
二、数组和集合的区别
三、数组和集合什么时候用
1,如果元素个数是固定的推荐用数组
2,如果元素个数不是固定的推荐用集合
补充说明:
1.所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
2. 集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
3. 抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
4. 实现类:8个实现类(实线表示),对接口的具体实现。
5. Collection 接口是一组允许重复的对象。
6. Set 接口继承 Collection,集合元素不重复。
7. List 接口继承 Collection,允许重复,维护元素插入顺序。
8. Map接口是键-值对象,与Collection接口没有什么关系。
9.Set、List和Map可以看做集合的三大类:
List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。
├List (有序集合,允许相同元素和null)
│├LinkedList (非同步,允许相同元素和null,遍历效率低插入和删除效率高)
│├ArrayList (非同步,允许相同元素和null,实现了动态大小的数组,遍历效率高,用的多)
│└Vector(同步,允许相同元素和null,效率低)
│ └Stack(继承自Vector,实现一个后进先出的堆栈)
└Set (无序集合,不允许相同元素,最多有一个null元素)
|-HashSet(无序集合,不允许相同元素,最多有一个null元素)
|-ThreeSet(一般用于排序,需要使用比较器,compare)
Map (没有实现collection接口,key不能重复,value可以重复,一个key映射一个value)
├Hashtable (实现Map接口,同步,不允许null作为key和value,用自定义的类当作key的话要复写hashCode和eques方法,)
├HashMap (实现Map接口,非同步,允许null作为key和value,用的多)
|-ThreeMap(一般用来排序,需要使用比较器compare,内部使用的二叉树)
└WeakHashMap(实现Map接口)
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
由Collection接口派生的两个接口是List和Set。
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
LinkedList实现了List接口,允许null元素。LinkenList底层采用了双向链表来存储数据,每个节点都存储着上一个节点和下一个节点的地址以及本节点的数据。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意:LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList底层采用动态数组的存储方式,便利效率非常高,ArrayList是线程不安全的。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
ArrayList:
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高。
Vector:
底层数据结构是数组,查询快,增删慢。
线程安全,效率低。
Vector相对ArrayList查询慢(线程安全的)
Vector相对LinkedList增删慢(数组结构)
LinkedList:
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高。
Vector和ArrayList的区别
Vector是线程安全的,效率低
ArrayList是线程不安全的,效率高
共同点:都是数组实现的
ArrayList和LinkedList的区别
ArrayList底层是数组结果,查询和修改快
LinkedList底层是链表结构的,增和删比较快,查询和修改比较慢
共同点:都是线程不安全的
查询多用ArrayList
增删多用LinkedList
如果都多ArrayList
Set是一种无序的并且不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,因为HashSet的底层实现是HashMap,但是HashSet只使用了HashMap的key来存取数据所以HashSet存的数据不能重复。
1.HashSet原理
*我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了 去重复的效率, 降低了使用equals()方法的次数
* 当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象
* 如果没有哈希值相同的对象就直接存入集合
* 如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存
2.将自定义类的对象存入HashSet去重复
* 类中必须重写hashCode()和equals()方法
* hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
* equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储
* 1.List
* a.普通for循环, 使用get()逐个获取
* b.调用iterator()方法得到Iterator, 使用hasNext()和next()方法
* c.增强for循环, 只要可以使用Iterator的类都可以用
* d.Vector集合可以使用Enumeration的hasMoreElements()和nextElement()方法
* 2.Set
* a.调用iterator()方法得到Iterator, 使用hasNext()和next()方法
* b.增强for循环, 只要可以使用Iterator的类都可以用
* 3.普通for循环,迭代器,增强for循环是否可以在遍历的过程中删除
Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Map接口和Collection接口的不同
* Map是双列的,Collection是单列的
* Map的键唯一,Collection的子体系Set是唯一的
* Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针对元素有效
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key,但是将HashMap视为Collection时(values()方法可返回Collection)。
WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。Hashtable是同步的。
* Arrays工具类的asList()方法的使用
* Collections中toArray(T[] a)泛型版的集合转数组,如二叉搜索、排序、洗牌(shuffling)、逆序(reverse)等
给角色设置权限的一段经典代码,使用org.apache.commons.collections4.CollectionUtils的工具类:
/**
* 给角色设置权限
*
* @param roleId
* @param permissionIds
*/
@Transactional
@Override
public void setPermissionToRole(Long roleId, Set permissionIds) {
SysRole sysRole = sysRoleDao.findById(roleId);
if (sysRole == null) {
throw new IllegalArgumentException("角色不存在");
}
// 查出角色对应的old权限
Set oldPermissionIds = rolePermissionDao.findPermissionsByRoleIds(Sets.newHashSet(roleId)).stream()
.map(p -> p.getId()).collect(Collectors.toSet());
// 需要添加的权限
Collection addPermissionIds = org.apache.commons.collections4.CollectionUtils.subtract(permissionIds,
oldPermissionIds);
if (!CollectionUtils.isEmpty(addPermissionIds)) {
addPermissionIds.forEach(permissionId -> {
rolePermissionDao.saveRolePermission(roleId, permissionId);
});
}
// 需要移除的权限
Collection deletePermissionIds = org.apache.commons.collections4.CollectionUtils
.subtract(oldPermissionIds, permissionIds);
if (!CollectionUtils.isEmpty(deletePermissionIds)) {
deletePermissionIds.forEach(permissionId -> {
rolePermissionDao.deleteRolePermission(roleId, permissionId);
});
}
log.info("给角色id:{},分配权限:{}", roleId, permissionIds);
}
1.如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
2.如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
3.要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
4.尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
Collection 接口将一组对象作为它的元素。然而元素是由具体的实现类来维护的。例如,List允许重复元素,而其他的实现类如Set并不允许这样做。许多 Collection接口的实现类都有一个public的clone()方法。但不是所有的实现类都包含此方法。因为Collection接口是一种抽象 的表示方法,表示的是一种契约、约定,对外暴露的“服务”,具体的实现类才有意义。对于cloning和serializing这些语义性的方法只有放到 具体的实现类中才有意义,也就是说,具体的实现类才能决定它是否应该被clone或serialized,甚至是它能否被clone或 serialized。
因此强制在所有的实现类中实现clone和serialization都会让灵活性降低并产生更多的局限性。具体的实现类才能决定它是否可以被clone或serialized。
尽管Map接口及其实现类是Collection框架的一部分,但是要注意的是Map不是集合,集合也不是Map。因此Map继承Collection接口是无意义的,反之亦然。
如果Map接口继承了Collection接口,那么元素如何存放呢?Map保存的是键值对,提供了取得所有key或value集合的方法,但它体现不了“元素的集合”这种概念。
JDK1.8官方文档
An iterator over a collection. Iterator takes the place of Enumeration in the Java Collections Framework. Iterators differ from enumerations in two ways:
Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
Method names have been improved.
Iterator接口提供了用于遍历集合的方法。可以在集合对象上调用iterator()方法来获取一个迭代器的实例。Java集合框架中,Iterator接口取代了Enumeration接口,并且,迭代器允许在进行遍历时移除元素。
Enumeration的速度是Iterator的2倍并且消耗较少的内存, 它很简单,适合在基本需求(basic needs)下使用。但是相对于Enumeration,Iterator更安全,因为在对集合进行遍历时会阻止其他线程对集合进行修改。
Java集合框架中, Iterator取代了Enumeration。Iterator允许从集合中移除元素,而这对Enumeration来说是不可能的。而且Iterator接口中的方法名清晰明了,一看就知道是干什么用的。
如果迭代器无法保证迭代的顺序,那么这种说法是不清晰的(unclear)。注意的是,ListIterator就提供了add()方法,因为它能保证迭代的顺序。
这可以在当前的Iterator接口之上进行实现,但是由于这种使用情况很少,所以把它包含在接口中几乎是无意义的,想要这种功能,自己实现即可。
有两种方式:使用迭代器或用for-each循环。
List strList = new ArrayList();
//using for-each loop
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
使用iterator 是线程安全的,因为它会确保在遍历时集合没有被修改,否则会抛出ConcurrentModificationException异常。
Iterator 的快速报错机制会在每次取下一个元素时检查集合是否发生了修改。若发现有修改,那么就抛出 ConcurrentModificationException异常。除了像 ConcurrentHashMap,CopyOnWriteArrayList这样的并发集合类,所有集合类的迭代器都是按fail-fast这种机制 进行实现的
terator的fail- safe是对集合在进行clone时提供的一种保护机制,因此集合发生了修改不会引起任何问题。从设计上来说,java.util包中的集合类都是 fail-fast机制的,而java.util.concurrent包中的集合类都是fail-safe的。Fail-fast机制实现的 iterator会抛出ConcurrentModificationException异常,而fail-safe机制的iterator永远不会抛出 ConcurrentModificationException异常。
可以使用并发集合类来遍历集合从而避免ConcurrentModificationException异常。例如, 使用CopyOnWriteArrayList 来替代 ArrayList.
Iterator 接口只是用于声明迭代集合的方法,但它的实现是依赖于具体的类的。 每个集合类都通过嵌套类的方式实现了自己的Iterator接口从而返回用于遍历的iterator对象。这样可以让集合类自己选择迭代器的实现是 fail-fast还是fail-safe机制。例如,ArrayList的迭代器就是fail-fast,而CopyOnWriteArrayList 的迭代器则是fail-safe的。
UnsupportedOperationException 一种用于指示当前对象不支持调用某种方法的异常。JDK中广泛地使用了这种异常,在集合框架中,如果是通过 java.util.Collections.UnmodifiableCollection()方法创建的集合,那么对集合对象调用add()或 remove()方法都会抛出此异常。
HashMap 会调用key对象的hashCode() 和equals() 方法来决定”键值对”的索引位置。当从HashMap中取值时这两个方法也会被调用。如果这两个方法不能正确地实现的话,那么对于两个不同的 key,hashCode()和equals()方法也许会产生相同的的输出,从而不会将它们存放到不同的位置了,HashMap将它们看作一样的,因此 一个对象会被另一个覆盖。同理,其他哪些不允许存放重复元素的集合类都回调用hashCode()和equals()方法来判断元素是否重复,因此,正确 地实现这两个方法是非常重要的。要实现它们,请遵循以下原则:
If o1.equals(o2), 那么o1.hashCode() == o2.hashCode()总为真。
If o1.hashCode() == o2.hashCode()为真, 并不意味着 o1.equals(o2)也为真。
Map允许使用的任意的类型作为 Key,然而在使用前请仔细考虑以下方面:
HashMap 和 Hashtable 都实现了Map接口,看起来很相似,但它们之间有如下的区别:
对 于插入、删除、定位元素频繁的操作,HashMap提供了最好的效率。如果想要按key的排序来遍历,那么TreeMap是不二选择。某些情况下,依赖集 合的大小,先向HashMap中添加元素,然后转换为TreeMap再按key的排序进行遍历也许会带来效率上的提高。
ArrayList, HashMap, TreeMap, Hashtable 类都具有随机访问元素的特性。
java.util.EnumSet 是对Set接口的一种实现,使用了枚举(enum)类型。 All of the elements in an enum set must come from a single enum type that is specified, explicitly or implicitly, when the set is created. EnumSet不是同步的,null不允许添加进去。它也提供了一些有用的方法,如copyOf(Collection c), of(E first, E… rest) 和 complementOf(EnumSet s)。
Vector, Hashtable, Properties and Stack都是同步的(synchronized)类,因此是线程安全的,可以在多线程环境下使用. Java 1.5 Concurrent API 引入了一些允许在迭代过程中可以修改集合的并发类,由于它们会在迭代时会clone一份集合,所以在多线程环境下也是安全的。
Java 1.5 Concurrent包(java.util.concurrent) 引入了线程安全的集合类,允许在迭代时对集合进行修改。这些类的迭代器都是按fail-fast机制进行实现的,会抛出 ConcurrentModificationException异常。常见的类有 CopyOnWriteArrayList,ConcurrentHashMap,CopyOnWriteArraySet等等。
java.util.concurrent.BlockingQueue 是Queue接口的子接口,支持获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。BlockingQueue接口是java集合框架的一部 分,它主要用于处理生产者、消费者问题。对于生产者不必再关心是否还有存储空间,对于消费者不必再关心是否有对象可以消费,所有这些细节都由具体的实现类 处理了。BlockingQueue接口有几个实现类,如ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue等等。
Queue 和Stack 都是用于存储临时数据的。java.util.Queue是一个接口,其实现类大多位于concurrent包中。 Queue 可以按FIFO的顺序取回元素,但并不是所有的queue都遵循这个原则。如Deque接口就可以从队列的两端访问元素。
Stack 与 queue 类似,但它是按LIFO的顺序访问元素。
Stack是一个类,继承了Vector类,而Queue只是一个接口。
java.util.Collections 是一个工具类(utility),有许多用于操作、返回集合的静态方法。 它包含了对集合操作的各种算法,其中较常见的是“包装器(wrappers)”,可以根据指定的集合返回一个新集合,还有许多其他的一些零碎的用法这里不 再赘述。这个类实现了许多针对集合操作的算法,如二叉搜索、排序、洗牌(shuffling)、逆序(reverse)等等
如果想要调用Array或Collection类的排序方法,那么任何自定义类都需要实现Comparable接口。排序方法会调用Comparable 接口中的compareTo(T obj) 方法。我们要重载此方法,以返回负整数,0,正整数来表示当前对象(this)是否小于,等于或大于传进来的对象。但是,在实际的应用中,我们通常有基于 不同的参数进行排序的需求。例如,CEO想要按职工的薪水进行排序,而HR希望按年龄进行排序。在这种情况下,我们就需要使用Comparator接口 了,因为Comparable接口的compareTo(Object o)方法的实现通常都是基于一个字段(field),无法对其他的属性进行排序。而Comparator接口的compare(Object o1, Object o2)方法接收两个Object类型的参数,因此在实现此方法时可以根据不同的属性进行实现,返回负整数,0,正整数来表示第一个对象小于、等于或大于第 二个对象。
Comparable 和 Comparator 接口都用于对集合或数组内的对象进行排序。Comparable 接口提供了对对象的自然排序(natural sorting)方法,我们也可以基于单一的逻辑(single logic)实现排序方法。
Comparator 接口用于提供普通的排序算法,以至于我们可以选择不同的comparator来对给定的集合进行排序。
想要对数组内的对象进行排序,只需调用Arrays.sort()方法。想要对list中的对象进行排序,只需调用Collections.sort()方 法。但是,这两种方法要么实现了Comparable接口的sort()方法,对集合进行自然排序,要么实现了Comparator接口中的sort() 方法,能按某些条件(criteria)进行排序。其实Collections内部会调用Array的排序方法,因此除了在排序前Collections 类会将list中的元素先复制到数组中消耗点时间外,它们在排序性能上几乎没有差别。
我们可以通过调用Collections.unmodifiableCollection(Collection c)方法来创建一个只读的集合,然后再将它传入函数中,这样就会确保任何企图修改集合的操作都会引起 UnsupportedOperationException异常
可以调用Collections.synchronizedCollection(Collection c)方法,会返回一个同步的集合。
Java Collections 框架提供了常见的像排序和搜索算法的实现。这些方法都在具体的类中进行实现。大多数这些算法都用于操纵List,但有一部分是适用于所有的集合类的。这些 算法有排序,搜索,洗牌(shuffling),极值(min-max values)等。
大O符号用于描述某种算法的性能,这些算法通常被应用于操作包含大量元素的数据结构。由于集合类实际上就是对数据结构的实现,因此大多数情况下,我们都从时间、空间和性能等方面的开销来选择合适的容器,而大O符号就是对这三方面的一种衡量。
例1:ArrayListget(index i)方法的执行时间为常数,并不依赖list的大小,因此用大O符号表示为O(1)。
例2:对数组或链表进行查找,其性能都是O(n),因为我们需要遍历整个集合才能找到元素。
要根据需求来选择正确的容器类型,例如,如果大小固定,也许选择Array就比ArrayList更明智。如果想按插入顺序对Map进行迭代,那么需要选TreeMap。如果集合不允许重复,那么就应该选Set。
某些集合类允许指定初始容量,如果我们能粗略的估计下要存储元素的数量,那么我们就可能避免重新hash(rehashing)和自动扩容(resizing)所带来的某些性能问题。
要针对接口编程而不是针对实现编程,这允许我们以后可以轻松的变更实现。
要尽可能的使用泛型以保证类型安全,从而避免运行时的ClassCastException异常。
使用JDK提供的不可变的(immutable)类型来作为Map的键,从而避免自己实现hashCode() 和equals()方法。
尽可能多的使用Collections这一工具类,因为我们可以很轻松的调用它已实现的算法或用来获取只读的、同步的或空的集合对象,从而避免自己实现。这会极大的提高代码的复用性、稳定性,降低维护成本。