欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java集合之List集合详细解释

程序员文章站 2022-05-25 21:18:45
...

数组与集合

数组和集合都是存放 Java数据的 Java容器,数组与集合底层的数据结构都是线性结构,对于数组而言,一旦声明了长度,数据存放的容量就不能进行改变,集合就不一样,集合也是容器但是里面的数据容量却是可以改变的,不同的集合所存放的集合数据规则也就不一样。数组在删除,添加,修改操作方面的效率不太高,存储数据的特点是有序可重复的,但是对于无序,不可重复的需求来讲,数组就无法实现了,此时Java就引出了集合这个概念。Java中的集合类都放在了**java.util**包中

单列集合框架结构

Java集合之List集合详细解释

上图中的集合主要就是三种Collection,List,Set集合,List,Set接口集合继承的是Collection单体集合接口,并拥有Collection中的接口方法。Collection单体集合主要存放一个一个的对象。

  • List集合是有序可重复的,实现类主要有ArrayList, LinkedList,Vector
  • Set集合是无序不可重复,实现类主要有HashSetLinkedHashSetTreeSet

Collection接口常用方法

①添加:

add(Object obj):只要是对象都可以添加

addAll(Collection c):添加另一个集合的元素

②删除:

clear():清空集合元素

remove(Object obj)删除某个元素对象

removeAll(Collection c):删除集合元素

③判断:

isEmpty():集合是否为空

contains(Object c):是否包含某个元素

contains(Collection c):是否包含某个集合

④获取:

size():获取集合大小
Iterator<E> iterator():获取迭代器对象,遍历集合

⑤其他:

Object[] toArray():集合转数组
retainAll(Collection c):两个集合交集操作

使用这些方法:

    Collection c = new ArrayList();
    //添加集合元素
    c.add("hello");
    c.add("world");
    c.add(199);//Integer.valueOf()
    Object[] o = c.toArray();
    System.out.println(Arrays.toString(o));
    System.out.println(c.size());//3
    Collection c1 = new ArrayList();
    c1.add("hello");
    c1.add("world");
    //添加另一个集合元素
    System.out.println(c);
    c.addAll(c1);
    System.out.println(c);//[hello, world, 199, hello, world]
    //删除c集合中的hello,只会删除第一个
    System.out.println(c.remove("hello"));
    System.out.println(c);
    c.clear();
    System.out.println(c);// [] c集合已经清空
    System.out.println(c.isEmpty());// true 是否为空 

此段代码中,199并不是对象为什么能够添加呢?因为底层使用了自动装箱的操作Integer.valueOf()的操作

重点来看一下集合retainAll(Collection c)的方法,它是判断两个集合是否具有交集,使用方式就是oldCourse.retainAll(newCollection),如果oldCourse发生了改变则为true,反之false

    Collection c1 = new ArrayList();
    c1.add("world");
    c1.add("cv");
    c1.add("xx");
    c1.add("abf");

    Collection c2 = new ArrayList();
    c2.add("hello");
    c2.add("world");

    //集合取交集,判断c2是否发生了改变,改变返回true,没变返回false
    boolean b1 = c2.retainAll(c1);
    System.out.println(b1);
    System.out.println(c1);//[world, cv, xx, abf]
    System.out.println(c2);//[world]

**分析:**判断c2是否与c1有交集,发现c1与c2具有交集“world”,则c2发生了改变,输出的b1为true,c1并不会发生改变,如果没有交集的话,c2会变为空[],c1还是原始的数据。

List集合接口

常用方法
  • 增:add(Object obj)

  • 删:remove(int index)或remove(Object obj)

  • 改:set(int index, Object ele)

  • 查:get(int index)

  • 插:add(int index, object ele)

  • 长度:size()

  • 遍历:① Iterator迭代器方式
    ② 增强for循环
    ③ 普通的循环

    List list = new ArrayList();
    //添加
    list.add("a");
    list.add("b");
    list.add("c");
    list.remove(0);//删除第一个元素
    list.set(0, "a");//修改第一个元素
    System.out.println(list.get(list.size()-1));//得到最后一个元素
    list.add(2, "d");//在索引位置为2的位置插入元素d
    System.out.println(list);//a,c,d

重点来看集合遍历的三种方式

  • ① Iterator迭代器遍历的方式:

    	List list = new ArrayList();
    		//添加
    		list.add("a");
    		list.add("b");
    		list.add("c");
    		Iterator it = list.iterator();
    		while(it.hasNext()) {
    			String s = (String) it.next();
    			System.out.println(s);
    		}
    

    原理:Java集合之List集合详细解释

    图中可以看出从a开始查,有元素就next(),没有元素则停止while循环

  • ② 增强for循环实现

        List<String> list = new ArrayList<>();
        //添加
        list.add("a");
        list.add("b");
        list.add("c");
        for(String i: list) {
            System.out.println(i);		
        }
    
  • ③ 普通的循环

        List<String> list = new ArrayList<>();
        //添加
        list.add("a");
        list.add("b");
        list.add("c");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    

    同时我们也可以用```ListIterator``对象进行遍历:

    这是ListIterator的正向遍历

        List<String> list = new ArrayList<>();
        //添加
        list.add("a");
        list.add("b");
        list.add("c");
        //正向遍历
        ListIterator<String> lt = list.listIterator();
        while(lt.hasNext()) {
            String s = lt.next();
            System.out.println(s);
        }
    

    这是ListIterator的逆向遍历

        List<String> list = new ArrayList<>();
        //添加
        list.add("a");
        list.add("b");
        list.add("c");
        //正向遍历,如果没有这一步逆向遍历会为空,因为逆向遍历获取的是前一个元素,a的前面没有元素,所以为空
        ListIterator<String> lt = list.listIterator();
        while(lt.hasNext()) {
            lt.next();
        }
    	//逆向遍历
        while(lt.hasPrevious()) {//获取上一个元素
            String s = lt.previous();
            System.out.println(s);//输出的是cba
        }
    

ListIteratorIterator的区别:

  • ListIterator实现了Iterator,并且还可以包括其它的增加元素,替换,获取前一个或者后一个元素的索引等
  • ListIterator只能操作List,而Iterator能操作Set和List
  • ListIterator不仅可以正向遍历而且还可以逆向遍历

看一个ListIterator例子

判断集合中是否存在“java”,如果存在就在里面添加“javaee”

下面提供二种方式:

第一种方法,直接for循环遍历

    public static void main(String[] args) {
        List<String> l = new ArrayList<>();
        l.add("php");
        l.add("java");
        l.add("c");
        check(l);
    }
    //第一种方法,直接for循环遍历
    public static void check(List<String> list) {
        for(int i = 0; i<list.size(); i++) {
            if("java".equals(list.get(i))) {
                list.add("javaee");
            }
        }
        System.out.println(list);
    }

第二种方法,使用迭代器进行遍历添加

	public static void main(String[] args) {
		List<String> l = new ArrayList<>();
		l.add("php");
		l.add("java");
		l.add("c");
		check02(l);
		System.out.println(l);
	}	
	public static void check02(List<String> list) {
		ListIterator<String> li = list.listIterator();
		while(li.hasNext()) {
			String s = li.next();
			if("java".equals(s)) {
				list.add("javaee");
				break;
			}
		}
	}

如果把break去掉的话,添加并不会成功,因为添加是用list集合添加的,事先迭代器并不知道list添加了数据,这个异常是并发修改异常ConcurrentModificationException只要加个break将程序进行阻断然后再次遍历就会添加成功,当然也可以用迭代器进行遍历,添加直接交给迭代器处理,改成li.add("javaee")即可。

ArrayList

对于ArrayList.它的底层就是用数组进行实现的,因此它查询比较快,执行效率就会比较高,支持null值的存在,但是插入删除数据比较慢,还有它线程是不安全的,ArrayList继承AbstractList,实现List接口,它实现了Serializable接口,可以被序列化,实现RandomAccess实现随机访问,实现了Cloneable接口,能被克隆。

Java集合之List集合详细解释

关于JDK7与JDK8的原码对比

JDK7:

  • 初始化时,会默认创建容量为10 的数组ArrayList list = new ArrayList();

    private static final int DEFAULT_CAPACITY = 10
    
  • 当添加的数据超出容量为10时,就会实现自动扩容,扩容默认是原来的1.5倍,同时将原来的数组复制到新扩容的数组中。

JDK8:

  • 底层Object[] elementData初始化为{}.并没创建长度为10的数组
  • 第一次调用的时候再创建容量为10的数组,后续的添加与操作和JDK7无异
LinkedList

LinkedList是双向链表,底层是用链表进行实现的,特点就是删除添加快,效率高,查询比较慢,同样它也是线程不同步,不安全的。提供方法可以访问第一个和最后一个元素。

Vector

底层使用数组进行实现的,线程是安全的,查询块,添加删除慢,效率低下。jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。

在开发中基本不去使用,但是得知道!

特有功能:

  • addElement(E obj):添加元素

  • E elementAt(int index):指定索引位置处对应的元素值

  • Enumeration elements():获取集合所元素构成的一个枚举对象
    Enumeration里面方法
    hasMoreElements():是否更多元素
    nextElement():获取下个元素

        Vector<String> vector = new Vector<>();
        vector.add("a");
        vector.add("b");
        vector.add("c");
        //获取首元素
        System.out.println("首元素:"+ vector.elementAt(0));
        //获取集合元素构成的枚举对象
        Enumeration<String> enumeration = vector.elements();
        while(enumeration.hasMoreElements()) {
            String s = enumeration.nextElement();
            System.out.println(s);
        }
    

关于对List集合遍历的补充说明:

  • 对于实现了RandomAccess接口的List,首选for循环,后选foreach
  • 未实现RandomAccess接口的List,首选Iterator迭代器进行遍历,对于size比较大的,千万不要使用普通for循环

Queue队列

java中也提供了队列的实现方式,它是一种特殊的线性表,从一端进行插入称为入队,另一端取出数据称为出队,数据遵循先进先出(FIFO)规则,如果想要获取到后来的数据,就必须先获取到先入队的数据。

主要的成员方法:

  • boolean offer(E e):向队列末尾添加元素(入队)
  • E poll():获取并删除队列的首元素(出队)
  • E peek():获取队列的首元素

使用这些方法:

    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        queue.offer("a");
        queue.offer("b");
        queue.offer("c");
        System.out.println("首元素为:"+queue.peek());//a
        System.out.println(queue.poll());//返回第一个元素并删除a
        System.out.println(queue.element());//返回第一个元素b
        for (String string : queue) {
            System.out.println(string);
        }//b,c
        //删除队列操作
        while(!queue.isEmpty()) {
            System.out.println(queue.poll());
        }
        System.out.println(queue);//[]
    }

看看add与offer,remove与poll,peek与element的区别?

  • add与offer都是表示添加,有的队列就会有大小的限制,使用add就会报出异常,使用offer只会返回false

  • 当队列为空值remove会报出异常,poll会返回null,peek与element方法也是一样的

Deque队列

属于双向队列,对于两端都可以进行相应的操作,如果只是在一端进行操作就变成了栈(先进后出)

    Deque<String> deque = new LinkedList<>();
    deque.push("a");
    deque.push("b");
    deque.push("c");
    deque.push("d");
    System.out.println("首元素为:" + deque.peekFirst());
    //移除所有元素
    while(!deque.isEmpty()) {
        deque.pop();
    }
    System.out.println(deque);
    for (String string : deque) {
        System.out.println(string);
    }