Java SE基础——Java集合

Java集合框架概述

Java容器:集合与数组

(1) 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。 集合,数组都是对多个数据进行存储操作的结构

  • 说明:此时的存储,主要指的是内存的存储,不涉及到持久化的存储( .txt, .jpg, .avi, 数据库中)

(2) 数组在存储多个数据方面的特点:

  • 一旦初始化以后,其长度就确定了。
  • 数组一旦定义好,其元素的类型也就确定了。只能操作指定类型的数据。比如:String[] arr;等,但是对于Object[] arr,可以利用多态性,存放Object类的子类。

(3)数组在存储多个数据方面的缺点:

  • 数组初始化以后,长度就不可变了,不便于扩展
  • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。
  • 获取数组中实际元素的个数的需求,数组中没有现成的属性或方法可用
  • 数组存储的数据是有序的、可以重复的。---->存储数据的特点单一

集合的使用

Java集合两种体系:Collection和Map(这两者是接口)

  • Collection接口(该接口没有提供具体实现类):单列数据,定义了存取一组对象的方法的集合。

    • List:元素有序、可重复的集合
    • Set:元素无序、不可重复的集合
  • Collection接口继承树:(实线:代表继承关系 , 虚线:代表实现关系 )

  • Map 接口:双列数据,保存具有映射关系“key-value对”的集合

  • Map接口继承树 :

Collection接口 方法

Collection接口:

  • 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals( )

  • 单列集合,用来存储一个一个的对象**(如果输入的是int,double等基本类型数据,则存储的是对应转换的包装类)**,是 List、Set 和 Queue 接口的父接口,该接口里定义的方法,既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

List接口:(重心)

  • 单列数据,也可以看作键值对(key-value)特点,不过这里的key对应的是索引(index)
    从0开始,key自动通过索引方式去维护
  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组,”“动态”数组。
  • 存储有序的,可重复的数据,集合中的每个元素都有其对应的顺序索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据
    序号存取容器中的元素。
List接口方法
  • 使用的是equals( )
  • void add(int index, Object ele): 在index 位置插入ele元素
  • boolean addAll(int index, Collection eles): 从index 位置开始将eles中的所有元素添加进来
  • Object get(int index): 获取指定index位置的元素
  • int indexOf(Object obj): 返回obj在集合中首次出现的位置,如果找不到,就返回-1。
  • int lastIndexOf(Object obj): 返回obj 在当前集合中末次出现的位置
  • Object remove(int index): 移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele): 设置指定index 位置的元素为ele
  • List subList(int fromIndex, int toIndex): 返回从fromIndex 到toIndex位置的左闭右开区间的子集合, 这里只是将返回值赋给变量,不会修改原来的值
总结:常用的方法
  • 增:add(Object obj)往末尾添加
  • 删:remove(int index) / remove(Object obj)
  • 改: set(int index, Object ele)
  • 查: get(int index)
  • 插; add(int index, Object obj)
  • 长度: size( ) 返回的不是底层数组的长度,是元素的个数
  • 遍历: (1) Interator迭代器方式 (2) 增强for循环 (3) 普通的循环
//用来测试遍历
@Test
public void test3(){ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
System.out.println(list)  //输出[123, 456, AA]//方式一:Iterator迭代器方式
Iterator iterator = list.iterator();
while(iterator.hasNext()){System.out.println(iterator.next());
}//方式二:增强for循环
for(Object obj : list){System.out.println(obj);
}//方式三:普通for循环
for(int i =0;i < list.size(); i++){System.out.println(list.get(i));
}
}
public class ListTest{@Test
public void test1(){ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("TOM",12));
list.add(456);System.out.println(list);//void add(int index, Object ele):在index位置插入ele元素
List list1 = Arrays.asList(1,2,3);
list.addAll(list1);
//list.add(list1);//这里如果用add,相当于是把1,2,3当作一个整体。
System.out.println(list.size());
}
}
面试题
  • 区分List中remove(int index) 和remove(Object obj )
@Test
public void testListRemove() {List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);//add( )方法中,没有重载,没有选择,因为形参是Object,所以自动装箱。
updateList(list);
System.out.println(list);
}
private static void updateList(List list) {list.remove(2);  //这里输出的结果是[1,2].这里remove()方法有选择,装箱麻烦,可以直接用index,所以这里remove中的2是代表索引
list.remove(new Integer(2));//这里就是删除2这个元素,结果为[1,3].
}

JDK API中List接口的实现类常用的有:

ArrayList
  • ArrayList 是 List 接口的典型实现类、主要实现类
  • 作为List接口的主要实现类 ;线程不安全的,效率高;底层使用Object[] elementData存储.
  • 本质上,ArrayList是对象引用的一个”变长”数组
  • Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是
    Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合
ArrayList的源码分析
  • jdk 7情况下:

    • ArrayList list = new ArrayList();底层创建了长度是10的Object[]数组elementData,
      list.add(123);elementData[0] = new Integer(123);… 如果此次添导致底层elementData数组容量不够,则扩容。默认情况下。扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
      结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
  • jdk 8情况下:
    • ArrayList list = new ArrayList();底层Object[ ] elementData初始化为{ },并没有创建长度为10的数组
    • List.add(123);第一次调用add( )时,底层才创建了长度10的数组,并将数组123添加到 elementData[0]中,后续的添加和扩容操作与jdk 7 无异。
  • 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList
  • 对于频繁的插入,删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
  • 没有角标的说法,双向链表,内部没有声明数组,而是定义了Node类型的first和last,
    用于记录首末元素。同时,定义内部类Node:数据存储的基本单位,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:(1) prev变量记录前一个元素的位置,(2) next变量记录下一个元素的位置
LinkedList的源码分析
  • LinkedList list = new LinkedList( );内部声明了Node类型的first和last属性,默认值为null
  • list.add(123); 将123封装到Node中,创建了Node对象
  • 其中,Node定义为:体现了LinkedList的双向链表的说法
Vector(不重要,不常用)
  • 作为List接口的古老实现类;线程安全的,效率低
Vector的源码分析:
  • jdk7和jdk8中通过Vector( )构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍。

面试题:ArrayList,LinkedList,Vector三者的异同?

(1)同:三个类都是实现了List接口,存储数据的特点相同:存储有序的,可重复的数据
(2)异:ArrayList: 作为List接口的主要实现类 ;线程不安全的,效率高;底层使用Object[] elementData存储.
LinkedList:对于频繁的插入,删除操作,使用此类效率比ArrayList高;底层使用双向链表存储。
Vector:作为List接口的古老实现类;线程安全的,效率低,底层是数组

面试题:请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?

  • ArrayList和LinkedList的异同

    • 二者都线程不安全,相对线程安全的Vector,执行效率高。此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
  • ArrayList和Vector的区别

    • Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类synchronized,属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

Set接口:

  • 存储无序的(即:无索引),这里无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序来添加,而是根据数据的哈希值决定的。
  • 不可重复的数据:保证添加的元素按照equals( )判断时,不能返回true. 即:相同的元素只能添加一个。
  • Set接口是Collection的子接口,set接口没有提供额外的方法,使用的都是Collection中声明过的方法
  • Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
  • Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
HashSet
  • 作为Set接口的主要实现类;线程不安全的;可以存储null值,不能保证元素的排列顺序

  • 底层:数组+链表的结构(前提:jdk7)

  • 对于存放在Set容器(主要指:HashSet , LinkedHashSet)中的对象, 对应的类一定要重写equals() 和hashCode(Object obj) 方法,以实现对象相等规则 。即:“相等的对象必须具有相等的散列码(即:哈希值)” 。

  • HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。

  • 添加元素过程

      1. 向HashSet中添加元素a,首先调用元素a所在类的hashCode( )方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
      1. 情况一 : 如果此位置没有其他元素,则元素a添加成功
      1. 如果此位置上有其他元素( 或以链表形式存在的多个元素),则比较元素a与元素b的hash值:情况二:(1) 如果hash值不相同,则元素a添加成功。 (2)如果hash值相同,进而需要调用元素a所在类的equals( )方法: equals( )返回true,元素a添加失败
        情况三:equals( )返回false,则元素a添加成功。
  • 对于添加成功的情况二和情况三而言:元素 a 与已经存在指定索引位置上数据以链表的方式存储。

      1. jdk 7:元素a放到数组中,指向原来的元素。
      1. jdk8 :原来的元素在数组中,指向元素a
      1. 总结:七上八下
HashSet经典面试题
package com.xsl.java;import org.junit.Test;import java.util.HashSet;/*** @author xsl* @create 2022-04-13 16:05*/
public class CollecTest {@Testpublic void test(){HashSet set = new HashSet();//这里Person类中重写了HashCode()和equals()方法Person p1 = new Person(1001,"AA");Person p2 = new Person(1002,"BB");set.add(p1);set.add(p2);//输出[Person{id=1001,name='AA'}, Person{id=1002,name='BB'}]System.out.println(set);p1.name = "CC";set.remove(p1); //输出[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]System.out.println(set);  //                         [1]set.add(new Person(1001,"CC"));//输出[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}] System.out.println(set); //                           [2]set.add(new Person(1001,"AA"));//输出[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]System.out.println(set); //                            [3]}
  • 代码结果分析

      1. 针对结果[1],这里将p1的name改成CC,但是原来是由1001,AA计算的哈希值,由底层原理可知,此时remove移除的是(1001,CC)的哈希值,此时,该哈希值对应的数组没有元素,所以没有元素可删除,故输出两个。
      1. 针对结果[2],虽然这里添加的(1001,CC),但是原来的(1001,CC)是通过修改前(1001,AA)的哈希值添加的,所以两者的哈希值不一样,可以添加,所以输出三个。
      1. 针对结果[3],这里添加的是(1001,AA),其哈希值对应的数组上的位置被占据,但是该值是(1001,CC),哈希值相等,但是equals值不一样,所以可以添加,通过链表的方式继续链接,故输出四个。
重写 hashCode() 方法的基本原则
  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。

  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()方法的返回值也应相等。

  • 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

  • 效果图:

LinkedHashSet
  • 作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历

  • 在添加数据的同时,每个数据还维护了两个引用(指针),记录此数据前一个数据和后一个数据

  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

  • LinkedHashSet 不允许集合元素重复。

  • 对于频繁的遍历操作,LinkedHashSet效率高于HashSet

  • LinkedHashSet底层结构

TreeSet
  • TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态
  • 可以按照添加对象的指定属性,进行排序。
  • 向TreeSet中添加的数据,要求是相同类的对象
  • 不能放相同的数据
  • TreeSet底层使用 红黑树(特点:有序,查询速度比List快) 结构存储数据
  • 两种排序方式:(1) 自然排序(默认情况下,TreeSet 采用自然排序) (2)定制排序
自然排序
  • 比较两个对象是否相同的标准为:compareTo(Object obj )返回0,不再是equals( )方法。

  • TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列

  • 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通compareTo(Object obj) 方法的返回值来比较大小。

  • 向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。

  • 因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类 的 对象

定制排序
  • TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
  • 比较两个对象是否相同的标准为: compare(Object o1,Object o2 )返回0,不再是equals( )方法。
  • 利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
  • 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
  • 此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。

Collection接口中的方法的使用

  • 添加

    • add(Object obj)
    • addAll(Collection coll)
  • 获取有效元素的个数
    • int size()
  • 清空集合
    • void clear()
  • 是否是空集合
    • boolean isEmpty()
  • 是否包含某个元素
    • boolean contains(Object obj):是通过元素的equals方法来判断是否
      是同一个对象
    • boolean containsAll(Collection c):也是调用元素的equals方法来比
      较的。拿两个集合的元素挨个比较。
  • 删除
    • boolean remove(Object obj) :通过元素的equals方法判断是否是
      要删除的那个元素。只会删除找到的第一个元素
    • boolean removeAll(Collection coll):取当前集合的差集
  • 取两个集合的交集
    • boolean retainAll(Collection c):把交集的结果存在当前集合中,不
      影响c
  • 集合是否相等
    • boolean equals(Object obj)
    • 这里如果是List或者ArrayList,这里不仅要求元素一样,顺序也要一样,如果是set,只需要元素一样,因为本身是无序的。
  • 转成对象数组
    • Object[] toArray()
  • 数组 -->集合:
    • 调用Arrays类的静态方法asList(可变形参)
  • 获取集合对象的哈希值
    • hashCode()
  • 遍历
    • iterator():返回迭代器对象,用于集合遍历
//向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
public class CollectionTest{@Testpublic void test1(){Collection coll = new ArrayList();//1.add(Object e):将元素e添加到集合coll中coll.add("AA");coll.add("BB");coll.add(123)//自动装箱coll.add(new Date());coll.add(new String("TOM"));Person p = new Person("Jerry","20");coll.add(p);//2.size():获取添加的元素的个数System.out.println(coll.size()); //4//3.addAll():将coll1集合中的元素添加到当前的集合中Collection coll1 = new ArrayList();coll1.add(456);coll1.add("CC");coll.addAll(coll1);System.out.println(coll.size());//6//4.clear():清空集合元素(是指清理里面的数据,不是将其变成null)coll.clear();//5.isEmpty():判断当前集合是否为空(是判断集合当中是否有元素)System.out.println(coll.isEmpty());//6.contains(Object obj):判断当前集合中是否包含obj//我们在判断时会调用obj对象所在类的equalsboolean contains = coll.contains(123);System.out.println(coll.contains(new String("Tom")));//true//这里判断的是里面的内容,不是地址System.out.println(coll.contains(p));//true,因为是同一个引用//7.containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在与当前集合中Collection coll1 = Arrays.asList(123,456);//多态形式System.out.println(coll.containsAll(coll1));//8.remove(Object obj):从当前集合中移除obj元素coll.remove(123);       //9.removeAll(Collection coll1):从当前集合中移除coll1中所有的元素Collection coll1 = Array.asList(123,4567);coll.removeAll(coll1);System.out.println(coll);//10.retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合Collcetion coll1 = Array.asList(123,456,789);coll.retainAll(coll1);System.out.println(coll);//11.equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同Collection coll2 = new ArrayList();coll2.add(456);coll2.add("CC");System.out.println(coll1.equals(coll2));//12.hashCode():返回当前对象的哈希值System.out.println(coll.hashCode());//13.集合-->数组:toArray()Object[] arr = coll.toArray();for(int i =0;i < arr.length;i++){System.out,println(arr[i]);//扩展:数组 -->集合:调用Arrays类的静态方法asList()//这里asList(可变形参),这里可变形参等同于数组。List<String> list = Arrays.asList(new String[]{"AA","BB","CC"});List<String> list = Arrays.asList({"AA","BB","CC"});//new String[]可以不用写,因为asList(可变形参),这里可变形参等同于数组。System.out.println(list);//这里注意//错误写法List arr1 = Arrays.asList(new int[]{123,456});System.out.println(arr1.size());//这里输出是1,因为把123,456看成整体//正确写法List arr1 = Arrays.asList(new Integer[]{123,456});System.out.println(arr2.size()); //2//iterator():返回Iterator接口的实例,用于遍历集合元素。}}
}

Map接口: (重心)

  • 双列集合,用来存储一对(key-value)一对的数据 ,这里key可以是任何指定的类型, 类似于高中的函数: y=f(x)
    -

  • Map与Collection并列存在。用于保存具有 映射关系的数据:key-value

  • Map 中的 key 和 value 都可以是任何引用类型的数据

  • Map 中的 key 用Set来存放,无序的, 不允许重复,即同一个 Map 对象(key所在的类)所对应的类,须重写hashCode()和equals()方法

  • Map中的value: 无序的,可重复的,使用Collection存储所有的value

  • Map中的entry:无序的,不可重复的,使用set存储所有的entry

  • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value

方法

    1. 添加 、 删除、修改操作
    • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
    • void putAll(Map m):将m中的所有key-value对存放到当前map中
    • Object remove(Object key):移除指定key的key-value对,并返回value
    • void clear():清空当前map中的所有数据
    1. 元素 查询的操作:
    • Object get(Object key):获取指定key对应的value
    • boolean containsKey(Object key):是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • int size():返回map中key-value对的个数
    • boolean isEmpty():判断当前map是否为空
    • boolean equals(Object obj):判断当前map和参数对象obj是否相等
    1. 元 视图操作的方法:
    • Set keySet():返回所有key构成的Set集合

    • Collection values():返回所有value构成的Collection集合

    • Set entrySet():返回所有key-value对构成的Set集合

    • 代码显示:

@Test
public void test(){Map map = new HashMap();map.put("AA",123);map.put(45,123);map.put("BB",56);//遍历用的iterator适用于Collection
//遍历所有的key集:keySet()Set set = map.keySet();Iterator iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());
}
//遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){System.out.println(obj);
}
//遍历所有的key-value
//方式一:entrySet():Set entrySet = map.entrySet();Iterator iterator1 = entrySet.iterator();while(iterator1.hasNext()){Object obj = iterator1.next();
//entrySet集合中的元素都是entryMap.Enter entry = (Map.Enter) obj;System.out.println(entry.getKey() + "------" + entry.getValue());
}//方式二:Set keyset = map.keySet();Iterator iterator2 = keySet.iterator();while(iterator2.hasNext()){Object key = iterator2.next();Object value = map.get(key);System.out.println(key + "====="m + value);
}}

常用方法

  • 添加:Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  • 删除:Object remove(Object key):移除指定key的key-value对,并返回value
  • 修改:Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  • 查询:Object get(Object key):获取指定key对应的value
  • 长度:size()
  • 遍历:Set keySet( ) / Collection values( ) / Set entrySet( )

HashMap (Map接口使用频率最高的实现类)

  • 线程不安全的,效率高,存储null的key和value

  • 允许使用null键和null值,与HashSet一样,不保证映射的顺序

  • 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()

  • 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()

  • 一个key-value构成一个entry

  • 所有的entry构成的集合是Set:无序的、不可重复的

  • HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,
    hashCode 值也相等。

  • HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true。

  • HashMap的内部存储结构其实是 数组+ 链表+ 树 的结合。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。

  • 每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

  • 当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

HashMap的底层实现原理?

以jdk7为例说明:
  • HashMap的内部存储结构其实是 数组和链表的结合。当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。 每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。而且新添加的元素作为链表的head。

  • 添加元素的过程

    • HashMap map = new HashMap( );

    • 在实例化以后,底层创建了长度是16的一维数组Entry[ ] table。

    • map.put(key1,value1):

    • 向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。 【情况一】

    • 如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。【情况二】

    • 如果hash值相同,继续比较二者是否equals(比较的是key的属性)。如果返回值为true,则使用entry1的value去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。 【情况三】 entry1指向原有的entry元素。

    • 补充: 关于情况二和情况三:此时key-value 和原来的数据以链表的方式存储

  • HashMap 的扩容
    -默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来

    • 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的
      长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
  • HashMap 什么时候进行扩容

    • 当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)×
      loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的 默 认 值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过16×0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2×16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
以jdk8为例说明:
  • jdk8相较于jdk7在底层实现方面的不同:

      1. new HashMap( ):底层没有创建一个长度为16的数组
      1. jdk8 底层的数组是: Node[ ] , 而非Entry[ ]
      1. 首次调用put( )方法时,底层创建长度为16的数组
      1. jdk 7 底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。 当前数组中的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储。
      1. 数组为Node类型,在jdk7中称为Entry类型
      1. 形成链表结构时,新添加的key-value对在链表的尾部(七上八下)–>[ jdk7:新的元素指向旧的元素。 jik8:旧的元素指向新的元素 ]

HashMap源码中的重要常量

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • DEFAULT_LOAD_FACTOR :HashMap的默认加载因子:0.75
  • threshold :扩容的临界值,=容量*加载因子 ,既: 16×0.75 =12
  • TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树 : 8
  • MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量。64

面试题:负载对 因子值的大小,对HashMap 有什么影响

  • 负载因子的大小决定了HashMap的数据密度。
  • 负载因子越大密度越大,发生碰撞(碰撞:两个key不一样,因为数组比较小,放在数组同一个位置上,以链表的方式存在)的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。
  • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
  • 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。

LinkedHashMap

  • LinkedHashMap 是 HashMap 的子类,底层使用的结构与HashMap相同。
  • 区别在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node
  • 保证在遍历map元素时,可以按照添加的顺序实现遍历。
  • 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素,对于频繁的遍历操作,此类执行效率高于HashMap

LinkedHashMap 的底层实现原理(了解)

TreeMap

  • 保证按照添加的key-value对 进行排序,实现排序遍历。TreeMap 可以保证所有的 Key-Value 对处于 有序状态。 此时考虑key的自然排序或定制排序,底层使用红黑树

  • 要求key必须是同一个类创建的对象

  • TreeMap 的 Key 的排序:

      1. 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
      1. 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable接口
  • TreeMap判断 两个key 相等的标准:两个key通过compareTo()方法或者compare()方法返回0。

Hashtable (不重要)

  • 作为古老的实现类,线程安全的,效率低。不能 存储null的key和value
  • 即使涉及到多线程,也不用Hashtable

Properties

  • 常用来处理配置文件。 key和value都是String类型。
  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件
  • 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key和 value 都是字符串类型
  • 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
  • 关键代码部分
    //properties:常用来处理配置文件。key和value都是String类型
    FileInputStream fis = null;Properties pros = new Properties();  //这里要处理异常fis = new FileInputStream("jdbc.properties");pros.load(fis);  //加载流对应的文件String name = pros.getProperty("name");String password = pros.getProperty("password");System.out.println("name "+name +", password = " + password);
    

Iterator迭代器接口

  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
  • GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元
    素,而又不需暴露该对象的内部细节。 迭代器模式,就是为容器而生。类似于“公
    交车上的售票员”、“火车上的乘务员”、“空姐”。
  • Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
  • Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
  • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合
    的第一个元素之前。

Iterator 接口的方法

  • hasNext()–boolean
  • next() – E
  • remove()–void:可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
    在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且
    下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。

迭代器的执行原理

注意:1.执行next()时候,指针才下移; 执行hasNext()指针没有下移
2.Iterator是迭代器,不是容器,其遍历的数据还是原来容器Collection里面的数据。
3. 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。

    public class IteratorTest{@Testpublic void test1(){Collection coll = new ArrayList();coll.add(123);coll.add(456);Person p = new Person("Jerry","20");coll.add(new String("TOM"));coll.add(false);Iterator interator = coll.iterator();System.out.printin(interator.next());//123//推荐://hasNext():判断是否还有下一个元素while(iterator.hasNext()){//next():1.指针下移 2.将下移以后集合位置上的元素返回System.out.println(iterator.next());//错误方式//原因:集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。while(coll.iterator().hasNext()){System.out,println(coll.iterator().next());//循环输出123}}//测试Iterator中的remove()@Testpublic void test2(){Collection coll = new ArrayList();coll.add(123);coll.add(456);Person p = new Person("Jerry","20");coll.add(new String("TOM"));coll.add(false);//删除集合中"TOM"Interator interator = 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());}}}}}
}

使用 foreach 循环遍历集合元素(jdk 5.0 新增)

  • foreach 循环迭代访问 Collection和数组。
  • 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
  • 遍历集合的底层调用Iterator完成操作。
  • for(集合元素的类型 局部变量 : 集合对象)
public class ForTest{@Testpublic void test1(){Collection coll = new ArrayList();coll.add(123);coll.add(456);Person p = new Person("Jerry","20");coll.add(new String("TOM"));coll.add(false);//for(集合元素的类型  局部变量 : 集合对象)//内部仍然调用了迭代器for(Object obj : coll){System.out.println(obj);}
}@Testpublic void test2(){int[] arr = new int[]}{1,2,3,4,5,6};//for(数组元素的类型  局部变量 : 数组对象)for(int i : arr){System.out.println(i)}}//面试题:@Testpublic void test3(){String[] arr = new String[]}{"MM","MM","MM"};for(String s : arr){s = "GG";}for(int i = 0;i < arr.length; i++){System.out.println(arr[i]);//"MM","MM","MM"}
//(解析:因为对于增强for循环,这里只是把arr数组中值取出来,赋给新的变量s,并不会改变原来的arr数组中的值)}

Collections工具类

  • Collections 是一个操作 Set、List 和 Map 等集合的工具类

-排序操作:(均为static方法)
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

  • 查找,替换

    • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    • Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
    • Object min(Collection)
    • Object min(Collection,Comparator)
    • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    • void copy(List dest,List src):将src中的内容复制到dest中
      ---- — 注意:
    //报异常
    List dest = new ArrayList();
    Collection.copy( dest,list);
    System.out.println(dest);  //这里会报异常,由底层源码可知,要让list.size() < = dest.size()//正确写法
    List dest = Arrays.asList(new Object[list.size( )]);
    System.out.printin(dest.size()); //输出就是list.size()
    Collections.copy(dest,list);
    System.out.println(dest);
    
    • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
  • Collections常用方法:同步控制

    • Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

Collection 和 Collections 的区别

  • Collection是集合类的上级接口,继承于他的接口主要有Set 和List.
    Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作

    • Object min(Collection,Comparator)
    • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    • void copy(List dest,List src):将src中的内容复制到dest中
      ---- — 注意:
    //报异常
    List dest = new ArrayList();
    Collection.copy( dest,list);
    System.out.println(dest);  //这里会报异常,由底层源码可知,要让list.size() < = dest.size()//正确写法
    List dest = Arrays.asList(new Object[list.size( )]);
    System.out.printin(dest.size()); //输出就是list.size()
    Collections.copy(dest,list);
    System.out.println(dest);
    
    • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
  • Collections常用方法:同步控制

    • Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

Collection 和 Collections 的区别

  • Collection是集合类的上级接口,继承于他的接口主要有Set 和List.
    Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作

Java SE基础——Java集合相关推荐

  1. Java SE基础(十六)集合

    Java SE基础(十六)集合 集合 集合体系 数据结构简述 栈 队列 数组 链表 Collection集合 List集合 ArrayList集合 LinkedList集合 Set集合 HashSet ...

  2. 面试必会系列 - 1.1 Java SE 基础

    本文已收录至 github,完整图文:https://github.com/HanquanHq/MD-Notes Java SE 基础 面向对象 Java 按值调用还是引用调用? 按值调用指方法接收调 ...

  3. Java复习总结(二)Java SE基础知识

    Java SE面试题 目录 Java SE基础 基本语法 数据类型 关键字 面向对象 集合 集合类概述 Collection接口 进阶 线程 锁 JDK 反射 JVM GC io操作和NIO,AIO ...

  4. Java SE 基础知识

    Java SE 基础知识 1 2 @(Notes)[J2SE, Notes] VICTORY LOVES PREPARATION. 特别说明: 该文档在马克飞象查阅最佳: 本部分知识还在迭代中,欢迎补 ...

  5. Java SE基础(更新中)

    Java的运行机制 Java SE基础(更新中) 基本语法 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的. 类名:对于所有的类来说,类名的首字母应该大写 ...

  6. JAVA SE基础笔记

    第一天  JDK安装.快捷键.JAVA语言特点 1.Java语言的概述 1.1 Java语言的背景 Java语言诞生于1995年,在编程语言排行榜占据重要的地位. Java语言之父是高斯林,以前隶属于 ...

  7. Java SE 基础知识汇总

    目录 一.Java概述 1.1 Java语言背景 1.2 Java语言的三个版本 1.3 Java语言的跨平台原理 1.4 JRE和JDK 1.5 DOS命令 二.Java环境搭建 2.1 环境变量的 ...

  8. JAVA SE基础知识总结

    JAVA基础篇 1_JAVA语言概述 1.1 JAVA的总体概述 1.2 JAVA语言概述 1.2.1 基础常识 1.2.2 计算机语言的发展迭代史 1.2.3 Java语言版本迭代概述 1.2.4 ...

  9. 零基础学JAVA]Java SE基础部分-01. Java发展及JDK配置

    1.课程名称:Java发展及JDK配置 本季介绍了JAVA的发展过程,包括JDK的发展历程,path路径的配置和classpath的配置及作用.并简单讲解了一个简单的JAVA程序,并通过此程序讲解了J ...

  10. java实现linkstring,【JAVA SE基础篇】32.String类入门

    [JAVA SE基础篇]32.String类入门 1.字符串 1.String类又称作不可变字符序列 2.String位于java.lang包中,java程序默认导入java.lang包下所有的类 3 ...

最新文章

  1. 千万级并发HAproxy均衡负载系统介绍
  2. 微信小程序的数字有部分会自动加粗的解决方法
  3. Java连MySQL性能调优(batch insert和连续left join筛选)
  4. java简单通讯录的实现02person类_Java中Math类的简单介绍
  5. 飘逸的python - 鲜为人知的参数
  6. JavaScript事件与jQuery方法
  7. 大数据时代的“找油利器”
  8. boost::math模块非有限信号 NaN 环回测试
  9. 12.04 深圳站 | Serverless Developer Meetup 开放报名
  10. Linux——curl(转)
  11. 阿里巴巴为什么禁止使用Apache Beanutils进行属性复制?
  12. double+float
  13. siesta在Linux运行,siesta-3.0-b
  14. Linux之JDK安装
  15. Kotlin学习笔记21 协程part1 基本概念
  16. 如何制作自己的网课网站 需网课查课插件
  17. 计算机软件系统测试报告模板,测试报告模板
  18. HTML5+CSS3+Bootstrap开发静态页面嵌入android webview中
  19. unrealengine(UE5)虚幻引擎下载安装
  20. 制作歌词录入系统php,如何制作歌词字幕 制作字幕的软件

热门文章

  1. android 剪贴板增强工具,剪切板增强工具(ClipboardFusion Pro)
  2. 恋与抽卡模拟器网页_恋与制作人抽卡模拟器-恋与制作人抽卡模拟器软件下载v1.14.1202-k73游戏之家...
  3. 计算机表格填充,excel自动填充怎样快速填充一列?/excle填充工具
  4. nachos交叉编译器java_ubuntu - 编译Nachos源代码时出错“gnu / stubs-32.h:没有这样的文件或目录”...
  5. JavaScript中的this指向以及bind()函数
  6. 按键精灵定时后台点击
  7. 为什么NIO比BIO效率高
  8. Flutter热重载原理探索调试
  9. linux中分号转义字符,Linux职场技术篇-Linux shell中元字符、转义符、通配符的使用方法...
  10. brandon公司_开发人员聚焦:布兰登·里德(Brandon Reid)