1、集合类简介 java
数组是很经常使用的一种的数据结构,咱们用它能够知足不少的功能,可是,有时咱们会遇到以下这样的问题: 面试
一、咱们须要该容器的长度是不肯定的。 算法
二、咱们须要它能自动排序。 编程
三、咱们须要存储以键值对方式存在的数据。 数组
若是遇到上述的状况,数组是很难知足需求的,接下来本章将介绍另外一种与数组相似的数据结构——集合类,集合类在Java中有很重要的意义,保存临时数据,管理对象,泛型,Web框架等,不少都大量用到了集合类。 缓存
常见的集合类有这些种: 安全
实现Collection接口的:Set、List以及他们的实现类。 数据结构
实现Map接口的:HashMap及其实现类,咱们经常使用的有Map及其实现类HashMap,HashTable,List、Set及其实现类ArrayList、HashSet,由于集合类是很大的一块内容,咱们不方便把它的所有内容写出来,只能慢慢的增长,但愿各位读者有本身想法的,踊跃向我提出,咱们共同打造精美的博客,供广大编程爱好者学习,下面我咱们经过一个图来总体描述一下: 多线程
这个图片无法显示的很清楚,因此我将原始图片上传到了个人资源里:http://download.csdn.net/detail/zhangerqing/4711389。愿意看清楚的就去下吧。 框架
下面的表格也许能够更直接的表现出他们之间的区别和联系:
接口 |
简述 |
实现 |
操做特性 |
成员要求 |
Set |
成员不能重复 |
HashSet |
外部无序地遍历成员 |
成员可为任意Object子类的对象,但若是覆盖了equals方法,同时注意修改hashCode方法。 |
TreeSet |
外部有序地遍历成员;附加实现了SortedSet, 支持子集等要求顺序的操做 |
成员要求实现caparable接口,或者使用 Comparator构造TreeSet。成员通常为同一类型。 |
||
LinkedHashSet |
外部按成员的插入顺序遍历成员 |
成员与HashSet成员相似 |
||
List |
提供基于索引的对成员的随机访问 |
ArrayList |
提供快速的基于索引的成员访问,对尾部成员的增长和删除支持较好 |
成员可为任意Object子类的对象 |
LinkedList |
对列表中任何位置的成员的增长和删除支持较好,但对基于索引的成员访问支持性能较差 |
成员可为任意Object子类的对象 |
||
Map |
保存键值对成员,基于键找值操做,compareTo或compare方法对键排序 |
HashMap |
能知足用户对Map的通用需求 |
键成员可为任意Object子类的对象,但若是覆盖了equals方法,同时注意修改hashCode方法。 |
TreeMap |
支持对键有序地遍历,使用时建议先用HashMap增长和删除成员,最后从HashMap生成TreeMap;附加实现了SortedMap接口,支持子Map等要求顺序的操做 |
键成员要求实现caparable接口,或者使用Comparator构造TreeMap。键成员通常为同一类型。 |
||
LinkedHashMap |
保留键的插入顺序,用equals 方法检查键和值的相等性 |
成员可为任意Object子类的对象,但若是覆盖了equals方法,同时注意修改hashCode方法。 |
||
IdentityHashMap |
使用== 来检查键和值的相等性。 |
成员使用的是严格相等 |
||
WeakHashMap |
其行为依赖于垃圾回收线程,没有绝对理由则少用 |
|
(上图来源于网友的总结,已不知是哪位的原创,恕不贴出地址,如原做者看到请联系我,必将贴出连接!)
实现Collection接口的类,如Set和List,他们都是单值元素(其实Set内部也是采用的是Map来实现的,只是键值同样,从表面理解,就是单值),不像实现Map接口的类同样,里面存放的是key-value(键值对)形式的数据。这方面就形成他们不少的不一样点,如遍历方式,前者只能采用迭代或者循环来取出值,可是后者可使用键来得到值得值。
2、基本方法及使用
---------------------------
实现Map接口的
HashMap
Set的实现类HashSet,底层仍是调用Map接口来处理,因此,此处,我将说下Map接口及其实现类的一些方法。Map接口中的原始方法有:
public abstract int size();
public abstract boolean isEmpty();
public abstract boolean containsKey(Object paramObject);
public abstract boolean containsValue(Object paramObject);
public abstract V get(Object paramObject);
public abstract V put(K paramK, V paramV);
public abstract V remove(Object paramObject);
public abstract void putAll(Map<? extends K, ? extends V> paramMap);
public abstract void clear();
public abstract Set<K> keySet();
public abstract Collection<V> values();
public abstract Set<Entry<K, V>> entrySet();
public abstract boolean equals(Object paramObject);
public abstract int hashCode();
此处细心的读者会看到,每一个方法前都有abstract关键字来修饰,其实就是说,接口中的每一个方法都是抽象的,有的时候咱们写的时候不加abstract关键字,可是在编译的过程当中,JVM会给加上的,因此这点要注意。抽象的方法意味着它没有方法实现体,同时必须在实现类中重写,接下来咱们依次分析一下他们的实现类HashMap中是怎么作的。
HashMap的设计复杂而又巧妙,用处普遍,值得咱们深究一下,由于HashMap是一块很大很重要的知识点,而在这儿咱们重点介绍集合类,因此请看这篇文章,关于【深刻解读HashMap内部结构】的文章。
重点方法介绍:
首先是构造方法,大多数状况下,咱们采起无参的构造函数来构造哈希表,
public HashMap() { this.entrySet = null; this.loadFactor = 0.75F; this.threshold = 12; this.table = new Entry[16]; init(); }此处先介绍三个重要的变量:loadFactor、threshold、table,loadFactor是一个加载因子,threshold是临界值,table说明哈希表的底层,实际上是个数组。能够看看他们的声明方式:
transient Entry[] table;;
int threshold;
final float loadFactor;
细心的读者彷佛又发现一个新问题,为何table前面采用的是transient关键字呢,那咱们得闲来研究下声明为transient关键字的含义:变量若是被声明为transient类型的话,那么在序列化的时候,忽略其的值,就是说此处的table,若是将要进行持久化的话,是不会对table的值进行处理的,直接忽略,为何此处table会这样处理呢?由于HashMap的存储结构,其实就是一个数组+多个链表,数组里存放对象的地址,链表存放数据,因此对地址进行持久化是没有任何意义的。关于这块知识,我会在另外一篇文章【深刻解读HashMap内部结构】中重点介绍。
HashMap的初始容量为0,每增长一对值,容量曾1,这点好理解,咱们经过一个小的例子,来看看HashMap的基本使用方法。
package com.xtfggef.map.test; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * HashMap的使用 * @author erqing * */ public class MapTest { public static void main(String[] args) { /* 初始化map */ Map<String, Integer> map = new HashMap<String, Integer>(); System.out.println("HashMap的初始值:" + map.size()); System.out.println("HashMap是否为空:" + (map.isEmpty() ? "是" : "否")); /* 想map中添加元素 */ map.put("erqing", 1); map.put("niuniu", 2); map.put("egg", 3); System.out.println(map.size()); ; System.out.println("HashMap是否为空:" + (map.isEmpty() ? "是" : "否")); /* 遍历HashMap中的元素 */ Set<String> set = map.keySet(); for (String s : set) { System.out.println(s + " " + map.get(s) + " " + "hashcode:" + s.hashCode()); } /*检测是否含有某个Key*/ System.out.println(map.containsKey("egg")); /*检测是否含有某个Value*/ System.out.println(map.containsValue(2)); /*打印hashCode*/ System.out.println(map.hashCode()); } }输出:
HashMap的初始值:0
HashMap是否为空:是
3
HashMap是否为空:否
niuniu 2 hashcode:-1045196352
egg 3 hashcode:100357
erqing 1 hashcode:-1294670850
true
true
1955200455
此处附一个利用HashMap来简单处理问题的例子,需求在注释中已经给出,但愿读者好好看看,代码不难,可是不少的面试及面试题都用到这个思路,笔者曾经面试的时候,常常会被问题这题的思想,可是就是没有去亲自实现一下,以至在hashmap的操做上被难住了。
package com.xtfggef.hashmap; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * 打印在数组中出现n/2以上的元素 * 利用一个HashMap来存放数组元素及出现的次数 * @author erqing * */ public class HashMapTest { public static void main(String[] args) { int [] a = {2,3,2,2,1,4,2,2,2,7,9,6,2,2,3,1,0}; Map<Integer, Integer> map = new HashMap<Integer,Integer>(); for(int i=0; i<a.length; i++){ if(map.containsKey(a[i])){ int tmp = map.get(a[i]); tmp+=1; map.put(a[i], tmp); }else{ map.put(a[i], 1); } } Set<Integer> set = map.keySet(); for (Integer s : set) { if(map.get(s)>=a.length/2){ System.out.println(s); } } } }关于HashMap的一些其余底层的东西及与HashTable的区别和联系,我会在另外一篇文章里介绍,此处暂很少说。
实现Collection接口的
ArrayList
后面的博文,我会以分析和例子为主,毕竟源码这种东西,不适合大量的贴出来,你们都会本身去看,有些很差懂的地方或者容易忽略的知识,我会贴出来,其它的建议读者本身去看JDK源码。ArrayList底层采用数组实现,具备较高的查询速度。
boolean isEmpty():若是容器里面没有保存任何元素,就返回true。
boolean contains(Object):若是容器持有参数Object,就返回true。
Iterator iterator():返回一个能够在容器的各元素之间移动的Iterator。
Object[] toArray():返回一个包含容器中全部元素的数组。
Object[] toArray(Object[] a):返回一个包含容器中全部元素的数组,且这个数组不是普通的Object数组,它的类型应该同参数数组a的类型相同(要作类型转换)。
void clear():清除容器所保存的全部元素。(“可选”)
boolean remove(Object o);
boolean add(Object):确保容器能持有你传给它的那个参数。若是没有把它加进去,就返回false。(这是个“可选”的方法,本章稍后会再做解释。)
boolean addAll(Collection):加入参数Collection所含的全部元素。只要加了元素,就返回true。
boolean containsAll(Collection):若是容器持有参数Collection所含的所有元素,就返回true
boolean removeAll(Collection):删除容器里面全部参数Collection所包含的元素。只要删过东西,就返回true。(“可选”)
boolean retainAll(Collection):只保存参数Collection所包括的元素(集合论中“交集”的概念)。若是发生过变化,则返回true。(“可选”)
boolean equals(Object o);
int hashCode();
int size():返回容器所含元素的数量。
这些方法也就是Set和List及它们的实现类里有的方法。
List接口在Collection接口的基础上,有添加了本身的一系列方法:
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
先看一下构造函数,和Map同样,咱们一样习惯使用默认的无参构造函数,
private transient Object[] elementData; private int size; public ArrayList(int paramInt) { if (paramInt < 0) throw new IllegalArgumentException("Illegal Capacity: " + paramInt); this.elementData = new Object[paramInt]; } public ArrayList() { this(10); }此处elementData就是它底层用来存放数据的数组元素,仔细看一下,不管采用无参还有有参的构造
函数,最终都归结于一句话:this.elementData = new Object[paramInt];若是没有传入参数的话,会默认开辟一个10个字节大小的空间,但是当咱们用的时候,咱们写下以下的语句:
List<String> list = new ArrayList<String>( );
List<String> list = new ArrayList<String>(5);
当咱们输出它的size值时:System.out.println(list.size());咱们发现,输出的都是0.这让人貌似有一丝迷惑,明明是10或者5,这儿应该用清楚,elementData数组的长度并非size的值,size是里面元素的个数,上面的10或者是5,意思是向内容开辟10个大小的空间,初始化的时候开辟必定数量的内存,可是里面并无听任何对象,因此用size()计算获得的结果仍为0.
这时,咱们又有新问题了,由于咱们知道List是能够自动扩容的,这个功能就取决于以下的方法:
public void ensureCapacity(int paramInt) { this.modCount += 1; int i = this.elementData.length; if (paramInt > i) { Object[] arrayOfObject = this.elementData; int j = i * 3 / 2 + 1; if (j < paramInt) j = paramInt; this.elementData = Arrays.copyOf(this.elementData, j); } }ensureCapacity(int paramInt)用来初始化或者扩大ArrayList的空间。
从上述代码中能够看出,数组进行扩容时,会将老数组中的元素从新拷贝一份到新的数组中,每次数组容量的增加大约是其原容量的1.5倍。这种操做的代价是很高的,所以在实际使用时,咱们应该尽可能避免数组容量的扩张。当咱们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以免数组扩容的发生。或者根据实际需求,经过调用ensureCapacity方法来手动增长ArrayList实例的容量。
ArrayList还给咱们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它能够经过trimToSize方法来实现。代码以下:
public void trimToSize() { this.modCount += 1; int i = this.elementData.length; if (this.size < i) this.elementData = Arrays.copyOf(this.elementData, this.size); }经过ensureCapacity(int paramInt)方法能够提升ArrayList的初始化速度,请看下面的代码:
package com.xtfggef.list.test; import java.util.ArrayList; public class EnsureCapacityTest { @SuppressWarnings("unchecked") public static void main(String[] args) { final int N = 1000000; Object obj = new Object(); /*没用调用ensureCapacity()方法初始化ArrayList对象*/ ArrayList list = new ArrayList(); long startTime = System.currentTimeMillis(); for (int i = 0; i <= N; i++) { list.add(obj); } long endTime = System.currentTimeMillis(); System.out.println("没有调用ensureCapacity()方法所用时间:" + (endTime - startTime) + "ms"); /*调用ensureCapacity()方法初始化ArrayList对象*/ list = new ArrayList(); startTime = System.currentTimeMillis(); // 预先设置list的大小 list.ensureCapacity(N); for (int i = 0; i <= N; i++) { list.add(obj); } endTime = System.currentTimeMillis(); System.out.println("调用ensureCapacity()方法所用时间:" + (endTime - startTime) + "ms"); } }输出:
没有调用ensureCapacity()方法所用时间:102ms
调用ensureCapacity()方法所用时间:46ms
很明显,使用ensureCapacity()能提升很多效率!
下面实现一个简单的例子,来展示下ArrayList的基本使用。
package com.xtfggef.list.test; import java.util.ArrayList; /** * * 关于ArrayList的基本操做 * 其它操做感兴趣的读者能够本身结合源码实现一下 * * @author erqing * */ public class ListTest { public static void main(String[] args) { /* 新建一个ArrayList */ ArrayList<String> list = new ArrayList<String>(); System.out.println("初始化大小:" + list.size()); /* 添加元素 */ list.add("zzz"); list.add("egg"); list.add("hell"); list.add("child"); System.out.println("当前容量:" + list.size()); /* 将ArrayList的大小和实际所含元素的大小设置一致 */ list.trimToSize(); /* 遍历 */ for (String string : list) { System.out.println(string); } /* 在指定位置插入元素 */ list.add(2, "zhu"); for (String string : list) { System.out.println(string); } System.out.println("--------------"); /* 清空list */ list.clear(); /* 遍历 */ for (String string : list) { System.out.println(string); } System.out.println("--------------"); } }
ArrayList基于数组实现,因此它具有数组的特色,即查询速度较快,可是修改、插入的速度却有点儿慢,可是,下面将要介绍的LinkedList就是来解决这个问题的,LinkedList基于链表,与ArrayList互补,因此实际开发中咱们应该按照本身的需求来定到底用哪个。
Where there is a will,there is a way.With the boat burned,Qin's territory final belonged to Chu.
The God won't cheat the hard working people.As the undergo self-imposed hardships
so as to strengthen his resolve,three thousand soldiers from Yue destroyed the country of Wu!
LinkedList
LinkedList底层采用双向循环列表实现,进行插入和删除操做时具备较高的速度,咱们还可使用LinkedList来实现队列和栈。
private static class Entry<E> { E element; Entry<E> next; Entry<E> previous; Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
这是LinkedList的原始存储模型,由于是双向循环列表,咱们能够回忆一下数据结构中双向列表是什么状况:一个数据data,两个指针,一个指向前一个节点,名为previous,一个指向下一个节点,名为next,可是循环怎么体现了,来看下她的无参构造函数:
public LinkedList() { header.next = header.previous = header; }
头尾相等,就是说初始化的时候就已经设置成了循环的。仔细观察源码,不难理解,若是熟悉数据结构的读者,必定很快就能掌握她的原理。下面我简单分析一个操做,就是LinkedList的add()。读者能够经过这个本身去理解其余操做。
public boolean add(E e) { addBefore(e, header); return true; }
private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);//-------1--------- newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry; }
咱们先来观察下上面给出的Entity类,构造方法有三个参数,第二个是她的next域,第三个是她的previous域,因此上述代码1行处将传进来的entry实体,即header对象做为newEntry的next域,而将entry.previous即header.previous做为previous域。也就是说在header节点和header的前置节点之间插入新的节点。看下面的图:
一目了然。
欢迎广大读者进行建议、反馈,笔者定会及时改正,更新!
其余的道理同样,下面我会给出一个LinkedList使用的例子,须要注意的地方,我会特别说明。
package com.xtfggef.list.test; import java.util.LinkedList; public class LinkedListTest { public static void main(String[] args) { /* 新建一个list */ LinkedList<Integer> list = new LinkedList<Integer>(); System.out.println(list.size()); /* 向list中添加元素 */ list.add(222); list.add(111); list.add(0); list.add(3333); list.add(8888); System.out.println(list.size()); /* 遍历list */ for (Integer integer : list) { System.out.println(integer); } /* 获取第一个元素 ,即header的next域*/ System.out.println("第一个元素是:" + list.getFirst()); /*获取最后一个元素,即header的previous域*/ System.out.println("最后一个元素是:"+list.getLast()); } }
比较简单,其余的方法,请读者本身去尝试,结合源码。
实现Map接口的
HashMap
关于HashMap的详细介绍,请看深刻解析HashMap章节
WeakHashMap
理解该集合类以前,建议先去了解Java的垃圾回收机制,WeakHashMap多用于缓存系统,就是说在系统内存紧张的时候可随时进行GC,可是若是内存不紧张则能够用来存放一些缓存数据。由于若是使用HashMap的话,它里面的值基本都是强引用,即便内存不足,它也不会进行GC,这样系统就会报异常。看一下WeakHashMap中Entry的实现:
private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> { private V value; private final int hash; private Entry<K,V> next; /** * Creates new entry. */ Entry(K key, V value, ReferenceQueue<K> queue, int hash, Entry<K,V> next) { super(key, queue); this.value = value; this.hash = hash; this.next = next; }......
Entry继承了WeakReference类,且在构造函数中构造了Key的弱引用,当进行put或者get操做时,都会调用一个函数叫expungeStaleEntries(),以下:
private void expungeStaleEntries() { Entry<K,V> e; while ( (e = (Entry<K,V>) queue.poll()) != null) { int h = e.hash; int i = indexFor(h, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; else prev.next = next; e.next = null; // Help GC e.value = null; // " " size--; break; } prev = p; p = next; } } }
就是用来判断,若是key存在弱引用,则进行垃圾回收,因此这个就是WeakHashMap的工做原理。它与HashMap的区别就是:数据量大的时候,它可根据内存的状况,自动进行垃圾回收。若是手动将key置为强引用,那么它将和HashMap变得同样,失去其功能。
3、比较(性能,功能方面)
这一块主要就是对咱们平时接触的这些集合类作一个简单的总结,一方面有助于本身整理思路,再者面试的时候,面试官总喜欢问一些他们之间的区别,凡是Java面试,几乎都要问到集合类的东西,问的形式有两种:1、整体介绍下集合类有哪些。这个问题只要把我上文中的图介绍一下就好了。2、比较一下XXX和XXXX。固然了,确定包括相同点和不一样的地方。这个稍微麻烦一点,须要咱们完全理解了,才能回答的比较准确。如下是我对常被比较的一些类的分析:
一、HashMap和HashTable
相同点:两者都实现了Map接口,所以具备一系列Map接口提供的方法。
不一样点:
一、HashMap继承了AbstractMap,而HashTable继承了Dictionary。
二、HashMap非线程安全,HashTable线程安全,处处都是synchronized关键字。
三、由于HashMap没有同步,因此处理起来效率较高。
四、HashMap键、值都容许为null,HashTable键、值都不容许有null。
五、HashTable使用Enumeration,HashMap使用Iterator。
这些就是一些比较突出的不一样点,实际上他们在实现的过程当中会有不少的不一样,如初始化的大小、计算hash值的方式等等。毕竟这两个类包含了不少方法,有很重要的功能,因此其余不一样点,请感兴趣的读者本身去看源码,去研究。笔者推荐使用HashMap,由于她提供了比HashTable更多的方法,以及较高的效率,若是你们须要在多线程环境中使用,那么用Collections类来作一下同步便可。
二、Set接口和List接口
相同点:都实现了Collection接口
不一样点:
一、Set接口不保证维护元素的顺序,并且元素不能重复。List接口维护元素的顺序,并且元素能够重复。
二、关于Set元素如何保证元素不重复,我将在下面的博文中给出。
三、ArrayList和LinkList
相同点:都实现了Collection接口
不一样点:ArrayList基于数组,具备较高的查询速度,而LinkedList基于双向循环列表,具备较快的添加或者删除的速度,两者的区别,其实就是数组和列表的区别。上文有详细的分析。
四、SortedSet和SortedMap
两者都提供了排序的功能。 来看一个小例子:
public static void main(String[] args) { SortedMap<String, Integer> map = new TreeMap<String, Integer>(); map.put("zgg", 1); map.put("erqing", 3); map.put("niu", 0); map.put("abc", 2); map.put("aaa", 5); Set<String> keySet = map.keySet(); for (String string : keySet) { System.out.print(map.get(string)+" "); } }
输出:5 2 3 0 1
从结果看得出:SortedMap具备自动排序功能
五、TreeMap和HashMap
HashMap具备较高的速度(查询),TreeMap则提供了按照键进行排序的功能。
六、HashSet和LinkedHashSet
HashSet,为快速查找而设计的Set。存入HashSet的对象必须实现hashCode()和equals()。
LinkedHashSet,具备HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序),因而在使用迭代器遍历Set时,结果会按元素插入的次序显示。
七、TreeSet和HashSet
TreeSet: 提供排序功能的Set,底层为树结构 。相比较HashSet其查询速度低,若是只是进行元素的查询,咱们通常使用HashSet。
八、ArrayList和Vector
同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的。
数据增加:当须要增加时,Vector默认增加为原来一培,而ArrayList倒是原来的一半
九、Collection和Collections
Collection是一系列单值集合类的父接口,提供了基本的一些方法,而Collections则是一系列算法的集合。里面的属性和方法基本都是static的,也就是说咱们不须要实例化,直接可使用类名来调用。下面是Collections类的一些功能列表:
生成单元素集合
Collections中的单元素集合指的是集合中只有一个元素并且集合只读。
Collections.singletonList——用来生成只读的单一元素的List
Collections.singletonMap——用来生成只读的单Key和Value组成的Map
Collections.singleton——用来生成只读的单一元素的Set
以下面的例子:
public static void main(String[] args) { Map<Integer, Integer> map = Collections.singletonMap(1, 1); //map.put(2, 2); ----------1------------- System.out.println(map.size()); }
Collections.singletonMap(1, 1)生成一个单元素的map,若是加上1处的代码,会报异常。
Checked集合
Checked集合具备检查插入集合元素类型的特性,例如当咱们设定checkedList中元素的类型是String的时候,若是插入其余类型的元素就会抛出ClassCastExceptions异常,Collections中提供了如下生成Checked集合的方法checkedCollection,checkedList,checkedMap,checkedSet,checkedSortedMap,checkedSortedSet
同步集合
Collections类提供一系列同步方法,为一些非线程安全的集合类提供同步机制。
查找替换
fill——使用指定元素替换指定列表中的全部元素。
frequency——返回指定 collection 中等于指定对象的元素数。
indexOfSubList—— 返回指定源列表中第一次出现指定目标列表的起始位置,若是没有出现这样的列表,则返回 -1。
lastIndexOfSubList——返回指定源列表中最后一次出现指定目标列表的起始位置,若是没有出现这样的列表,则返回-1。
max—— 根据元素的天然顺序,返回给定 collection 的最大元素。
min——根据元素的天然顺序 返回给定 collection 的最小元素。
replaceAll——使用另外一个值替换列表中出现的全部某一指定值。
附一个小例子:
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); for (Integer integer : list) { System.out.println(integer); } /*找出最大值*/ int max = Collections.max(list); System.out.println("最大的为:"+max); /*用指定元素替换指定list中的元素*/ Collections.fill(list, 6); System.out.println("替换后:"); for (Integer integer : list) { System.out.println(integer); } /*找出某个list里某个元素的个数*/ int count = Collections.frequency(list, 6); System.out.println("里面有6的个数:"+count); }
集合排序
Collections还提供了集中对集合进行排序的方法。
reverse——对List中的元素进行转置
shuffle——对List中的元素随即排列
sort——对List中的元素排序
swap——交换List中某两个指定下标位元素在集合中的位置。
rotate——循环移动
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(5); list.add(2); list.add(1); list.add(9); list.add(0); System.out.println("排序前:"); for (Integer integer : list) { System.out.print(integer+" "); } System.out.println(); /*排序*/ Collections.sort(list); System.out.println("排序后"); for (Integer integer : list) { System.out.print(integer+" "); } }
输出:
排序前:
5 2 1 9 0
排序后
0 1 2 5 9
下面是关于rotate(List<?> list, int distance)的一个例子:
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(5); list.add(2); list.add(1); list.add(9); list.add(0); System.out.println("原序列:"); for (Integer integer : list) { System.out.print(integer+" "); } System.out.println(); /*根据步长进行循环*/ Collections.rotate(list, -1); System.out.println("循环后:"); for (Integer integer : list) { System.out.print(integer+" "); } }
读者能够屡次换换第二个参数,来观察它的变化。
总结一下,带Tree的集合类,底层通常是基于二叉树的,因此具备自动排序功能。有些功能方面差异不大,具体开发的时候需根据实际状况选择使用哪一个类。
本博客持久更新,欢迎你们积极建议、补充!若有转载,敬请说明出处!
4、常见问题
这块内容,我讲就一些常见的问题作一下分析,欢迎广大读者提出更多的问题,咱们一块儿讨论,解决!
一、Set集合如何保证对象不重复
这儿我采用HashSet来实现Set接口,先看个例子:
public static void main(String[] args) { Set<String> set = new HashSet<String>(); String a = "hello"; String b = "hello"; String s = new String("hello"); String s1 = new String("hello"); set.add(a); set.add(s); set.add(s1); set.add(b); System.out.println("size:"+set.size()); for (String ss : set) { System.out.println(ss); } }
输出:
size:1
hello
说明,Set集合不容许有重复出现的对象,且最终的判断是根据equals()的。其实原理是这样的:HashSet的底层采用HashMap来存放数据,HashMap的put()方法是这样的:
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode());//----------1---------- int i = indexFor(hash, table.length);//-----------2--------- for (Entry<K,V> e = table[i]; e != null; e = e.next) {//-----------3--------- Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }//------------------4-------------------- modCount++; addEntry(hash, key, value, i); return null; }
当向HashMap中添加元素的时候,首先计算元素的hashcode值,而后根据1处的代码计算出Hashcode的值,再根据2处的代码计算出这个元素的存储位置,若是这个位置为空,就将元素添加进去;若是不为空,则看3-4的代码,遍历索引为i的链上的元素,若是key重复,则替换并返回oldValue值。<关于这部份内容,请看另外一篇文章:《深刻解读HashMap》有详细介绍>
二、集合类排序问题
一种状况是集合类自己自带排序功能,如前面说过的TreeSet、SortedSet、SortedMap等,另外一种就是自己不带排序功能,咱们经过为须要排序的类实现Comparable或者Comparator接口来实现。
先来看两个例子,一个是实现Comparable的,一个是实现Comparator的,为了方便,我将类都写在了一个文件中。
package com.xtfggef.list.test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @SuppressWarnings("unchecked") public class ComparableTest { public static void main(String[] args) { // User[] users = { new User("egg", 23), new User("niuniu", 22), // new User("qing", 28) }; // Arrays.sort(users); // for (User user : users) { // System.out.println(user.getName() + " " + user.getAge()); // } List<User> users = new ArrayList<User>(); users.add(new User("egg", 23)); users.add(new User("niu", 22)); users.add(new User("qing", 28)); Collections.sort(users); for (User user : users) { System.out.println(user.getName() + " " + user.getAge()); } } } @SuppressWarnings("unchecked") class User implements Comparable { private String name; private int age; public User(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int compareTo(Object o) { return this.age - ((User) o).getAge(); } }下面是实现Comparator接口的:
package com.xtfggef.comparator.test; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class ComparatorTest { public static void main(String[] args) { List<User> users = new ArrayList<User>(); users.add(new User("egg", 21)); users.add(new User("niu", 22)); users.add(new User("gg", 29)); UserComparator comparator = new UserComparator(); Collections.sort(users, comparator); for (User user : users) { System.out.println(user.getUsername() + " " + user.getAge()); } } } class User { private String username; private int age; public User(String username, int age) { super(); this.username = username; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } class UserComparator implements Comparator<User> { @Override public int compare(User user1, User user2) { int age1 = user1.getAge(); int age2 = user2.getAge(); if (age1 < age2) { return 1; } return 0; } }经过上面的这两个小例子,咱们能够看出,Comparator和Comparable用于不一样的场景,实现对对象的比较从而进行排序。
总结为:
相同点:
一、两者均可以实现对象的排序,不论用Arrays的方法仍是用Collections的sort()方法。
不一样点:
一、实现Comparable接口的类,彷佛是预先知道该类将要进行排序,须要排序的类实现Comparable接口,是一种“静态绑定排序”。
二、实现Comparator的类不须要,设计者无需事先为须要排序的类实现任何接口。
三、Comparator接口里有两个抽象方法compare()和equals(),而Comparable接口里只有一个方法:compareTo()。
四、Comparator接口无需改变排序类的内部,也就是说实现算法和数据分离,是一个良好的设计,是一种“动态绑定排序”。
五、Comparator接口可使用多种排序标准,好比升序、降序等。
三、使用for循环删除元素陷阱
先来看看下面这个程序:
public class Test { public static void main(String[] args) { List<String> list = new LinkedList<String>(); list.add("A"); list.add("B"); list.add("C"); for(int i=0; i<list.size(); i++){ list.remove(i); } for(String item:list){ System.out.println(item); } } }读者朋友们能够先猜猜这个程序输出什么?按咱们的思路,应该是输不出什么,可是执行它,输出的倒是:B。这是为何呢?咱们分部分析下这个程序,当地一步remove完后,集合内还剩2个元素,此时i为1,而list.size()的值为2,从0开始的话,i为1时,正好指向第二个元素,也就是说当remove完A后,直接就跳到C,将B漏了。
解决办法: