JAVA中的List的使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA中的List的使用相关的知识,希望对你有一定的参考价值。

List<E>([]内的内容可省略),与数组类似:
实例化:List[<数据类型>] list = new ArrayList[<数据类型>]();
获得集合内元素个数:list.size();

添加元素:
默认添加:list.add(e);
指定下标添加(添加后下标后的元素向后挪一位):list.add(index,e);

删除元素:
返回是否删除:list.remove(e);
直接删除指定下标的元素(只删除找到的第一个相符合的元素):list.remove(index);

替换元素(替换掉指定下标的元素):list.set(index,e);

取出元素:list.get(index);

清空集合:list.clear();

判断集合中是否存在某个元素(存在返回true,不存在返回false):list.contains(e);

对比两个集合中的所有元素:
两个对象一定相等:list.equals(list2);
两个对象不一定相等:list.hashCode() == list2.hashCode();
(两个相等对象的equals方法一定为true, 但两个hashcode相等的对象不一定是相等的对象。)

获得元素下标:
元素存在则返回找到的第一个元素的下标,不存在则返回-1:list.indexOf(e);
元素存在则返回找到的最后一个元素的下标,不存在则返回-1:list.lastIndexOf(e);

判断集合是否为空(空则返回true,非空则返回false):list.isEmpty();

返回Iterator集合对象:list.iterator();

将集合转换为字符串:list.toString();

截取集合(从fromIndex开始在toIndex前结束,[fromIndex,toIndex)):list.subList(fromIndex,toIndex);

将集合转换为数组:
默认类型:list.toArray();
指定类型(objects为指定类型的数组对象,并将转换好的数组赋值给objects数组):list.toArray(objects);

以上为List常用的方法。
参考技术A import java.util.*;

public class ListIteratorTest

public static void main(String[] args)
List list = new ArrayList();
//添加数据
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println("下标0开始:"+list.listIterator(0).next());//next()
System.out.println("下标1开始:"+list.listIterator(1).next());
System.out.println("子List 1-3:"+list.subList(1,3));//子列表
ListIterator it = list.listIterator();//默认从下标0开始
//隐式光标属性add操作 ,插入到当前的下标的前面
it.add("sss");
//循环输出数据
while(it.hasNext())
System.out.println("next Index="+it.nextIndex()+",Object="+it.next());

//set属性
ListIterator it1 = list.listIterator();
it1.next();
it1.set("ooo");
ListIterator it2 = list.listIterator(list.size());//下标
while(it2.hasPrevious())
System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());


参考技术B 你学习数据结构就知道怎样使了。 。

debug底层java代码,对list中的数据去重的正确姿势,及对比java list remove正确使用方法与错误使用

前言

List中主要有2种数据结构的实现,一种是数组,另一种是链表。

数组,均实现RandomAccess类

数组的实现有ArrayList、Vector、Stack、CopyOnWriteArrayList他们都继承了AbstractList,AbstractList实现了List中的部分接口,并且实现了数组结构的遍历,也就是上边说到的迭代器设计模式中Iterator部分,Stack实现了一个LIFO,Vector实现了线程安全,CopyOnWriteArrayList则是线程安全的一个变体,读的操作没有锁性能会更好,而写的时候存在锁并且在写的时候copy元数据,在新的数据上做修改,然后在同步回元数据,从而实现了在遍历的时候,可以对其进行修改而不会抛出CurrentModificationException异常。

链表,未实现RandomAccess类

LinkedList底层使用Entry类来存放数据元素。链表,就是手拉手,通过一个元素可以知道上一个元素和下一个元素,链表的遍历方式实现和数组是不一样的, 构造方法中初始化了next的值,if(int < (size >> 1))这里介绍下>>和>>>符号,>>代表向右移位。

例如3>>1则代表二进制0011向右移动1位,则是0001最终结果就是1,8>>>3代表8除以2的3次方最终结果则是1。

这里的if(int < (size >> 1)),主要是为了判断这个元素时前半段还是在后半段,size >> 1相当于size/2,主要是为了更快的定位链表第index元素的位置,不用从头遍历,这就是迭代器设计模式的好处,隐藏了内部实现的细节,对不同的数据结构提供了统一的迭代接口。

准备测试list的主方法

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            list.add("shanghai");
            list.add("shanghai");
            list.add("guangzhou");
            list.add("shenzhen");
            list.add("shanghai");
            list.add("hangzhou");
            list.add("shanghai");
            list.add("beijing");
            list.add("shanghai");
            list.add("shanghai");
            list.add("guangzhou");
            list.add("shenzhen");
            list.add("shanghai");
            list.add("hangzhou");
            list.add("shanghai");
        }

//        print(list);
        System.out.println("\\n");
        removeSetRepeat(list); // 9ms  25ms
//        removeStrRepeat(list);  //19ms  78ms
        System.out.println("\\n");
//        removeRowRepeat(list);  //9ms  24ms
    }

    private static void print(List<String> list) {
        System.out.print("item:");
        for (String item : list) {
            System.out.print(item+":");
        }
    }

for循环去重对比

StringBuilder拼接去重

    /*
     * 正确 StringBuilder去重
     */
    public static void removeStrRepeat(List<String> list){
        StopWatch sw = new StopWatch();
        sw.start();
        StringBuilder stringBuilder = new StringBuilder("x");
        for(int i = list.size() - 1; i >= 0; i--){
            String item = list.get(i);
            if(item == null || stringBuilder.toString().contains(item)){
                list.remove(i);
                continue;
            }
            stringBuilder.append(item).append("x");
        }
        print(list);
        sw.stop();
        System.out.println("----:" + sw.getTotalTimeMillis()+"ms");
    }
StringBuilder去重 14万条数据19ms,140万条数据 78ms

 ArrayList去重

    /*
     * 正确 ArrayList去重 14万条数据9ms,140万条数据 24ms
     */
    public static void removeRowRepeat(List<String> list){
        StopWatch sw = new StopWatch();
        sw.start();
        List<String> rowx = new ArrayList<>();
        for(int i = list.size() - 1; i >= 0; i--){
            String item = list.get(i);
            if(item == null || rowx.contains(item)){
                list.remove(i);
                continue;
            }
            rowx.add(item);
        }
        print(list);
        sw.stop();
        System.out.println("----:" + sw.getTotalTimeMillis()+"ms");
    }
ArrayList数组去重 14万条数据9ms,140万条数据 24ms。

set去重

/*
     * 正确 HashSet去重 14万条数据9ms,140万条数据 25ms
     */
    public static void removeSetRepeat(List<String> list){
        StopWatch sw = new StopWatch();
        sw.start();
        Set<String> set = new HashSet<>(list);
        print(new ArrayList<>(set));
        sw.stop();
        System.out.println("----:" + sw.getTotalTimeMillis()+"ms");
    }
Set去重 14万条数据9ms,140万条数据 25ms,与ArrayList数组for循环去重不相上下。

for循环size类型的remove

错误案例

IndexOutOfBoundsException索引越界

 public static void remove11(List<String> list, String target) {
        int size = list.size();
        for (int i = 0; i < size; i++) {
            String item = list.get(i);
            if (target.equals(item)) {
                list.remove(item);
            }
        }
        print(list);
    }

index从0开始,remove不能完全删除目标关键词shanghai----逆行撞车

由于index一直增加,而list中删除一次,list的size减小,导致list.get(i)可能忽略其中一个元素
  /*
     * 错误  index从0开始,remove不能完全删除目标关键词shanghai
     * 由于index一直增加,而list中删除一次,list.get(i)可能忽略其中一个元素
     */
    public static void remove12(List<String> list, String target) {
        //item:beijing:shanghai:shanghai:guangzhou:shenzhen:hangzhou:
        for (int i = 0; i < list.size(); i++) {
            String item = list.get(i);
            System.out.println("----:" + item);
            if (target.equals(item)) {
                list.remove(item);
            }
        }
        print(list);
    }

正确案例

相向而行--list.remove,不会出错

size 倒着衰减,index 也是从最大值开始衰减,两者相向而行,故list.remove,不会出错
public static void remove13(List<String> list, String target) {
        int size = list.size();
        for (int i = size - 1; i >= 0; i--) {
            String item = list.get(i);
            if (target.equals(item)) {
                list.remove(item);
            }
        }
        print(list);
    }

public static void remove14(List<String> list, String target){
        for(int i = list.size() - 1; i >= 0; i--){
            String item = list.get(i);
            if(target.equals(item)){
                list.remove(item);
            }
        }
        print(list);
}

对象遍历list--foreach 写法--for(String item : list)

错误-->for(String item : list)进行remove造成ConcurrentModificationException

对象遍历list,list.remove,造成list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了ConcurrentModificationException异常。

    /*
     * 错误 对象遍历list,list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了ConcurrentModificationException异常。
     */
    public static void remove21(List<String> list, String target){
        for(String item : list){
            System.out.print(item+":");
            if(target.equals(item)){
                list.remove(item);
            }
        }
        print(list);
    }

正确-->CopyOnWriteArrayList 解决了 List的并发问题

public static void remove22(ArrayList<String> list, String target) {
        final CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<String>(list);
        for (String item : cowList) {
            if (item.equals(target)) {
                cowList.remove(item);
            }
        }
        print(cowList);
    }

每个CopyOnWriteArray List对象,里面有一个array 数组对象用来存放具体元素, ReentrantLock 独占锁对象,用来保证同时只有一个线程 对 array进行修改。

并发包中的并发List只有CopyOnWriteArrayList,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作,都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略(add/set/remove(修改)时,复制新数组进行操作)。

原理:采用每add一个元素,则复制出一个新数组,此时如果多线程下,add/set/remove操作时,用新数组get操作用旧数组,这样就不用对get进行加锁,但add/set/remove还是要加锁的(独占锁lock)。此时就形成了所谓的读写分离的状态

CopyOnWriteArrayList的复制操作,导致了其存在以下问题:

  1. 存在内存占用的问题,因为每次对容器结构进行修改的时候,都要对容器进行复制,这么一来我们就有了旧有对象新入的对象,会占用两份内存。如果对象占用的内存较大,就会引发频繁的垃圾回收行为,降低性能;
  2. CopyOnWrite只能保证数据最终的一致性,不能保证数据的实时一致性。

比如:一个线程正在对容器进行修改,另一个线程正在取容器的内容,这其实是两个容器数组,那么读线程读到的是旧数据

所以CopyOnWriteArrayList 集合比较适用于读操作远远多于写操作的场景。

list.iterator迭代遍历

错误案例  list.iterator删除报ConcurrentModificationException

  /*
     * 错误案例  list.iterator删除报ConcurrentModificationException
     */
    public static void remove31(List<String> list, String target){
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            String item = iter.next();
            if (item.equals(target)) {
                list.remove(item);
            }
        }
        print(list);
    }

产生java.util.ConcurrentModificationException异常。list.iterator写法实际上是对的 Iterable、hasNext、next方法的简写。因此我们从list.iterator()着手分析,跟踪iterator()方法,该方法返回了 Itr 迭代器对象。

正确案例--用list.iterator中的内部类Itr进行删除

   /*
     * 正确 用list.iterator中的内部类Itr进行删除
     */
    public static void remove32(List<String> list, String target){
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            String item = iter.next();
            if (item.equals(target)) {
                iter.remove();
            }
        }
        print(list);
    }
用list.iterator中的内部类Itr进行删除,不会修改ArrayList的modCount的值,故而modCount != expectedModCount永远为false

 

以上是关于JAVA中的List的使用的主要内容,如果未能解决你的问题,请参考以下文章

debug底层java代码,对list中的数据去重的正确姿势,及对比java list remove正确使用方法与错误使用

java List集合如何使用两个实体类中的参数方法?

Java中的Collection和Map--List体系

Java中自定义对象使用Collections工具类中的Sort方法

Java—List的使用数据结构

Java基础_List集合