:在java开发中咱们确定会大量的使用集合,在这里我将总结常见的集合类,每一个集合类的优势和缺点,以便咱们能更好的使用集合。下面我用一幅图来表示java
其中淡绿色的表示接口,红色的表示咱们常用的类。数组
Java容器类类库的用途是保存对象,能够将其分为2个概念。dom
1.1:Collection函数
一个独立元素的序列,这些元素都服从一条或多条规则。其中List必须按照插入的顺序保存元素、Set不能有重复的元素、Queue按照排队规则来肯定对象的产生顺序(一般也是和插入顺序相同)测试
1.2:Map优化
一组成对的值键对对象,容许用键来查找值。ArrayList容许咱们用数字来查找值,它是将数字和对象联系在一块儿。而Map容许咱们使用一个对象来查找某个对象,它也被称为关联数组。或者叫作字典。this
List承诺能够将元素维护在特定的序列中。List接口在Collection的基础上加入了大量的方法,使得能够在List中间能够插入和移除元素。下面主要介绍2种Listspa
2.1:基本的ArrayList设计
它的优势在于随机访问元素快,可是在中间插入和移除比较慢3d
那么如今咱们就一块儿来看看为何ArrayList随机访问快,而插入移除比较慢。先说关于ArrayList的初始化。
ArrayList有三种方式进行初始化以下
private transient Object[] elementData;
public ArrayList() { this(10); } public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
咱们能够看出ArrayList其实就是采用的是数组(默认是长度为10的数组)。全部ArrayList在读取的时候是具备和数组同样的效率,它的时间复杂度为1。
插入尾部就是elementData[size++] = e;固然中间会进行扩容。如今主要说插入中间为何相对来讲比较慢源码以下
public void add(int index, E element) { rangeCheckForAdd(index);//验证(能够不考虑) ensureCapacityInternal(size + 1); // Increments modCount!!(超过当前数组长度进行扩容) System.arraycopy(elementData, index, elementData, index + 1, size - index);(核心代码) elementData[index] = element; size++; }
System.arraycopy(elementData, index, elementData, index + 1)第一个参数是源数组,源数组起始位置,目标数组,目标数组起始位置,复制数组元素数目。那么这个意思就是从index索性处每一个元素向后移动一位,最后把索引为index空出来,并将element赋值给它。这样一来咱们并不知道要插入哪一个位置,因此会进行匹配那么它的时间赋值度就为n。
2.2:LinkedList
它是经过代价较低在List中间进行插入和移除,提供了优化的顺序访问,可是在随机访问方面相对较慢。可是他的特性功能要比ArrayList强大的多。支持Queue和Stack
ListedList采用的是链式存储。链式存储就会定一个节点Node。包括三部分前驱节点、后继节点以及data值。因此存储存储的时候他的物理地址不必定是连续的。
咱们看下它的中间插入实现
从代码咱们能够看出先获取插入索引元素的前驱节点,而后把这个元素做为后继节点,而后在建立新的节点,而新的节点前驱节点和获取前驱节点相同,然后继节点则等于要移动的这个元素。因此这里是不须要循环的,从而在插入和删除的时候效率比较高。
咱们在来看看查询(咱们能够分析出它的效率要比ArrayList低了很多)
Set也是一个集合,可是他的特色是不能够有重复的对象,因此Set最经常使用的就是测试归属性,很容易的询问出某个对象是否存在Set中。而且Set是具备和Collection彻底同样的接口,没有额外的功能,只是表现的行为不一样。
3.1:HashSet
HashSet查询速度比较快,可是存储的元素是随机的并无排序,下面我写一段程序看一下
public static void main(String[] args){ /** * 没有顺序可循,这是由于hashset采用的是散列(处于速度考虑) */ Random random=new Random(47); Set<Integer> intset=new HashSet<Integer>(); for (int i=0;i<10000;i++){ intset.add(random.nextInt(30)); } System.out.print(intset); }
3.2:TreeSet
TreeSet是将元素存储红-黑树结构中,因此存储的结果是有顺序的(因此若是你想要本身存储的集合有顺序那么选择TreeSet)
public static void main(String[] args){ Random random=new Random(47); Set<Integer> intset=new TreeSet<Integer>(); for (int i=0;i<10000;i++){ intset.add(random.nextInt(30)); } System.out.print(intset); }
关于LinkedHashSet后面再说。
Queue是队列,队列是典型的先进先出的容器,就是从容器的一端放入元素,从另外一端取出,而且元素放入容器的顺序和取出的顺序是相同的。LinkedList提供了对Queue的实现,LinkedList向上转型为Queue。其中Queue有offer、peek、element、pool、remove等方法
offer是将元素插入队尾,返回false表示添加失败。peek和element都将在不移除的状况下返回对头,可是peek在对头为null的时候返回null,而element会抛出NoSuchElementException异常。poll和remove方法将移除并返回对头,可是poll在队列为null,而remove会抛出NoSuchElementException异常,如下是例子
public static void main(String[] args){ Queue<Integer> queue=new LinkedList<Integer>(); Random rand=new Random(); for (int i=0;i<10;i++){ queue.offer(rand.nextInt(i+10)); } printQ(queue); Queue<Character> qc=new LinkedList<Character>(); for (char c:"HelloWorld".toCharArray()){ qc.offer(c); } System.out.println(qc.peek()); printQ(qc); List<String> mystrings=new LinkedList<String>(); mystrings.add("1"); mystrings.get(0); Set<String> a=new HashSet<String>(); Set<String> set=new HashSet<String>(); set.add("1"); } public static void printQ(Queue queue){ while (queue.peek
从上面的输出的结果咱们能够看出结果并非一个顺序的,没有规则的,这个时候若是想让队列按照规则输出那么这个时候咱们就要考虑优先级了,这个时候咱们就应该使用PriorityQueue,这个时候若是在调用offer方法插入一个对象的时候,这个对象就会按照优先级在对列中进行排序,默认的状况是天然排序,固然咱们能够经过Comparator来修改这个顺序(在下一篇讲解)。PriorityQueue能够确保当你调用peek、pool、remove方法时,获取的元素将是对列中优先级最高的元素。ok咱们再次经过代码查看
public static void main(String[] args) { PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(); Random rand = new Random(); for (int i = 0; i < 10; i++) { priorityQueue.offer(rand.nextInt(i + 10)); } QueueDemo.printQ(priorityQueue); List<Integer>ints= Arrays.asList(25,22,20,18,14,9,3,1,1,2,3,9,14,18,21,23,25); priorityQueue=new PriorityQueue<Integer>(ints); QueueDemo.printQ(priorityQueue); }
从输出能够看到,重复是容许的,最小值拥有最高优先级(若是是String,空格也能够算做值,而且比字母具备更高的优先级)若是你想消除重复,能够采用Set进行存储,而后把Set做为priorityQueue对象的初始值便可。
Map在实际开发中使用很是广,特别是HashMap,想象一下咱们要保存一个对象中某些元素的值,若是咱们在建立一个对象显得有点麻烦,这个时候咱们就能够用上map了,HashMap采用是散列函数因此查询的效率是比较高的,若是咱们须要一个有序的咱们就能够考虑使用TreeMap。这里主要介绍一下HashMap的方法,你们注意HashMap的键能够是null,并且键值不能够重复,若是重复了之后就会对第一个进行键值进行覆盖。
put进行添加值键对,containsKey验证主要是否存在、containsValue验证值是否存在、keySet获取全部的键集合、values获取全部值集合、entrySet获取键值对。
public static void main(String[] args){ //Map<String,String> pets=new HashMap<String, String>(); Map<String,String> pets=new TreeMap<String, String>(); pets.put("1","张三"); pets.put("2","李四"); pets.put("3","王五"); if (pets.containsKey("1")){ System.out.println("已存在键1"); } if (pets.containsValue("张三")){ System.out.println("已存在值张三"); } Set<String> sets=pets.keySet(); Set<Map.Entry<String , String>> entrySet= pets.entrySet(); Collection<String> values= pets.values(); for (String value:values){ System.out.println(value+";"); } for (String key:sets){ System.out.print(key+";"); } for (Map.Entry entry:entrySet){ System.out.println("键:"+entry.getKey()); System.out.println("值:"+entry.getValue()); } }
如今foreach语法主要做用于数组,可是他也能够应用于全部的Collection对象。Collection之因此可以使用foreach是因为继承了Iterator这个接口。下面我写段代码供你们查看
public class IteratorClass { public Iterator<String> iterator(){ return new Itr(); } private class Itr implements Iterator<String>{ protected String[] words=("Hello Java").split(" "); private int index=0; public boolean hasNext() { return index<words.length; } public String next() { return words[index++]; } public void remove() { } } }
Iterator iterators=new IteratorClass().iterator(); for (Iterator it=iterator;iterators.hasNext();) { System.out.println(iterators.next()); } while (iterators.hasNext()){ System.out.println(iterators.next()); }
从中咱们能够看出foreach循环最终是转换成 for (Iterator it=iterator;iterators.hasNext();)只不过jdk帮咱们隐藏咱们没法查看。下面咱们在来分析一个问题,关于List删除问题。咱们大多确定使用过for循环或者foreach循环去删除,可是结果很明显会出现错误,那么如今咱们一块儿分析为啥会出现错误。
1:使用for循环删除(出现错误分析)
2:foreach循环删除(错误分析)
从上面咱们得知foreach最终是会转成Iterator的因此它首先会经过next来获取元素,咱们看代码
请看for循环删除那段代码,没删除一次modCount会++,因此第二次在次删除的时候modCount因为增长和expectedModCount不等因此没法获取元素也就没法删除。
3:正确的删除方式
采用迭代器代码以下
Iterator<String> iterator=userList.iterator(); while (iterator.hasNext()){ iterator.next(); iterator.remove(); }
请记住必定要加上iterator.next();这是由于在源码中有一个lastRed,经过它来记录是不是最后一个元素,若是不加上iterator.next()那么lastRed=-1,在删除验证的时候有这么一段代码if (lastRet < 0)throw new IllegalStateException();因此就会抛出异常。
这里只介绍2个经常使用的Collections.addAll和Arrays.asList
addAll:
asList采用的是数组
能够看出最终转换成ArrayList。
1):数组是将数字和对象联系起来,它保存明确的对象,查询对象时候不须要对查询结果进行转换,它能够是多维的,能够保存基本类型的数据,可是数组一旦生成,其容量不能改变。因此数组是不能够直接删除和添加元素。
2):Collection保存单一的元素,而Map保存相关联的值键对,有了Java泛型,能够指定容器存放对象类型,不会将错误类型的对象放在容器中,取元素时候也不须要转型。并且Collection和Map均可以自动调整其尺寸。容器不能够持有基本类型。
3):像数组同样,List也创建数字索性和对象的关联,所以,数组和List都是排好序的容器,List能够自动扩容
4):若是须要大量的随机访问就要使用ArrayList,若是要常常从中间插入和删除就要使用LinkedList。
5):各类Queue和Stack由LinkedList支持
6):Map是一种将对象(而非数字)与对象相关联的设计。HashMap用于快速访问,TreeMap保持键始终处于排序状态,因此不如HashMap快,而LinkedHashMap保持元素插入的顺序,可是也经过散列提供了快速访问的能力
7):Set不接受重复的元素,HashSet提供最快的访问能力,TreeSet保持元素排序状态,LinkedHashSet以插入顺序保存元素。