java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台(即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se))的总称。
本文主要介绍了 java 容器的详细解析。具有很好的参考价值。下面跟着小编一起来看下吧
前言:在 java 开发中我们肯定会大量的使用集合,在这里我将总结常见的集合类,每个集合类的优点和缺点,以便我们能更好的使用集合。下面我用一幅图来表示
其中淡绿色的表示接口,红色的表示我们经常使用的类。
1:基本概念
Java 容器类类库的用途是保存对象,可以将其分为 2 个概念。
1.1:Collection
一个独立元素的序列,这些元素都服从一条或多条规则。其中 List 必须按照插入的顺序保存元素、Set 不能有重复的元素、Queue 按照排队规则来确定对象的产生顺序(通常也是和插入顺序相同)
1.2:Map
一组成对的值键对对象,允许用键来查找值。ArrayList 允许我们用数字来查找值,它是将数字和对象联系在一起。而 Map 允许我们使用一个对象来查找某个对象,它也被称为关联数组。或者叫做字典。
2:List
List 承诺可以将元素维护在特定的序列中。List 接口在 Collection 的基础上加入了大量的方法,使得可以在 List 中间可以插入和移除元素。下面主要介绍 2 种 List
2.1:基本的 ArrayList
它的优点在于随机访问元素快,但是在中间插入和移除比较慢
那么现在我们就一起来看看为什么 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 低了不少)
3:Set
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 后面再说。
4:Queue
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 对象的初始值即可。
5:Map
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());
- }
- }
6:Iterator 和 Foreach
现在 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(); 所以就会抛出异常。
7:Collections 和 Arrays
这里只介绍 2 个常用的 Collections.addAll 和 Arrays.asList
addAll:
asList 采用的是数组
可以看出最终转换成 ArrayList。
8:总结
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 以插入顺序保存元素。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持 PHPERZ!
来源: http://www.phperz.com/article/17/1224/358036.html