Java集合

集合

1. Java集合框架的概述

 集合与数组都是用来存储多个数据的结构,通常称为Java容器。此时的存储指的是存储在内存之中,不涉及到持久化存储(.txt、.jpg、数据库等)。

数组存储的特点:

  1. 一旦初始化之后,数组的长度就已经确定了。
  2. 一旦定义好,数组存储数据的类型就确定了,后续操作只能操作同一种数据类型的数据

数组存储的弊端:

  1. 一旦初始化之后,数组的长度就不能更改了。
  2. 数组中的相关方法很少,尤其是在插入、删除等操作的时候,非常的不方便,效率也比较低。
  3. 获取数组中实际添加元素的个数非常不方便。
  4. 数组存储数据的时候是有序可重复的,对于无序不可重复的需求无法实现。

集合存储可以解决数组存储方面的弊端。

集合中的框架结构:

Collection接口:单列集合,存储一个一个的元素。

List:存储有序可重复的数据。(ArrayList、LinkedList、Vector)

Set:存储无序不可重复的数据。(HashSet、TreeSet、LinkedHashSet)

Map接口:双列集合,通过键值对(Key-Value)存储一对一对的数据。(HashMap、LinkedHashMap、TreeMap、Properties、HashTable)

2. Collection接口方法

add(E e):将元素添加到集合里面。

size():获取添加的元素的个数。

addAll(Collection list):将一个集合元素list添加到另外一个集合里面。

clear():清空集合中的元素。

isEmpty():判断集合是否为空(是否有元素)。

contains(Object o):判断集合中是否包含元素o,此时contains会调用对象所在类的equals方法。

containsAll(Collection list):判断当前集合中是否包含集合list

remove(Object o):从集合中移除元素o

removeAll(Collection list):从当前集合中移除集合list

retainAll(Collection list):获取当前集合与集合list的交集并将结果赋给当前集合

equals(Object o):比较当前集合与集合o是否相等。

hashCode():返回当前集合的哈希值

toArray():将当前集合转为数组。数组转集合可以调用Arrays.asList()

iterator():返回Iterator接口的实例,用来遍历集合元素。

 

public class CollectionTest {
    @Test
    public void test1(){
        Collection collection=new ArrayList();

        //add(Object e):将元素e添加到collection中
        collection.add("AA");
        collection.add(123);
        collection.add(new Date());

        //size():获取添加的元素的个数
        System.out.println(collection.size());

        Collection collection1=new ArrayList();
        collection1.add("CC");
        collection1.add(456);

        //addAll(Collection list): 将一个集合添加到另外一个集合中
        collection1.addAll(collection);
        System.out.println(collection1.size());

        //clear():清空集合元素
       // collection1.clear();

        //isEmpty():判断当前集合是否为空
        boolean empty = collection1.isEmpty();
        System.out.println(empty);

        System.out.println("*******************");

        //contains(Object o):判断集合中是否包含元素o
        boolean cc = collection1.contains("CC");
        System.out.println(cc);
        //来个有意思的测试,此时contains调用了String类的equals方法而不是用==判断的(String类重写了equals方法)
        //如果contains里面的参数是对象的话,此时调用的相当于是Object中的equals方法,
        // 而object中的equals方法本质上是==。所以结果为false, 如果想比较结果为true,则需要重新写自定义类中的equals方法
        //contains会调用对象所在类的equals方法,所以在向Collection接口的实现类中添加对象时,要求对象的所在类要重写equals方法。
        collection1.add(new String("Java"));
        System.out.println(collection1.contains(new String("Java")));//true

        //containsAll(Collection list):判断当前集合中是否存在list集合中的所有元素.

        System.out.println(collection1.containsAll(collection));

        //remove(Object o):移除集合中的某个对象
        collection1.remove(new String("Java"));
        System.out.println(collection1.toString());

        //removeAll(Collection list):从当前集合中移除list中的所有相同的元素,并修改当前集合

        //retainAll(Collection list):获取list与当前集合的交集并修改当前集合为交集的值。
       
        //equals(Object o):判断当前集合与集合o元素是否相同(在ArrayList需注意是否有序)

        //hashCode():返回当前对象的hash值。
        System.out.println(collection1.hashCode());

        //toArray():集合转成数组
        Object[] objects = collection1.toArray();
        System.out.println(objects);

        //数组转成集合:Arrays.asList()
        List<String> strings = Arrays.asList(new String[]{"AA", "BB"});
        System.out.println(strings);
        System.out.println(strings.size());

        //小坑
        List ints = Arrays.asList(new int[]{123, 456});
        System.out.println(ints);
        System.out.println(ints.size());
        //避免方式一
        List integers = Arrays.asList(123, 456);
        System.out.println(integers.size());
        System.out.println(integers);
        //避免方式二
        List integers1 = Arrays.asList(new Integer[]{123, 456});
        System.out.println(integers1.size());
        System.out.println(integers1);

        //iterator():返回Iterator接口的实例,用来遍历集合元素

    }
部分方法的代码演示

 

3. Iterator迭代器接口

iterator对象称为迭代器(设计模式的一种),主要用来遍历Collection集合中的元素。

内部的方法:

hasnext():判断是否还有下一个元素

next():①指针下移②返回指针下移之后集合位置上的元素

remove():删除集合中的某个元素,此时调用的不是集合中的remove()方法。

JDK5.0新增了增强for循环:foreach。用来遍历集合、数组。内部依然调用了迭代器

 

 @Test
    public void test1(){
        Collection coll1=new ArrayList();
        coll1.add(123);
        coll1.add("司藤");
        coll1.add(456);
        coll1.add(new String("前路奔腾,未来可期"));
        coll1.add(new Date());
        coll1.add(789);

        Iterator iterator = coll1.iterator();//创建迭代器接口,
        // 此时可以看作已经有了一个迭代器的指针,指向集合中第一个元素前方空的位置,可以理解为-1
//        System.out.println(iterator.next());
//        //hasNext:判断是否还有下一个元素
//        while (iterator.hasNext())
//            //next:①指针下移②将下移以后集合位置上的元素返回
//            System.out.println(iterator.next());
//
//        //**********************两种错误写法
//        while(iterator.next()!=null){//错误写法一:①会出现.NoSuchElementException异常②跳着输出
//            System.out.println(iterator.next());
//        }
//        //错误写法二:出现死循环,而且只输出第一个元素。
//        // 原因:集合对象每次调用iterator都会新生成一个iterator对象,指针总是指向第一个元素的前方。
//        while(coll1.iterator().hasNext())
//        {
//            System.out.println(coll1.iterator().next());
//        }


//        //****************remove方法
//        while(iterator.hasNext()){
//            Object next = iterator.next();
//            if ("司藤".equals(next)){
//                iterator.remove();
//            }
//        }
//        //注意点:再次遍历coll时,需要重新定义iterator对象
//        iterator=coll1.iterator();
//        while (iterator.hasNext()){
//            System.out.println(iterator.next());
//        }


//        jdk5.0 新增增强for循环:foreach
       // for(集合中的元素类型  局部变量:集合名称),内部仍然调用了迭代器
        for (Object o : coll1) {
            System.out.println(o);
        }
    }
相关代码

 

4. Collection子接口一:List

List集合类中的元素特点:有序且可重复,集合中的每个元素都有其对应的顺序索引。

三个实现类:

ArrayList:作为list接口的主要实现类,线程不安全,执行效率高,底层使用Object类型的数组存储

LinkedList:底层结构使用的是双向链表存储,对于频繁的插入删除操作,该类的效率要高

Vector:作为list接口的古老实现类,线程安全,执行效率低,底层使用Object类型的数组存储

面试题:ArrayList、LinkedList、Vector的异同?

同:都实现了List接口,存储数据的特点相同,都是有序的,可重复的,ArrayList和Vector底层存储都是使用object类型的数组

异:

ArrayList源码分析

jdk7.0情况下:

ArrayList  list=new ArrayList():此时底层创建了一个长度为10的object类型的数组。

list.add():list.add(1)//elementData[0]=new Integer(1);...当添加的数据个数超过10时,此时需要扩容,默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。建议开发中使用带参的构造器ArrayList  list=new ArrayList(int capacity)

jdk8.0情况下:

ArrayList  list=new ArrayList():此时数组初始化时长度为{},只有第一次调用add方法时,才创建了长度为10的数组,扩容时与jdk7.0相同。

总结:jdk7.0的创建类似于单例模式中的饿汉式,jdk8.0中创建类似于单例的懒汉式,延迟了数组的创建,节省了内存空间。

LinkedList源码分析:

LinkedList list=new LinkedList():内部声明了first和last属性,默认值为null。当调用add方法时,创建了Node对象。

 

 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;
        }
    }
源码中Node的定义

 

Vector源码分析:该类基本不怎么使用,与ArrayList不同的时扩容方式,扩容大小为原来数组长度的2倍

5. Collection子接口二:Set

set存储数据的特点是无序的不可重复的。set中没有定义新的方法,都是collection中定义过的

HashSet:作为set接口的主要实现类,是一个线程不安全的,可以存储null值。

LinkedHashSet:是HashSet的一个子类,遍历其内部数据时,可以按照添加顺序遍历

TreeSet:可以按照添加对象的指定属性进行排序

如何理解set存储数据是无序的、不可重复的?

无序性:不等于随机性,每次输出都是一样的顺序。以HashSet为例,存储的数据在底层数组中并非按照索引顺序进行的添加,而是根据数据的Hash值决定的。

不可重复性:保证添加的对象重写了equals方法,且判断时不可返回true,即相同的元素只能添加一个。

set中添加元素的过程(以HashSet为例)

在jdk1.8中HashSet的底层是由HashMap实现的

当添加元素a时,调用a元素所在类的hashCode()方法算出hash值,此hash值通过某种算法算出元素a在hashSet底层数组中的位置,如果该位置没有元素,则直接添加,如果当前位置有元素,判断新添加的元素a与当前位置上的所有元素的hash值是否一样,如果都不一样,以链表的方式添加a元素(数组中原来的元素指向元素a),如果一样,新添加的元素a调用所在类的equals()方法与相同hash值的元素比较,如果返回结果为false,按照链表的方式添加,如果返回结果为true,则违背了不可重复性,新元素不可添加。

向set中添加数据,要求该数据所在的类需要重写hashCode()和equals()方法,重写的hashcode和equals方法尽量保持一致性:相等的对象必须拥有相等的散列码。

LinkedHashSet作为HashSet的子类,在添加数据的同时每个数据还添加了引用,用来记录前一个数据和后一个数据,好处是,对于频繁的遍历操作,效率要高于HashSet。

TreeSet添加数据时,要求是由同一个类的对象,否则会添加失败。该类主要涉及到排序方面的问题,有两种排序方式,自然排序(实现comparable的接口)、定制排序(comparator)

自然排序中,比较两个对象是否相等的方法,是看compareto方法返回值是否为0,不在通过equals方法比较。

定制排序中,比较两个对象是否相等的方法,是看compare方法返回值是否为0

6. Map接口

Map存储的是双列数据,使用键值对(key-value)的形式来存储数据。

HashMap:底层存储结构(数组+链表+红黑树(jdk1.8中)),可以存储null值,是线程不安全的。

  LinkedHashMap:是HashMap的子类,但是使用Entry替换了HashMap中的Node,增加了entry类型的before、after属性,可以知道新添加元素的前一个元素和后一个元素

Hashtable:作为古老的Map实现类,是线程安全的,但是不能存储null值。

Properties:常用来处理配置文件,key和value都是String类型的

TreeMap:底层结构是红黑树,可以按照添加的key进行排序遍历,与TreeSet类似,有自然排序(key所在的类需要实现Comparable接口)、定制排序(comparator),要求key必须是由同一个类创建的对象(和TreeSet类似)

HashMap的底层实现原理:

在jdk1.7中,HashMap在实例化的时候创建了一个长度为16的底层数组Entry[] table,调用添加键值对中key所在类的hashcode方法计算出哈希值,根据根据某种算法计算出添加元素在数组中存放的位置(下标)。

如果当前位置上没有元素,则新添加的元素存放成功。

如果当前位置上已经存在了一个或者多个元素(多个元素以链表的形式存在),判断已有元素中是否有与新添加的元素的哈希值相同

如果都不相同,则新元素添加成功

如果有一个或者多个相同,调用新添加元素中key值所在类的equals方法,判断是否存在key值相同的情况

如果返回结果为false,使用头插法将新元素添加到当前位置上链表的头部。

如果返回结果为true,则新元素添加失败

在jdk1.8中,hashMap在实例化时没有创建长度为16的数组,是在首次调用put方法时才进行创建。在jdk1.8中,entry数组被替换为node数组。底层结构相较于jdk7来说,增加了红黑树,当数组某个索引位置上的元素个数超过了8并且此时数组长度扩容到了64以上(且该索引位置上已经有元素了)此时该索引位置上的元素采用红黑树的结构来存储,提高了遍历速度。

jdk7和jdk8的扩容一样,根据负载因子和数组的长度算出扩容的临界值。在达到该临界值时扩容为原来数组的2倍。

负载因子:

默认为0.75,负载因子越大,hashMap的数据密度越大,出现hash碰撞的几率越大,数组中链表的长度也会越长,在进行添加和查询的时候比较的次数就会增多,影响效率。负载因子小可以比较容易的触发扩容,提高插入或者查询的效率,但是如果过小会浪费一定的内容空间,经常扩容也会影响性能。经过长时间的研究实验,负载因子经常设置为0.7~0.75;

key特点:无序不可重复,使用set存储

value特点:无序可重复,使用collection存储

entry特点:无序不可重复,使用set存储

常用方法:(Map整体是无序的,所以不存在插入相关的方法)

增:put(Object key,Object value)

删:remove(Object key)

改:put(Object key,Object value)

查:get(Object key)

长度:size()

遍历:

keyset():遍历map中所有的key值,返回一个set集合

values():遍历map中所有的value值,返回一个collection

entrySet():遍历map中所有的key-value,返回一个set集合

7. Collections工具类

操作Collection和Map的工具类

collection和conllections的区别:

collection是存储单列数据的集合接口,常见的子接口是list和set,collections是操作collection和map的工具类。

常见方法:

 @Test
    public void test3(){
        List list = new ArrayList();
        list.add(132);
        list.add(123);
        list.add(154);
        list.add(112);
        list.add(-421);
        list.add(-75);
        list.add(0);

        System.out.println(list);

        //reverse():反转list
     //   Collections.reverse(list);
        //shuffle():随机排序
     //   Collections.shuffle(list);
        //sort(List list):根据元素的自然顺序对指定的集合元素进行升序排序
     //   Collections.sort(list);
        //sort(List list,Comparator):按照定制排序对指定的集合元素排序
        //swap(list,int,int):将集合中i位置和j位置的元素交换位置
     //   Collections.swap(list,1,2);
        //frequency():返回指定集合中指定元素出现的次数。
     //   int frequency = Collections.frequency(list, 123);
     //   System.out.println(frequency);
        //copy(List dect,List src):将src中的内容复制到dect中,需要注意的是,dect中的数据长度必须要大于等于list中的数据长度
     //   List dect = Arrays.asList(new Object[list.size()]);
     //   Collections.copy(dect,list);
     //   System.out.println(dect);
        //replaceAll(list,Object oldValue,Obejct newValue):使用新的元素替换掉list集合中的旧元素
        System.out.println(list);

    }
工具类中的常用方法

 除了上述方法,还提供了synchronizedXXX相关的方法,可以返回一个线程安全的集合。

 

 

 

 

 

 

 

 

 

 

 

posted @ 2021-03-17 17:29  云入山涧  阅读(115)  评论(0编辑  收藏  举报