集合

数组在查询和修改文件的时候效率是很快的,但是新增和删除效率不高,因为他们都是根据索引查值,这里我们用集合来替换数组

集合

理论上存储不同类型的不定量的元素数据。但是实际开发中还是存储相同类型的数据。且集合只能存储引用类型的数据

:泛型的标志 类型的自动转换 默认object,T就类似形式参数一样,是不确定的,但是必须是引用类型

集合里面使用泛型,表示集合数据有了限定,只能存储参数化的真实类型的数据

Collection

元素都是单值,加了泛型只能存一组类型相同的元素

子接口 元素是否有序 元素是否可重复
List 有序(所有元素都有索引位置) 可以
Set 无序 不可以

翻看Java的api文档可以找到Collection还有一个父接口:

public interface Collection extends Iterable

我们先来看他父接口Iterable:提供遍历集合元素的方式

常用方法:

1. iterator() //获得指定集合迭代器对象
    
2.forEach(Consumer<? super T> action)//循环遍历集合元素

Collection接口:常用方法:

增: add(E e)
        
删:
boolean remove(Object o) 从此集合中删除元素 
default boolean removeIf(Predicate<? super E> filter)删除满足条件的元素 
void clear() 清空集合的所有元素
    
改:由于Collection没有索引值,因此需要子接口或者类来实现该功能

查:
Iterable<E> iterator():
    hasNext()判断指针时候是否有更多元素需要迭代
    next() 获得光标之后的数据
    remove()删除当前元素数据
forEach(Consumer<? super T> action)
    
判断: 
boolean contains(Object o) 如果此集合包含指定的元素,则返回 true 
boolean isEmpty() 如果此集合不包含元素,则返回 true 

集合转数组:
Object[] toArray()  
<T> T[] toArray(T[] a) 

获得集合元素个数:int size() 
    
default Stream<E> stream() 串行化
default Stream<E> parallelStream() 并行化

讲了这么多方法,先拿个不带泛型的集合来用一下,注释都写在代码里面

//创建集合对象,接口,用多态创建对象
Collection collection = new ArrayList();
//此时没有加泛型,因此元素为object类型

//add
((ArrayList) collection).add(100);
((ArrayList) collection).add(100);
((ArrayList) collection).add(100);
((ArrayList) collection).add("hello");
((ArrayList) collection).add(false);
((ArrayList) collection).add(new Object());
System.out.println(collection.size());//7

//delete
collection.remove(100);//只删除一个100
System.out.println(((ArrayList) collection).size());//6

collection.removeIf(new Predicate() {
    @Override
    public boolean test(Object o) {
        return Integer.valueOf(100).equals(o);
    }
});

//我们用lamda格式来替换抽象方法
collection.removeIf(o->{
   return Integer.valueOf(100).equals(o);
});

上面我们只写了增删操作,下面我来实现遍历集合元素:

  1. 利用增强for来遍历我们元素,且遍历的同时我要删除某个元素
for(Object o : collection) {
    if(o.equals(100))collection.remove(o);
    System.out.println(o);
}

出现异常,ConcurrentModificationException 并发修改异常,说明普通for是不能进行增删改的操作的,但是可以遍历查询所有元素。

  1. 利用迭代器来遍历,并且修改某个值
//1、迭代器iterator()
//获得集合对象的迭代器对象
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
    Object object = iterator.next();
    if (new Integer(100).equals(object))
        iterator.remove();
}
System.out.println(collection);

输出正常,并且将条件满足的元素全部删除,该方法可行。

  1. foreach
collection.forEach(new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o);
    }
});

collection.forEach((Object obj)->{
    System.out.println(obj);
});

点开底层他用的也是增强for,所以还是不能进行增删改,说明在需要修改的时候,只能利用迭代器。

但是下面的遍历又可以拼接成功,因为下面是改变了list里面的值

Collection collection1=new ArrayList();
((ArrayList) collection1).add("hello");
((ArrayList) collection1).add(100);
((ArrayList) collection1).add(200);
//给集合中string类型的元素拼接world
collection1.forEach((Object obj)->{
    if (obj instanceof String) {
        ((ArrayList) collection1).set(0,obj.toString().concat("abc"));
            }
});
System.out.println(collection1);//[helloabc, 100, 200]

上面的是不带泛型的,下面就是带泛型的集合了:

涉及集合转数组:

<T> T[] toArray(T[] a)
Collection<Integer> collection=new ArrayList<>();
((ArrayList<Integer>) collection).add(100);
((ArrayList<Integer>) collection).add(200);
((ArrayList<Integer>) collection).add(300);
((ArrayList<Integer>) collection).add(400);

for (Integer integer : collection) {
    if(integer.equals(new Integer(100))) System.out.println(integer);
}

//集合转数组,使用相同类型的数组来接收,并且新数组一般长度给0
Integer[] integers = collection.toArray(new Integer[0]);
System.out.println(Arrays.toString(integers));

1、List

list也是一种接口,因此还是需要通过创建new一个接口实现类的方式(多态)来创建我们的对象

list的实现类有以下几个类,我们来比较一下:

接口实现类 数据结构(底层) 线程安全 效率
ArrayList 动态数组 查询/修改高,新增/删除慢
LinkedList 双向链表 新增/删除快,查询/修改慢
Vector 动态数组 性能一般
CopyOnWriteArrayList 动态数组 性能最慢
链表结构:
  单向链表:
     元素数据 记的是下一个元素的引用 next
  双向链表:
     元素数据 上一个元素引用prev,当前元素引用,下一个元素引用next

ArrayList

常用方法:

 new ArrayList<>();--初始容量为10
set(int index, E element);--修改
add(element);--新增
list.forEach(Consumer<? super T> action);--遍历
list.remove(index);--删除
list.subList(first index,last index);--截取元素
list.size();--元素个数
list.iterator()-->遍历
private static void demo4() {
    List<Integer> list = new ArrayList<>();
    list.add(100);
    list.add(200);
    list.add(3000);
    list.add(234);
    list.add(89);
    list.add(198);
    System.out.println(list);//[100, 200, 3000, 234, 89, 198]
    list.forEach(System.out::print);//100200300023489198
    System.out.println();
    for (int i = 0; i < list.size(); i++) {
       Integer obj= list.get(i);
        if(obj.equals(new Integer(89)))list.remove(i);
        if(obj.equals(new Integer(200)))list.set(i,200);
    }
    List<Integer> list1=list.subList(0,3);//操作list1会作用到原集合上面,此时原集合成为一种视图,只能遍历,不能增删改
    list.forEach(System.out::println);
    list.add(1000);//ConcurrentModificationException
    list.remove(0);//ConcurrentModificationException
    System.out.println(list1);
}

源码分析:

add代码,第一次扩容和它的源码

ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(100);
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
//此时size=0;
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!并且确保容量足够
    elementData[size++] = e;
    return true;
}

//此时minCapacity=1,elementdate和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是相等的空数组
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
     ensureExplicitCapacity(minCapacity);
}
private static final int DEFAULT_CAPACITY = 10;

//此时minCapacity=DEFAULT_CAPACITY=10,还要继续执行ensureExplicitCapacity(minCapacity);
protected transient int modCount = 0;//用于统计新增删除的次数
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//大于零执行下面grow扩容
            grow(minCapacity);
}


//此时minCapacity=10,传进来,判断是否需要扩容或者超过最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
//当空间10填满的时候会进行二次扩容,也就是此时minCapacity=11的时候,newCapacity等于10+10/2=15.第二次扩容的容量为15

remove代码和源码分析:

  1. 根据索引删除元素
ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(100);
arrayList.add(200);
arrayList.add(300);
arrayList.remove(2);
public E remove(int index) {//根据索引删除并且返回被删除的元素
    rangeCheck(index);//判断索引是否超出范围

    modCount++;//计算删除次数
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)//移位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}  
  1. 根据元素删除元素
arrayList.remove(Integer.valueOf(200));
public boolean remove(Object o) {//返回是否删除成功
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
//删除并移位
private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

取值get源码分析:

arrayList.get(1);
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

修改set源码分析:

arrayList.set(0,1);
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

构造源码分析:

ArrayList<Integer> arrayList=new ArrayList<>(10);
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

但是这个类是不安全的,当把他当作成员变量来使用的时候,会出现异常:

static ArrayList<Integer> integers = new ArrayList<>();

private static void demo8() {
    new Thread(() -> {
        for (int i = 0; i < 10000; i++) {
            integers.add(100);
        }
    }).start();
    new Thread(() -> {
        for (int i = 0; i < 10000; i++) {
            integers.add(100);
        }
    }).start();

}

异常:

Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 2776
	at java.util.ArrayList.add(ArrayList.java:459)
	at SetDemo.lambda$demo8$0(SetDemo.java:24)
	at java.lang.Thread.run(Thread.java:745)

LinkedList

底层是双向链表Node(Node prev, E element, Node next)维护,元素可以为null值

LinkedList<String> linkedList = new LinkedList();
linkedList.add("adv");
linkedList.add("hello");
linkedList.add(3, "java");
linkedList.addFirst("liku");
linkedList.addLast("abc");
linkedList.push("liku");//默认加到第一个元素

linkedList.remove();
linkedList.remove("abc");
linkedList.remove(4);
linkedList.removeFirst();
linkedList.removeLast();
linkedList.removeLastOccurrence("ha");
linkedList.removeFirstOccurrence("hello");

linkedList.indexOf("hello");//出现元素的第一个索引位
linkedList.lastIndexOf("hello");//出现元素的最后一个索引

linkedList.get(3);//得到指定索引位的元素
linkedList.getFirst();//得到第一个元素
linkedList.getLast();//得到最后一个元素

linkedList.peek();//得到第一个元素
linkedList.peekFirst();//得到第一个元素
linkedList.peekLast();//得到最后一个元素

linkedList.poll();//得到第一个元素并且把他从集合中移除
linkedList.pollFirst();//得到第一个元素并从一个中移除该元素;
linkedList.pollLast();//得到最后一个元素并移除

linkedList.pop();//liku

构造源码:

LinkedList<String> linkedList=new LinkedList<>();
/**
 * Constructs an empty list.
 */
public LinkedList() {
}

add源码:

linkedList.add("abc");
//第一次add的时候
public boolean add(E e) {
    linkLast(e);
    return true;
}
transient Node<E> first;//链表第一个,会记录上一个prev,当前结点,和下一个next结点元素
transient Node<E> last;//链表最后一个,默认null
 /**
     * Links e as last element.
     */
void linkLast(E e) {
        final Node<E> l = last;//null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;//最后一个结点为null,将新结点给第一个结点
        else
            l.next = newNode;
        size++;
        modCount++;
}
//这是一个双向链表,记录前一个元素和当前元素以及下一个元素
private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
//第二次add
public boolean add(E e) {
        linkLast(e);
        return true;
    }
transient Node<E> first;//"abc"
transient Node<E> last;//null
void linkLast(E e) {
        final Node<E> l = last;           "abc",第二次添加的元素,null
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;//将新增的元素放在上一个新增的元素的next
        size++;
        modCount++;
    }
private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

linkedList.add(2,"adm");
public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));//放在索引所在结点之前
}
//检查位置索引是否越界,越界直接抛异常
private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
//检索索引对应的元素值
Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
//将e元素放入索引所在结点前面
void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

删除remove源码:

public E remove(int index) {
    checkElementIndex(index);//判断是否越界
    return unlink(node(index));
}
//将index后面的元素左移,x的下一个变成x的位置,并且解绑x与他前后的关系
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

get源码:

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

set源码:

public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

但是作为成员变量,线程也是不安全的

Vector

底层是synchronized修饰的,底层是同步的,线程安全,但是很慢,可以将线程放在集合里面等线程走完

static Vector<Integer> vector = new Vector<>();

    private static void demo9() {
        ArrayList<Thread> threads = new ArrayList<>();
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 4; i++) {
            threads.add(new Thread(() -> {
                for (int i1 = 0; i1 < 100000; i1++) {
                    vector.add(100);
                }
            }));
        }
        for (Thread thread : threads) {
            thread.start();
        }
        threads.forEach((thread) -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end= System.currentTimeMillis();
        System.out.println(end-begin);//77毫秒
    }

vector是二倍扩容方式,当容量为20的时候再次扩容为2倍40

CopyOnWriteArrayList

线程是安全的,但是速度是最慢的

static CopyOnWriteArrayList<Integer> vector = new CopyOnWriteArrayList<>();
private static void demo9() {
    ArrayList<Thread> threads = new ArrayList<>();
    long begin = System.currentTimeMillis();
    for (int i = 0; i < 4; i++) {
        threads.add(new Thread(() -> {
            for (int i1 = 0; i1 < 10000; i1++) {
                vector.add(100);
            }
        }));
    }
    for (Thread thread : threads) {
        thread.start();
    }
    threads.forEach((thread) -> {
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    long end= System.currentTimeMillis();
    System.out.println(end-begin);//1043
}

2、Set

元素唯一且无序,用该集合需要重写equals和hashcode。实现类的区别:

接口实现类 数据结构 线程安全 元素是否可以为null
HashSet HashMap(数组+单向链表+红黑树) 可以
LinkedHashSet HashMap 可以
TreeSet 红黑树 TreeMap 不可以
CopyOnWriteArraySet CopyOnWriteArrayList 可以

HashSet

HashSet() //构造一个新的空集合实例,具有默认初始容量(16)和负载因子(0.75)
HashSet(int initialCapacity) //创建指定容量空集合 负载因子0.75
HashSet(int initialCapacity, float loadFactor)//指定容量和负载因子

其他方法跟collection一样,但是元素唯一且无序,一般不会给太高容量或太低负载,而且可以给null值

HashSet<Integer> hashSet=new HashSet<>();
hashSet.add(234);
hashSet.add(null);
Iterator<Integer> integers=hashSet.iterator();
while (integers.hasNext()){
    System.out.println(integers.next());
}

LinkedHashSet

元素有序,新增的顺序与遍历顺序是一致的,且可以为空

LinkedHashSet()
LinkedHashSet(int initialCapacity)
LinkedHashSet(int initialCapacity, float loadFactor)
private static void demo1() {
    LinkedHashSet<Integer> linkedHashSet=new LinkedHashSet<>();
    linkedHashSet.add(100);
    linkedHashSet.add(null);
    System.out.println(linkedHashSet);
}

方法都是继承的,没什么区别,唯一的区别就是元素有序

TreeSet

排序,元素都是有序的,按照自然顺序进行排列,升序排列,不能为null,

TreeSet(Comparator<? super E> comparator)自定义比较器
TreeSet<Integer> treeSet=new TreeSet<>();
//treeSet.add(null);
treeSet.add(7);
treeSet.add(89);
treeSet.add(8);
treeSet.add(2);
treeSet.first();//2
treeSet.last();//89
System.out.println(treeSet);//[2, 7, 8, 89]

因为上面的包装类是实现了comparable接口public final class Integer extends Number implements Comparable ,那我们看下面:

我写了这样一个用户类:

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
    private String name;
    private Integer id;

}

并且将他放到集合里面:

private static void demo3() {
    TreeSet<User> treeSet=new TreeSet();
    treeSet.add(new User("liku",35));
    treeSet.add(new User("mark",35));
    treeSet.add(new User("lily",35));
    treeSet.add(new User("lucy",35));
    System.out.println(treeSet);
}

由于user对象并没有实现Comparable接口,因此会报异常:

listdemo.User cannot be cast to java.lang.Comparable

索引在用户类上面我们需要实现Comparable接口,比较对象为用户类型,重写里面的compare方法:

@Override
public int compareTo(User user) {
    return this.id.compareTo(user.id);
    //因为integer实现了compareTo,因此用compareTo来进行比较
}

这是跟着编号降序排列的,来但是这时候会出现一个问题,就是当id一样的时候,treeset会认为他们是同一个对象,不会保留重复元素,因此此时输出treeset他还是只有一个元素,

[User(name=liku, id=35)]

因为此时集合是按照编号排序并且编号不允许重复,编号不同的时候就是不同的元素了

TreeSet<User> treeSet=new TreeSet();
treeSet.add(new User("liku",35));
treeSet.add(new User("mark",5));
treeSet.add(new User("lily",37));
treeSet.add(new User("lucy",3));
System.out.println(treeSet);
//[User(name=lucy, id=3), User(name=mark, id=5), User(name=liku, id=35), User(name=lily, id=37)]

将数组转成集合:

Arrays.asList();
返回的是一个视图,不可更改,

/**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with {@link Collection#toArray}.  The returned list is
     * serializable and implements {@link RandomAccess}.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List&lt;String&gt; stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

如果要对返回的集合进行操作需要将返回的数组new一遍,并且覆盖返回的集合

CopyOnWriteArraySet

下面写一个小泛型:

public class DynamicArray<E> {
    private static final int DEFAULT_CAPACITY=10;
    private int size;
    private Object[] elementData;

    public DynamicArray() {
        this.elementData=new Object[DEFAULT_CAPACITY];
    }
    private void ensureCapacity(int capacity){
        int oldCapacity=elementData.length;
        if(oldCapacity>capacity)return;
        int newCapacity=capacity*2;
        if(newCapacity<capacity)newCapacity=capacity;
        elementData=Arrays.copyOf(elementData,newCapacity);
    }
    public void add(E e){
        ensureCapacity(size+1);
        elementData[size++]=e;
    }
    public E get(int index){
        return (E) elementData[index];
    }
    public int size(){
        return size;
    }
    public E set(int index,E element){
        E oldValue=get(index);
        elementData[index]=element;
        return oldValue;
    }
}
posted @ 2022-11-18 22:26  Liku007  阅读(22)  评论(0编辑  收藏  举报