Java集合框架总结

本文阅读 22 分钟
首页 代码,Java 正文

这里推荐几篇关于集合的文章 https://blog.csdn.net/weixin_42533238/article/details/106651056 https://blog.csdn.net/ThinkWon/article/details/98844796 https://blog.csdn.net/feiyanaffection/article/details/81394745

先看看结构图,大致有个印象,方便后续学习

img img

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。 此时的存储,注意指的是内存层面的存储,都不涉及持久化的存储(.txt,.jpg,数据库)

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

Java容器

​ 集合、数组都是用来对多个数据进行存储的结构,简称Java容器

数组

数组在存储多个数据方面的特点

1.数组一旦创建,它的长度就不能改变
2.数组一旦定义好,它的元素类型就确定了

数组在存储多个数据方面的缺点

1。数组一旦确定以后,它的长度就不能改变
2.数组中提供的方法很有限,对于添加、删除、修改、插入等一系列操作不方便,而且效率不高
3.获取数组中实际元素的个数的需求,数组没有现成的属性或方法可以用
4.数组存储数据的特点:有序、可重复,对于无序、不可重复的需求不能满足

Java集合的体系分类

集合不能直接存储基本数据类型,也不能存储Java对象,只是存储java对象的内存地址

​ 分为:Collection和Map两种体系

img JDK提供的集合API位于java.util包内 img

Collection接口:

存的是一个一个的数据,就好比是一个个对象。

Collection接口中的常用方法:

img 关于Arrays.asList()这个方法,其实有很多的坑,一不小心就会错 可以看看这篇博客 Arrays.asList() 详解

向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重新equals方法

package com.atguigu.java;


import org.junit.Test;

import java.util.*;

/** * 集合元素的遍历操作,使用迭代器Iterator接口 * 1.内部方法:hasNext 和 next * 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象 * 默认游标都在集合的第一个元素之前 * 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合直接调用remove * * * @author zengyihong * @created 2021-04-25 15:06 */
public class CollectionTest { 
    /** * * 结论: * 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重新equals方法 */
    @Test
    public void test1() { 
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(false);
        coll.add( new String("Tom"));
        coll.add(new Person("Jerry",20));

        //contains(Object obj)判断当前集合是否包含obj
        //我们在判断时会调用obj对象所在类的equals方法
        boolean contains = coll.contains(123);
        System.out.println(contains);
        System.out.println(coll.contains(new String("Tom")));
        System.out.println(coll.contains(new Person("Tom",20)));

        //containsAll(Collection coll):判断形参coll中的所有元素是否都在当前集合中

        Collection coll1= Arrays.asList(123,4567);
        System.out.println(coll.containsAll(coll1));

    }
    @Test
    public void test2(){ 

        //remove(Object obj)

        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(false);
        coll.add( new String("Tom"));
        coll.remove(123);
        System.out.println(coll);

        //removeAll(Collection coll1):从当前集合中移除集合coll1中的所有元素

        //集合---->数组 :toArray()
        Object[] array = coll.toArray();
        for (int i = 0; i < array.length; i++) { 
            System.out.println(array[i]);
        }
        //拓展:数组--->集合
        List<String> list = Arrays.asList(new String[]{ "aa", "bb", "cc"});
        System.out.println(list);
        List list=Arrays.asList(new Integer(123,456));
        System.out.println(list.size()); // 1
        

//z
        List list1 = Arrays.asList(new int[]{ 123, 456});
        System.out.println(list1.size());//1

        List list2 = Arrays.asList(new Integer[]{ 123, 456});
        System.out.println(list2.size());//2

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


    }

    @Test
    public void test3(){ 
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(false);
        coll.add( new String("Tom"));

        //删除集合中的”Tom"
        Iterator iterator=coll.iterator();
        while (iterator.hasNext()){ 

            Object obj = iterator.next();//接收一下集合中的元素
            if ("Tom".equals(obj)){ 
                iterator.remove();
            }


        }
        //遍历集合
        //这个时候指针已经到最后的元素,我们需要把指针重新指到第一个元素
        iterator=coll.iterator();
        while (iterator.hasNext()){ 
            System.out.println(iterator.next());
        }

    }
    @Test
    public void testListRemove() { 
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list);//1,2
    }
    private static void updateList(List list) { 
        list.remove(2);//移除索引位置为2的元素
    }



}

Collection接口中的contains方法源码分析

boolean contains(Object o) 判断集合是否包含某个对象 contains方法底层调用equals方法 img img contains方法是用来判断集合是否包含某个元素的方法,它在底层是调用equals方法进行比对的

equals方法返回true,就表示包含这个元素

放在集合里面的元素,如果没有重写equals方法,那么在比较的是,调用的是object的是equals方法 img img

remove方法源码分析

boolean remove( )

底层调用equals方法 img img

Iterator迭代器接口:

可以用于集合的遍历,但是Map不可以用这个来遍历

Iterator接口概述

img

Iterator方法

可以将iteritor看做是游标,指向所在元素的左边。
1.boolean hasNext(); //判断游标右边是否有元素
2.Object next(); //返回游标右边的元素并将游标移动到下一个位置
3.void remove(); //删除游标左边的元素,在执行完next

iterator中的remove方法

void remove()删除迭代器指向的当前元素 img img

使用Iterator迭代器来遍历集合中的元素

方式一:

   //先造一个集合
   Collection coll=new ArrayList();
   //给集合添加元素
   coll.add(123);
   coll.add("aa");
   coll.add(new String("Tom"));
   coll.add(false);
   //然后造一个迭代器(即Iterator对象)
   Iterator iterator=coll.iterator();
   //接着调用next方法,有几个元素,就调用几次
   //如果调用次数超过元素个数的话,会报异常 NoSuchElementException
   iterator.next();
   iterator.next();
   iterator.next();
   iterator.next();

方式二:
//先造一个集合
   Collection coll=new ArrayList();
   //给集合添加元素
   coll.add(123);
   coll.add("aa");
   coll.add(new String("Tom"));
   coll.add(false);
   //然后造一个迭代器(即Iterator对象)
   Iterator iterator=coll.iterator();
   //然后使用循环语句,元素有几个,就调用几次next方法
   for(int i=0;i<coll.length;i++){ 
   iterator.next();
   }
方式三:重点推荐
//先造一个集合
    Collection coll=new ArrayList();
    //给集合添加元素
    coll.add(123);
    coll.add("aa");
    coll.add(new String("Tom"));
    coll.add(false);
    //然后造一个迭代器(即Iterator对象)
    Iterator iterator=coll.iterator();
    
    
    while(iterator.hasNext()){ //判断是否还有下一个元素
    //next() ①指针下移 ②把下移以后的集合位置上的元素返回
        System.out.println(iterator.next());
        
    }

集合的结构一旦发生改变,迭代器就一定要重新获取 img

迭代器执行原理

img 迭代器刚开始没有指向第一个元素

我们先获取一个迭代器对象

此时,相当于有一个指针

然后判断集合有没有下一个元素

有的话, 指针后移,指向元素,获得这个元素

使用迭代器的时候,可能出现下面这个异常 concurrentmodificationexception是什么异常

使用foreach循环来遍历集合中的元素

1.JDK5.0提供了foreach循环迭代访问Collection和数组
2。遍历操作不需要获取集合或数组的长度,不需要使用索引访问元素
3.遍历集合的底层调用Iteraor完成操作

使用foreach循环的语法

//方式一:普通for循环
       String[] arr=new String[]{ "MM","MM","MM"};
// for (int i = 0; i < arr.length; i++) { 
// arr[i]="GG";
// }

        //方式二:增强for循环
        //内部仍然调用迭代器
        for (String s:arr){ 

            s="GG";
        }
        for (int i = 0; i < arr.length; i++) { 
            System.out.println(arr[i]);
        }
        
分析讨论:
     一、使用普通for循环:
             我们直接对字符串数组中的元素进行操作,改变元素所指的常量池中的值
     二、使用加强for循环:
             我们用一个变量来指向字符串数组中的所有元素,然后对这个新变量进行操作数据,并不会影响原本字符串数组中所储存的数据

List接口

img

存储有序的、可重复的数据--->动态数组,替换原有的数组

List接口的实现类

三者的相同点:三个类都实现了List接口,都是存储有序的、可重复的数据

ArrayList

1.JDK7情况下
  ArrayList list=new ArrayList();//底层创建了一个长度为10的Object[]数组elementData
  List.add(123)//elementData[0]=new Integer(123)
  ...
  List.add(11)//如果此次的添加导致底层elementData数组容量不够,则扩容。
  默认情况下,扩容为原来的1.5倍,同时需要把原来的数组复制到新的数组中
  
  结论:建议开发中使用带参的构造器 : ArrayList list=new ArrayList(int capacity)可以减少扩容次数,效率可以高一点
  
2.JDK8中ArrayList的变化
   ArrayList list=new ArrayList()//底层Object[]elementData初始化为{},并没有创建长度为10的数组
   ArrayList.add(123)//第一次调用add()时,底层才创建了长度为10的数组,并把数据123添加到elementData中
   后续的添加和扩容操作与JDK7没有差异,扩容为原来的1.5倍

3.总结:JDK7中的ArrayList的创建类似于单例的饿汉式,而JDK8中的ArrayList的对象的创建类似于单例的懒汉式,延迟数组的创建,节省内存

LinkedList

底层是双向链表结构

LinkedList list=new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
List.add(123);//把123封装到Node中,创建了Node对象

其中Node定义为:体现了LinkedList的双向链表的说法
 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;
        }
    }

img

Vector

JDK7和JDK8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来数组长度的2倍
底层也是数组

创建一个Vector集合,默认初始化容量10

扩容后为原来容量的2倍

Vector所有方法都是线程同步的,都带有synchronized

线程安全,效率低,少用 img img

List接口的方法

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来
操作集合元素的方法。 
1.void add(int index, Object ele):在index位置插入ele元素
2. boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
3.Object get(int index):获取指定index位置的元素
4. int indexOf(Object obj):返回obj在集合中首次出现的位置
5. int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
6. Object remove(int index):移除指定index位置的元素,并返回此元素
7. Object set(int index, Object ele):设置指定index位置的元素为ele
8. List subList(int fromIndex, int toIndex):返fromIndex到toIndex位置的子集合

List常用方法

1.增:add(Object obj)
2.删:remove(int index)/remove(Object obj)
3.改:set(int index,Object obj)
4.查:get(int index)
5.插:add(int index,Object obj)
6.长度:size()
7.遍历:①Iterator迭代器 ②增强for循环 ③普通for循环 for(int i=0;i<list.size();i++){

<!-- -->
System.out.println(list.get(i))
}

remove方法的易错题

@Test
public void testListRemove() { 
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);//[1,2]
}
private static void updateList(List list) { 
list.remove(2);
}
 
目的:区别remove(int index)/remove(Object obj)
     当我们使用remove方法,里面传入的是一个整型数据,默认认为 我们要移除该索引位置的元素
     如果我们想要移除对应元素,应该进行装箱,把这个数据转换为包装类
     例如 : List.remove(new Integer(2)) 把元素2删除
            List.remove(2)) 把索引位置为2的地方的元素删除

Set接口

img

存储无序的,不可重复的数据---->高中讲的集合 Set接口中没有额外定义方法,使用的是Collection接口中的方法
要求:向Set接口中添加的数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性,相同的对象具有相同的散列码

Set接口的特性

无序性

以HashSet为例: 不等于随机性, 每次遍历的顺序都是固定的。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的

HashSet底层是数组,它的长度为16

不可重复性

保证添加的元素按照equals方法判断时,不能返回true,如果返回true,则认为两个对象相同,即相同的元素只能添加一个

添加元素的过程:以 HashSet 为例

我们向HashSet添加元素a时,首先调用HashCode()方法,计算元素a的哈希值,这个哈希值在通过散列函数计算出HashSet在底层数组中的存放位置(即为:索引位置),判断数组此位置上面是否有元素:
    如果此位置上面没有元素,则元素a添加成功。---->情况1
    如果此位置上面有其他元素b(或以链表形式存在的多个元素),则比较元素a和元素b的Hash值:
        如果Hash值不同,则元素a添加成功---->情况2
        如果Hash值相同,进而需要调用元素a所在类的equals()方法:
            equals()返回true,元素a添加失败
            equals()返回false,则元素a添加成功---->情况3
            
对于添加成功的情况2和情况3而言:元素a和已经存在指定索引位置上的数据以链表的形式存储
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a

总结:七上八下(在jdk8中新的元素放到上面)
HashSet底层:数组+链表
添加元素的要求

向Set接口中添加的元素,其所在的类一定要重写equals()方法,和HashCode()方法

重写方法的要求

img

img

重写的equals()方法和HashCode()方法尽可能保持一致性:相等的对象必须具有相等的散列码(即哈希值)
重写两个方法的小技巧:对象中用作equals()方法比较的属性:都应该用来计算哈希值
尽量使得当两个对象的属性都相等时,它们的哈希值也相等
当它们的哈希值相等时,它们的属性也相等

Set接口的实现类

HashSet

img

重写hashCode()方法的基本原则 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。 对象中用作
equals() 方法比较的 Field,都应该用来计算 hashCode 值。

LinkedHashSet

1.作为HashSet的子类,在添加数据的同时,每一个数据还维护两个引用,用来记录此数据前一个数据和后一个数据
2. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置, 但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入 顺序保存的。
3. LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全 部元素时有很好的性能。
4. LinkedHashSet 不允许集合元素重复 优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
5.遍历内部数据的时候,可以按照添加的顺序进行遍历

TreeSet

不能放相同的数据

TreeSet和 TreeMap 采用红黑树的存储结构 特点:有序,查询速度比List快
放入TreeSet中的数据要是同一个类new出来的,我们可以按照对象的某一些属性进行排序
默认是从小到大排序

向TreeSet中添加的数据,要求是相同类的对象

自然排序

实现Comparable接口 重写compareTo方法

自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()

定制排序

实现Comparator接口 重写compare方法

img img

Map实现类的结构

/---Map:存储双列数据,存储key-value数据   ---类似高中讲的函数
    /---HashMap:作为Map的主要实现类,线程不安全,效率高,可以存储key和value都是null的数据
        /---LinkedHashMap:HashMap的子类,在原有的基础上加一对指针,指向前一个元素和后一个元素,形成链表结构,能够保证在遍历Map元素时,可以按照我们添加的顺序进行遍历
        对于频繁的遍历操作,此类指向效率高于HashMap
        
   /---TreeMAp:可以保证按照添加的key-value进行排序,实现排序遍历,按照key来排序
                       此时考虑key的自然排序或定制排序
               底层使用红黑树
   /---Hashtable:作为古老的实现类,线程安全,效率低,不能存储null(key和value中有一个是null都不行)
           /---Properties:常用来处理配置文件。key和value都是String类型
           
           
           
  HashMap的底层:  数组+链表(jdk7之前)
                  数组+链表+红黑树(jdk8)

Map结构的理解

img

我们知道Map类似于高中讲的函数,所以Map中的key不能重复
1.Map中的key:无序的、不可重复的,使用Set存储所有的key  --->Key所在的类要重写equals方法和HashCode方法(以HashMap为例) 因为存储的key可能是我们自定义的类
2.Map中的value:可重复的、无序的,使用Collection存储所有的value---->value所在的类要重写equals方法
3.一个键值对:key-value构成了一个Entry对象,在计算机中Map存储是存一个Entry对象,而不是存储两个值key和value,而是把这两个数据封装在一起
4.Map中的entry:无序的,不可重复的,使用Set存储所有的entry

事实上Map里面放的是一个一个的数据,这一个个的数据是Entry,Entry里面有两个属性,一个key,一个value,里面放的数据无序不可重复,因为 key无序不可重复,一个key对应一个value

HashMap的底层实现原理

img

img

以JDK7为例

HashMap map=new HashMap();
在实例化以后,底层创建了长度为16的一维数组Entry[] table
map.put(key1,value1)
首先调用key1所在类的HashCode方法来计算key1的哈希值,然后根据某种算法计算以后,得到在Entery数组中的存储位置。
如果此位置数据为空,此时的key1-value1添加成功—情况1
如果此位置数据不为空(说明此位置存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数值的哈希值:
如果key1的哈希值和已经存在的数据的哈希值不相同,此时的key1-value1添加成功—情况2
如果key1和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较,调用key1所在类的equals方法,比较:
如果equals方法返回false,此时key1-value1添加成功–情况3
如果equals方法返回true:使用value1去替换相同key的数据值value2

补充:关于情况2和情况3: 此时key1-value1和原来的数据以链表的形式存在 在不断添加的过程中,会涉及到扩容问题:当超出临界值(而且其存放的位置非空) 默认的扩容方式:扩容为原来的2倍,并把原来的数据复制过来

JDK8.0和JDK7的不同

1.  new HashMap();//底层没有创建一个长度为16的数组
2.  JDK8底层的数组:Node[],不是Entry[]
3.  JDK8首次调用put方法时,底层创建长度为16的数组
4.  JDK7底层结构只有数组+链表   JDK8中底层结构:数组+链表+红黑树  当数组的某一个位置上的元素以链表形式存在的数据个数=8,且当前数组的长度>64,此时索引位置上的所有数据改为使用红黑树存储,便于查找数据

img

加载因子:0.75

LinkedHashMap

img

它能够让输出的顺序和我们添加的顺序一样,是因为底层有before,after属性,用来记录添加Entry的前一个和下一个是谁,记录添加的元素顺序

Map接口常用方法

img

@Test
 public void test1(){ 
  Map map=new HashMap<>();
  map.put("AA",123);
  map.put("BB",12 );
  map.put( 156,78);
  //修改
  map.put("BB",33);
  System.out.println(map);

  Map map1=new HashMap();
  map1.put("cc",40);
  map1.put(78,"tim");
  map.putAll(map1);
  System.out.println(map);
  //remove 如果没有找到对应的key,返回null
  Object value = map.remove("cc");
  System.out.println(value);
  System.out.println(map);
//clear 清空元素 
  map.clear();
  System.out.println(map.size());//0

 }

Map的遍历方法

@Test
 public void test2(){ 
  Map map=new HashMap<>();
  map.put("AA",123);
  map.put("BB",12 );
  map.put( 156,78);
  /** * 遍历所有的key */

  Set set = map.keySet();
  Iterator iterator = set.iterator();
  while (iterator.hasNext()) { 
   Object next = iterator.next();
   System.out.println(next);
  }


 }
/** * Map的遍历 */
 @Test
 public void test2(){ 
  Map map=new HashMap<>();
  map.put("AA",123);
  map.put("BB",12 );
  map.put( 156,78);
  /** * 遍历所有的key */

  Set set = map.keySet();
  Iterator iterator = set.iterator();
  while (iterator.hasNext()) { 
   Object next = iterator.next();
   System.out.println(next);
  }

  System.out.println();
  /** * 遍历所有的value集 */
  Collection values = map.values();
  Iterator iterator1 = values.iterator();
  while (iterator1.hasNext()) { 
   Object next =  iterator1.next();
   System.out.println(next);

  }
  System.out.println();

  /** * 遍历key 和 value * 方式一: */
  Set entrySet = map.entrySet();
  Iterator iterator2 = entrySet.iterator();
  while (iterator2.hasNext()){ 
   //这个时候,obj是entry类型
   Object obj=iterator2.next();
   //entrySet集合中的元素都是entry
   Map.Entry entry=(Map.Entry)obj;
   System.out.println(entry.getKey()+"--->"+entry.getValue());

  }
  System.out.println();
  /** * 方式二:先遍历所有的key,通过key来获取value */
  Set set1 = map.keySet();
   Iterator iterator3=set1.iterator();
   while (iterator3.hasNext()){ 

    //获取key
    Object next = iterator3.next();
    System.out.println(next+"--->"+map.get(next));

   }

Properties

img img img

Collections工具类

操作Collection和Map的工具类 img

img img

package com.atguigu.java;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/** * * CollectionsTest是操作Colletion和Map的工具类 * * 面试题:Collection和Collections的区别 * * @author zengyihong * @created 2021-09-18 14:52 */
public class CollectionsTest { 
    /** *reverse(list); 反转List中元素的顺序 *shuffle(list):对List集合元素进行随机排序 *sort(list):根据元素的自然排序对指定list集合元素按照升序排序 *swap(list,int,int):将指定List集合中的i处元素和j处元素进行互换 * * * * *Object max(Collection):根据元素的自然排序,返回给定集合中的最大元素 *Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素 *Object min(Collection) *Object(Collection,Comparator) *int frequency(Collection,Object):返回指定集合中指定元素的出现次数 *void copy(List dest,List src):将src中的内容复制到dest中 *boolean replaceAll(List list,Object newVal):使用新值替换List中的旧值 * * *Collections 类中提供了多个synchronizedXxx()方法, *该方法可以将指定集合包装成线程同步的集合,从而可以解决 * 多线程并发访问集合时的线程安全问题 * * */

    @Test
    public void test1(){ 

        List list=new ArrayList();
        list.add(123);
        list.add(456);
        list.add(10);
        list.add(-56);
        list.add(0);
        System.out.println(list);

      // Collections.reverse(list); 反转list中元素的顺序
        //shuffle(list)对list集合元素进行随机排序
        //Collections.shuffle(list);

        //报异常:java.lang.IndexOutOfBoundsException: Source does not fit in dest

// List dest=new ArrayList();
//
// Collections.copy(dest,list);


        List dest= Arrays.asList(new Object[list.size()]);
        Collections.copy(dest,list);
        System.out.println(dest);





    }


}
本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://zengyihong.blog.csdn.net/article/details/123133131
-- 展开阅读全文 --
安全面试之XSS(跨站脚本攻击)
« 上一篇 07-24

发表评论

成为第一个评论的人

热门文章

标签TAG

最近回复