JAVA——集合

目录

集合纲要

什么是集合?有什么用?

集合继承结构图_Collection 部分​

 集合继承结构图_Map 部分 ​

实现类特征总结:

超级接口Collection

Collection接口的常用方法 

集合迭代Iterator(*****)

 Iterator迭代器对象中的方法

迭代器执行原理

深入contains方法 

深入remove方法

迭代器获取位置(***)

总结

List接口

List接口元素特点 

 List接口中常用方法

add(int index, Object element)方法

get(int index)方法

int indexOf(Object o)方法

int lastIndexOf(Object o)方法

Object remove(int index)     

Object set(int index, Object element)

增删查改单词

ArrayList

ArrayList容量

位运算符  << , >>  

ArrayList集合的扩容

ArrayList集合总结

LinkedList

单向链表数据结构

 LinkedList也有下标

双向链表数据结构 

链表总结

Vector


集合纲要

什么是集合?有什么用?

 1、 数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据。

        集合为什么说在开发中使用较多?
            集合是一个容器,是一个载体,可以一次容纳多个对象。
            在实际开发中,假设连接数据库,数据库当中有10条记录,
            那么假设把这10条记录查询出来,在java程序中会将10条
            数据封装成10个java对象,然后将10个java对象放到某一个
            集合当中,将集合传到前端,然后遍历集合,将一个数据一个
            数据展现出来。

2、集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,
    集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
        list.add(100); //自动装箱Integer
        注意:
            集合在java中本身是一个容器,是一个对象。
            集合中任何时候存储的都是“引用”。

3、在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。

什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:
        数组、二叉树、链表、哈希表...
        以上这些都是常见的数据结构。

        你往集合c1中放数据,可能是放到数组上了。
        你往集合c2中放数据,可能是放到二叉树上了。
        .....
        你使用不同的集合等同于使用了不同的数据结构。

        你在java集合这一章节,你需要掌握的不是精通数据结构。java中已经将数据结构实现了,已经写好了这些常用的集合类,你只需要掌握怎么用?在什么情况下选择哪一种合适的集合去使用即可。

        new ArrayList(); 创建一个集合,底层是数组。
        new LinkedList(); 创建一个集合对象,底层是链表。
        new TreeSet(); 创建一个集合对象,底层是二叉树。
        ..... 

4、集合在java JDK中哪个包下?
      java.util.*;
所有的集合类和集合接口都在java.util包下

在API文档中可以看到Collection这个超级接口并且继承Iterable(迭代接口,父接口)

集合继承结构图_Collection 部分

此图非常重要!!

5、在java中集合分为两大类:
        一类是单个方式存储元素:
            单个方式存储元素,这一类集合中超级父接口:java.util.Collection;

        一类是以键值对儿的方式存储元素
            以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;


 集合继承结构图_Map 部分 

Map集合和Collection集合之间没有关系


实现类特征总结:

ArrayList:底层是数组
LinkedList:底层是双向链表
Vector:底层是数组,线程安全的,效率低,使用较少
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了
TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了
HashMap:底层是哈希表。
Hashtable:底层是哈希表,线程安全的,效率低,使用较少
Properties:线程安全的,并且key和value只能存储字符串String
TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序

List集合存储元素的特点:
    有序可重复
    有序:存进去的顺序和取出的顺序相同,每一个元素都有下标。
    可重复:存进去1 ,可以再存储一个1
Set(Map)集合存储的特点:
    无序不可重复
    无序:存进的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标
    不可重复:存进去1,不能再存储1了
SortedSet(SortMap)集合存储的特点:
    首先是无序不可重复的,但SortedSet集合中元素是可以排序的
    无序:存进的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标
    不可重复:存进去1,不能再存储1了
    可排序:可以按照大小顺序排列。

    Map集合的key就是一个Set集合
    往Set集合中放数据,实际上放到了Map集合的key部分。


超级接口Collection

Collection接口的常用方法 

 1、Collection中能存放什么元素?
        没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
        使用了“泛型”之后,Collection中只能存储某个具体的类型。
        集合后期我们会学习“泛型”语法。目前先不用管。Collection中什么都能存,
        只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存
        java对象,只是存储java对象的内存地址。)
    2、Collection中的常用方法

  • boolean add(Object e) 向集合中添加元素
  • int size()  获取集合中元素的个数
        // 创建一个集合对象
        //Collection c = new Collection(); // 接口是抽象的,无法实例化。
        // 多态
        Collection c = new ArrayList();
        // 测试Collection接口中的常用方法
        c.add(1200); // 自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
        c.add(3.14); // 自动装箱
        c.add(new Object());
        c.add(new Student());
        c.add(true); // 自动装箱

        // 获取集合中元素的个数
        System.out.println("集合中元素个数是:" + c.size()); // 5
  • void clear() 清空集合
        // 清空集合
        c.clear();
        System.out.println("集合中元素个数是:" + c.size()); // 0
  • boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
        // 再向集合中添加元素
        c.add("hello"); // "hello"对象的内存地址放到了集合当中。
        c.add("world");
        c.add("浩克");
        c.add("绿巨人");
        c.add(1);

        // 判断集合中是否包含"绿巨人"
        boolean flag = c.contains("绿巨人");
        System.out.println(flag); // true
  • boolean remove(Object o) 删除集合中的某个元素。
        System.out.println("集合中元素个数是:" + c.size()); // 5

        // 删除集合中某个元素
        c.remove(1);
        System.out.println("集合中元素个数是:" + c.size()); // 4
  • boolean isEmpty()  判断该集合中元素的个数是否为0
        // 判断集合是否为空(集合中是否存在元素)
        System.out.println(c.isEmpty()); // false
        // 清空
        c.clear();
        System.out.println(c.isEmpty()); // true(true表示集合中没有元素了!)
  • Object[] toArray()  调用这个方法可以把集合转换成数组。【作为了解,使用不多。】 
Object[] objs = c.toArray();
        for(int i = 0; i < objs.length; i++){
            // 遍历数组
            Object o = objs[i];
            System.out.println(o);
        }

集合迭代Iterator(*****)

 Iterator迭代器对象中的方法

                boolean hasNext()如果仍有元素可以迭代,则返回 true。
                Object next() 返回迭代的下一个元素。

【注意】:以下的遍历方式/迭代方式,是所有Collection通用的一种方式。
在Map集合中不能用。在所有的Collection以及子类中使用。

第一步:获取集合对象的迭代器对象Iterator

Iterator it = c.iterator();

第二步:通过以上获取的迭代器对象开始迭代/遍历集合。

while(it.hasNext()){
       Object obj = it.next();
       System.out.println(obj);
 }

 不管你当初存进去什么,取出来统一都是Object。


迭代器执行原理

 迭代器迭代过程图:

由图可知:迭代器初始位置并没有指向元素(类似栈中的栈帧的初始位置为int index = -1;一样),因此 next()方法是返回下一个元素。

迭代器通过next()方法进行一步一步迭代,而next()方法能使得迭代器对象移动的原理:

next()源码:

public E next() {
    E item = nextItem;
    if (item == null) throw new NoSuchElementException();  //防止迭代时死循环
    advance();        //前进
    return item;
}

深入contains方法 

    boolean contains(Object o)
        判断集合中是否包含某个对象o
        如果包含返回true, 如果不包含返回false。

contains方法是用来判断集合中是否包含某个元素的方法,
那么它在底层是怎么判断集合中是否包含某个元素的呢?
        调用了equals方法进行比对。
        equals方法返回true,就表示包含这个元素。

        // 创建集合对象
        Collection c = new ArrayList();

        // 向集合中存储元素
        String s1 = new String("abc"); // s1 = 0x1111
        c.add(s1); // 放进去了一个"abc"

        String s2 = new String("def"); // s2 = 0x2222
        c.add(s2);

        // 集合中元素的个数
        System.out.println("元素的个数是:" + c.size()); // 2

        // 新建的对象String
        String x = new String("abc"); // x = 0x5555

        System.out.println(c.contains(x)); //判断集合中是否存在"abc" true
ArrayList中contains方法源码:

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

---------------------------------------------------------------------------------------------

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

分析可知contains()方法在判断时调用了equals方法,如果比较的内容是对象,对象中的equals方法必须要重写,不重写默认调用Object类中的原始equals方法比较是内存地址而不是内容,因此存放在一个集合中的类型,一定要重写equals方法。


深入remove方法

        // 创建集合对象
        Collection cc = new ArrayList();
        // 创建字符串对象
        String s1 = new String("hello");
        // 加进去。
        cc.add(s1);

        // 创建了一个新的字符串对象
        String s2 = new String("hello");
        // 删除s2
        cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。
        // 集合中元素个数是?
        System.out.println(cc.size()); // 0

ArrayList中remove方法源码:

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;
}

根据源码分析,remove方法中使用了equals方法,而s1,s2为String类型,String已经重写过equals方法,因此此时执行remove方法时认为s1和s2是相等的,删除s2就是删除s1,会将二者删除。 

因此存放在一个集合中的类型,一定要重写equals方法。


迭代器获取位置(***)

        Collection c = new ArrayList();
        Iterator it = c.iterator();
    
        // 添加元素
        c.add(1); // Integer类型
        c.add(2);
        c.add(3);

        // 获取迭代器
        //Iterator it = c.iterator();
        while(it.hasNext()){
            // 编写代码时next()方法返回值类型必须是Object。
            // Integer i = it.next();
            Object obj = it.next();
            System.out.println(obj);
        } //运行异常!!

注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。
一定要注意:集合结构只要发生改变,迭代器必须重新获取。
当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException

        Collection c2 = new ArrayList();
        c2.add("abc");
        c2.add("def");
        c2.add("xyz");

        Iterator it2 = c2.iterator();
        while(it2.hasNext()){
            Object o = it2.next();
            
            //运行异常   
            c2.remove(o); 

        }

删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:java.util.ConcurrentModificationException
出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)直接通过集合去删除元素,没有通知迭代器。(导致迭代器的快照和原集合状态不同。)

使用迭代器来删除可以吗? 

        Iterator it2 = c2.iterator();
        while(it2.hasNext()){
            Object o = it2.next();

            // 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。
            it2.remove(); // 删除的一定是迭代器指向的当前元素。
            System.out.println(o);
        }

        System.out.println(c2.size()); //0

 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。相当于迭代器在迭代前对集合拍了一个快照,对快照内容进行迭代,但如果用集合的删除方法修改内容,迭代器并不知道,于是在对照时会发生异常;而使用迭代器的删除方法修改内容,相当于迭代器在快照上进行修改,并且在对照时也对原集合对应位置进行对照修改,这样就不会出现异常。

总结

重点:当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现
异常:java.util.ConcurrentModificationException 

重点:在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:
c.remove(o); 迭代过程中不能这样。会出现:java.util.ConcurrentModificationException

重点:在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素,
不要使用集合自带的remove方法删除元素。 


List接口

List接口元素特点 

List集合存储元素特点:有序可重复
        有序:List集合中的元素有下标。
        从0开始,以1递增。
        可重复:存储一个1,还可以再存储1

 List接口中常用方法

List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:
        以下只列出List接口特有的常用的方法:
            void add(int index, Object element)
            Object set(int index, Object element)
            Object get(int index)
            int indexOf(Object o)
            int lastIndexOf(Object o)
            Object remove(int index)

以ArrayList为例:

add(int index, Object element)方法

        // 创建List类型的集合。
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素。
        myList.add("B");
        myList.add("C");
        myList.add("D");

        //获取迭代器
        Iterator it = myList.iterator();
        while(it.hasNext()){
            Object elt = it.next();
            System.out.println(elt);
        }

其输出顺序为:A B C D

 如果使用add(int index , Object element);方法

        myList.add("A"); 
        myList.add("B");
        myList.add("C");
        myList.add("D");
myList.add(1, "KING");

其输出顺序为:A  KING B C D 

在列表的指定位置插入指定元素(第一个参数是下标)
这个方法使用不多,因为对于ArrayList集合来说效率比较低。 


get(int index)方法

根据下标获取元素 

        Object firstObj = myList.get(0);
        System.out.println(firstObj);
        //A

因为有下标,所以List集合有自己比较特殊的遍历方式
通过下标遍历。【List集合特有的方式,Set没有。】

        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }

int indexOf(Object o)方法

获取指定对象第一次出现处的索引。

        System.out.println(myList.indexOf("C")); // 3 

int lastIndexOf(Object o)方法

myList.add(4,C)
System.out.println(myList.lastIndexOf("C")); // 4

Object remove(int index)     

删除指定下标位置的元素

        // 删除下标为0的元素
        myList.remove(0);

Object set(int index, Object element)

修改指定位置的元素 

myList.set(2, "Soft");

增删查改单词

    增删改查这几个单词要知道:
        增:add、save、new
        删:delete、drop、remove
        改:update、set、modify
        查:findget、query、select


ArrayList

ArrayList容量

ArrayList源码:

private static final int DEFAULT_CAPACITY = 10;

ArrayList集合初始化容量10

transient Object[] elementData;

  ArrayList底层是Object类型的数组。  

        // 数组的长度是10
        List list1 = new ArrayList();
        // 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
        System.out.println(list1.size()); // 0

在新版本JDK(8以上)中有这么规定:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = { };

 源码注释:

We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.

 说明底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。


位运算符  << , >>  

System.out.println(10 >> 1);//5

上述表示>> 1 二进制右移1位。右移1位其实就是除以2

10的二进制位是:         00001010  【10】

10的二进制右移1位是:00000101  【5】

<< 1表示二进制左移1位。左移1位其实就是乘以2 

ArrayList集合的扩容

 add() 源码:

int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity,oldCapacity >> 1);

说明增加量▲为原来的0.5倍 总体相当于扩容到原容量的11.5倍


ArrayList集合总结

    1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
    2、集合底层是一个Object[]数组。
    3、构造方法:
        new ArrayList();
        new ArrayList(20);
    4、ArrayList集合的扩容:
        增长到原容量的1.5倍。
        ArrayList集合底层是数组,怎么优化?
            尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合
            的时候预估计元素的个数,给定一个初始化容量。
    5、数组优点:
        检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
        然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
    6、数组缺点:
        随机增删元素效率比较低。
        另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
    7、向数组末尾添加元素,效率很高,不受影响。
    8、面试官经常问的一个问题?
        这么多的集合中,你用哪个集合最多?
            答:ArrayList集合。
            因为往数组末尾添加元素,效率不受影响。
            另外,我们检索/查找某个元素的操作比较多。

    7、ArrayList集合是非线程安全的。(不是线程安全的集合。)


LinkedList

单向链表数据结构

单链表中的节点。
节点是单向链表中基本的单元。
每一个节点Node都有两个属性:
    一个属性:是存储的数据
    另一个属性:是下一个节点的内存地址

public class Node {

    // 存储的数据
    Object data;

    // 下一个节点的内存地址
    Node next;

    public Node(){

    }

    public Node(Object data, Node next){
        this.data = data;
        this.next = next;
    }
}

 链表类。(单向链表) 


 LinkedList也有下标

        List list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");

        for(int i = 0; i <list.size(); i++){
            Object obj = list.get(i);
            System.out.println(obj);
        }

注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。


双向链表数据结构 

LinkedList定义源码 :

transient int size = 0;

//Pointer to first node
transient Node<E> first;

//Pointer to last node
transient Node<E> last;

 整个LinkedList add()方法运行过程:

add("a");方法源码:

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null); //(Node是一个静态的内部类last = newNode;
    if (l == null)
        first = newNode;
    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;
    }
}

 过程原理图:

由源码可知:

LinkedList集合没有初始化容量。
最初这个链表中没有任何元素。first和last引用都是null。
不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。
因为我们要面向接口编程,调用的方法都是接口中的方法。 

        List list2 = new ArrayList(); // 这样写表示底层你用了数组。
        List list2 = new LinkedList(); // 这样写表示底层你用了双向链表。

        // 以下这些方法你面向的都是接口编程。
        list2.add("123");
        list2.add("456");
        list2.add("789");

        for(int i = 0; i < list2.size(); i++){
            System.out.println(list2.get(i));
        }

 上述两种创建链表的方法都可以,以下方法均面向接口,上面的对象如果进行修改,下面的内容则不会变动。


链表总结

链表的优点:
    由于链表上的元素在空间存储上内存地址不连续。
    所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
    在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
    使用LinkedList。

链表的缺点:
    不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
    节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率
    较低。

    ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
    LinkedList:把随机增删发挥到极致。
    加元素都是往末尾添加,所以ArrayList用的比LinkedList多。


Vector

    1、底层也是一个数组。
    2、初始化容量:10

public Vector() {
    this(10);

    3、怎么扩容的?
        扩容之后是原容量的2倍。
        10--> 20 --> 40 --> 80

源码注释:
the capacity of the vector is doubled each time it needs to grow. 

    4、ArrayList集合扩容特点:
        ArrayList集合扩容是原容量1.5倍。

    5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,
    是线程安全的。效率比较低,使用较少了。

        // 创建一个Vector集合
        List vector = new Vector();
        //Vector vector = new Vector();

        // 添加元素
        // 默认容量10个。
        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(4);
        vector.add(5);
        vector.add(6);
        vector.add(7);
        vector.add(8);
        vector.add(9);
        vector.add(10);

        // 满了之后扩容(扩容之后的容量是20.)
        vector.add(11);

        Iterator it = vector.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }

    6、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
        使用集合工具类:
            java.util.Collections;

            java.util.Collection 是集合接口。
            java.util.Collections 是集合工具类。

        import java.util.*;
        // 这个可能以后要使用!!!!
        List myList = new ArrayList(); // 非线程安全的。

        // 变成线程安全的
        Collections.synchronizedList(myList); // 这里没有办法看效果
        // myList集合就是线程安全的了。
        myList.add("111");
        myList.add("222");
        myList.add("333");

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值