Java集合
编辑时间:2021/03/22
读完本节:大概花费50分钟,共5261词
文章目录
1.Java集合框架概述
-
集合、数组都是对多个数据进行存储和操作的结构,简称Java容器。此时的存储主要指内存层面的存储,不涉及持久化的存储。
-
数组在存储多个数据方面的特点
- 数组初始化以后,长度就确定了
- 数组声明的类型,就决定了进行元素初始化时的类型
-
数组在存储数据方面的弊端
- 数组在初始化之后,长度就不可变了,不便于扩展
- 数组中提供的属性和方法少,不便进行添加、删除插入等操作,且效率不高。同时无法直接获取存储元素的个数
- 数组存储的数据是有序的、可重复的。对于无需、不可重复的需求不能满足
-
Java集合类可以用于村拿出数量不等的多个对象,还可以用于保存具有映射关系的关联数组
-
集合框架
Java集合可以分为Collection和Map两种体系
- Collection接口:单列数据,定义了存取一组对象的方法的集合
- List:元素有序,可重复的集合
- Set:元素无序,不可重复的集合
- Map接口:双列数据,保存具有映射关系“key-value对”的集合
- Collection接口:单列数据,定义了存取一组对象的方法的集合
2.Colllection接口中方法的使用
-
Collection中常用的方法:
- add(Object obj):向集合中添加元素
- addAll(Collection coll):向集合中添加传入集合的所有元素
- int size():获取有效元素的个数
- boolean isEmpty():判断是否为空集合
- void clear():清空集合中所有的元素
- boolean contain(Object obj):是否包含某个元素;内部通过元素的equals方法来判断是否是同一个对象
- boolean containAll(Collection c):两个集合挨个比较,是否包含传入集合的元素;内部通过元素的equals方法来比较
- boolean remove(Object obj):删除指定元素;通过元素的equals方法判断是否存在待删元素,只会删除第一个找到的元素
- boolean removeAll(Collection coll):取当前集合的差集;同样是通过equals方法判断是否存在待删元素
- boolean retainAll(Collection coll):求两个集合的交集,把交集的结果存在当前集合中,不会影响coll中的元素
- boolean equals(Object obj):判断集合是否相同
- Object[] toArray():将集合转换为对象数组
- hashCode():获取集合对象的哈希值
- iterator():返回迭代器对象,用于遍历集合
-
import org.junit.Test; import java.util.*; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 10:34 * @Description: */ public class CollectionMethodTest { @Test public void test3(){ Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("AA"); coll.add(new String("biggy")); coll.add(false); //hashCode():返回当前对象的hash值 System.out.println(coll.hashCode()); //toArray():集合转换为对象数组 Object[] arr = coll.toArray(); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } //调用Arrays类的静态方法asList()将数组转换为集合 List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"}); System.out.println(list); List arr1 = Arrays.asList(new int[]{123,456}); System.out.println(arr1.size());//1 List arr2 = Arrays.asList(new Integer[]{123,456}); System.out.println(arr2.size());//2 } @Test public void Test2(){ Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("AA"); coll.add(new String("biggy")); coll.add(false); coll.add(new Person("biggy", 22)); //remove(Object obj):从当前集合中移除obj元素 coll.remove(1234); System.out.println(coll); //如果不在obj相应的类中重写equals方法则无法移除 coll.remove(new Person("biggy", 22)); System.out.println(coll); //removeAll(Collection coll1):求差集,从当前集合中移除coll1中所有的元素 Collection coll1 = Arrays.asList(123,456); coll.removeAll(coll1); System.out.println(coll); //equals(Object obj):判断两个集合是否相同,想要返回true需要当前集合和形参集合的元素都相同 Collection coll2 = new ArrayList(); coll2.add("AA"); coll2.add(new String("biggy")); coll2.add(false); coll2.add(new Person("biggy", 22)); System.out.println(coll1.equals(coll2)); } @Test public void Test1(){ Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("AA"); coll.add(new String("biggy")); coll.add(false); coll.add(new Person("biggy", 22)); //contain(Object obj):判断当前集合中是否包含obj boolean contains = coll.contains(123); System.out.println(contains);//true System.out.println(coll.contains(new Person("biggy",22)));//false //false的原因是没有在Person类中重写equals方法, //直接使用了根父类Object中的equals方法,实际上就是使用了“==”对地址值进行判断 //可以通过重写Object类中的equals方法,实现false到true的转变 //因此,向Collection接口的实现类的对象中添加数据obj时,要求obj所在的类重写equals()方法 //containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中 Collection coll1 = Arrays.asList(123,456); System.out.println(coll.containsAll(coll1)); } @Test public void test1(){ //使用ArrayList实现类的方法展示Collection中的抽象方法 Collection coll = new ArrayList(); //add(object e):将元素e添加到集合coll中 coll.add("AA"); coll.add("BB"); //自动装箱 coll.add(123); coll.add(new Date()); //调用默认的toString()方法将集合输出 System.out.println(coll); //size():获取集合中有效元素的个数 System.out.println(coll.size()); //addAll(Collection coll):将给形参给定的集合元素添加至当前集合 Collection coll1 = new ArrayList(); coll1.add(456); coll1.add("CC"); coll.addAll(coll1); System.out.println(coll); System.out.println(coll.size()); //isEmpty():判断当前集合是否为空 System.out.println(coll.isEmpty()); //clear():清空当前集合中的元素 coll.clear(); System.out.println(coll); System.out.println(coll.isEmpty()); } }
3.Iterator接口迭代器
-
使用Iterator接口遍历集合元素
-
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
-
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不暴露该对象的内部细节。迭代器模式就是为容器而生。
-
Collection接口继承了java.lang.Iterator接口,该接口有一个Iterator()方法,那么所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象
-
Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合
-
集合对象每次调用iterator()方法都会得到一个全新的迭代器对象,默认游标都会在集合的第一个元素之前。
import org.junit.Test; import java.util.*; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 10:34 * @Description: */ public class CollectionMethodTest { @Test /** 集合元素的遍历操作,使用迭代器Iterator接口 */ public void iteratorTest(){ Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("AA"); coll.add(new String("biggy")); coll.add(false); Iterator iterator = coll.iterator(); // //方式1: // System.out.println(iterator.next()); // System.out.println(iterator.next()); // System.out.println(iterator.next()); // System.out.println(iterator.next()); // System.out.println(iterator.next()); // //报异常:NoSuchElementException // System.out.println(iterator.next()); // //方式2:不推荐 // for (int i = 0; i < coll.size(); i++) { // System.out.println(iterator.next()); // } //方式3:推荐方式 //hasNext():判断是否还有下一个元素 while (iterator.hasNext()){ //next():1.指针下移;2.将下移以后集合位置上的元素返回 System.out.println(iterator.next()); } //错误方式一 // Iterator iterator1 = coll.iterator(); // while((iterator1.next()) != null){ // System.out.println(iterator1.next()); // } //错误方式二 // while(coll.iterator().hasNext()){ // System.out.println(coll.iterator().next()); // } } }
-
-
Iterator接口的remove()方法
-
在迭代器内部定义了remove()方法,可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
-
如果还未调用next()或在上一次调用next()方法之后已经调用了remove()方法,再次调用remove()都会报illegalStateException
import org.junit.Test; import java.util.*; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 10:34 * @Description: */ public class CollectionMethodTest { @Test public void iteratorTest1(){ Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("AA"); coll.add(new String("biggy")); coll.add(false); //删除集合中的“biggy” Iterator iterator = coll.iterator(); while(iterator.hasNext()){ Object obj = iterator.next(); if("biggy".equals(obj)){ iterator.remove(); } } //遍历集合 while(iterator.hasNext()){ System.out.println(iterator.next()); } } }
-
4.使用foreach(增强for循环遍历集合元素)
-
JDK1.5提供了foreach循环迭代访问Collection和数组
-
遍历操作不需要获取Collection或数组的长度,无需使用索引访问元素
-
遍历集合的底层调用Iterator完成操作
-
foreach还可以用来遍历数组
for(要遍历的元素类型 遍历后自定义元素名称 : 要遍历的结构名称){
//输出语句
}
-
import org.junit.Test; import java.util.ArrayList; import java.util.Collection; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 15:04 * @Description: */ public class ForTest { @Test public void test1(){ Collection coll = new ArrayList(); coll.add(123); coll.add(456); coll.add("AA"); coll.add(new String("biggy")); coll.add(false); //for(集合元素类型 局部变量 : 集合对象){} //内部仍然调用了迭代器 for(Object obj : coll){ System.out.println(obj); } } @Test public void test2(){ int[] arr = new int[]{1,2,3,4,5,6}; //for(数组元素的类型 局部变量 : 数组对象) for (int i : arr){ System.out.println(i); } } @Test public void test3(){ String[] arr = new String[]{"AA","AA","BB"}; //方式一:普通for循环 for (int i = 0; i < arr.length; i++) { arr[i] = "BB"; } for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } //方式二:增强for循环 for(String s : arr){ // s = "CC"; } for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }
5.Collection的子接口:List接口
- 鉴于Java中数组用来存储数据的局限性,通常使用List代替数组
- List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据需要存取容器中的元素
- JDK API中List接口的实现类常用的有:ArrayList、LinkedList、Vector
6.ArrayList、LinkedList、Vector三者的区别
- 相同点:三个类都实现了List接口,存储数据的特点相同均是可以存储有序的、可重复的数据
- 不同点:
- ArrayList:作为List接口的主要实现类;ArrayList的线程是不安全的,但是效率高;底层使用Object[] elementData数组存储
- Vector:作为Link接口的古老实现类;线程是安全的,但是效率低;底层使用Object[] elementData数组存储
- LinkedList:对于频繁的插入、删除操作,使用LinkdedList效率比ArrayList高;底层采用双向链表存储
7.ArrayList扩容源码分析
-
JDK1.7环境下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData list.add(123);//elementData[0] = new Integer(123); //..... list.add(11);//如果此次添加导致底层element数组容量不够,则扩容; //默认情况下,扩容为原来容量的1.5倍(原本长度 + (原本长度)右移一位),同时需要将原有数组中的数据赋值到新的数组中
JDK1.8环境下
ArrayList list = new ArrayList();//底层Object elementData初始化为{},并没有立刻创建长度为10的数组 list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,此时将123自动装箱放入elementData中 //..... list.add(11);//如果此次添加导致底层element数组容量不够,则扩容; //默认情况下,扩容为原来容量的1.5倍(原本长度 + (原本长度)右移一位),同时需要将原有数组中的数据赋值到新的数组中
-
源码
-
小结:
- 建议在开发中使用带参数的构造器:ArrayList list = new ArrayList(int capacity);
- JDK1.7中的ArrayList对象的创建类似于饿汉式单例模式,而JDK1.8中的ArrayList对象的创建类似于懒汉式单例模式,延迟了数组的创建,节省了内存
8.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; } }
-
添加元素的源码
9.Vector的源码分析
-
源码
-
分析:JDK1.7和JDK1.8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在自动扩容机制上,默认为原来数组长度的2倍。
10.具体实现List接口的方法
-
List除了从Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的方法。
- 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当前集合中的末次出现的位置,若未找到返回-1
- Object remove(int index):移除指定index位置上的元素,并返回此元素
- Object set(int index, Object ele):设置指定index位置上的元素为ele
- List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
-
常用方法小结:
增:add(Object obj) List实现的Collection接口中定义的方法
删:remove(int index) / remove(Object value) 前一个是List中重载的方法,后一个是List实现Collection接口中定义的方法,二者之间构成重载
改:set(int index, Object ele)
查:get(int index)
插:add(int index,Object ele) List中对List实现的add方法进行重载,实现插入元素
长度:size()
遍历:①Iterator迭代器方式;②foreach循环(增强for循环);③普通循环
-
对具体实现List接口中的两个remove进行辨析
import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 16:43 * @Description: 对List中的两个构成重载的remove进行辨析 */ public class ListMethodTest { @Test public void testListRemove1(){ List list = new ArrayList(); list.add(1); list.add(2); list.add(3); updateList1(list); System.out.println(list); } private void updateList1(List list) { //此处的remove是remove(Object value),是ArrayList中重载的方法 list.remove(new Integer(2)); } @Test public void testListRemove(){ List list = new ArrayList(); list.add(1); list.add(2); list.add(3); updateList(list); System.out.println(list); } private void updateList(List list) { //此处的remove是remove(int index),是ArrayList中实现List接口中的方法 list.remove(2); } }
11.Collection的子接口:Set接口
- Set接口是Collection的子接口,Set接口没有提供额外的方法
- Set集合不允许包含相同的元素,如果把两个相同的元素加入到Set集合中,则添加操作失败
- Set判断两个对象是否相同不是使用”==“运算符,而是根据equals()方法
- Set有三个实现类:HashSet、LinkedHashSet、TreeSet
- Set接口的实现类所具有的特点,无序性、不可重复性
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加。而是根据数据计算出来的散列值(哈希值)决定的。
- 不可重复性:保证添加的元素按照equals()判断时,不能返回ture。即相同的元素只能添加一个
12.Set实现类之HashSet
-
HashSet是Set接口的典型实现,大多数时候使用Set集合时,都会使用这个实现类。
-
HashSet按Hash算法来储存集合中的元素,因此具有很好的存取、查找、删除性能
-
HashSet具有的特点:
- 不能保证元素的排列顺序
- HashSet不是线程安全的
- 集合元素可以是null
-
HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,且两个对象的equals()方法返回值也相等
-
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象的相等规则。即”相等的对象必须具有相等的散列码“
-
重写hashCode()方法的基本原则:
- 在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值
- 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也相等
- 对象中作用equals()方法比较的Field,都应该用来计算hashCode值。
- 重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。
-
IDE重写的hashCode()为什么有31这个数字
- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址值越大,”冲突“就越少,查找效率也会提高
- 31只占用5bit,相乘造成的数据溢出的概率较小
- 31可以 由i * 31 == (i << 5) - 1来表示,现在很多虚拟机里面都有相关的优化,提高算法的效率
- 31是一个素数,素数的特点是只能被1和自己整除,而且一个数乘以素数得到的新数只能由素数、被乘数和1整除,减少冲突。减少冲突能减少使用链地址法,提高查找效率
-
添加元素的过程:以HashSet为例说明:
对于情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储(链地址法);JDK1.7中将元素a放置在数组中,并且指向原来的数组;JDK1.8中原来的元素放置在数组中,该元素指向元素a
-
HashSet扩容机制:HashSet底层也是数组,初始容量为16,当使用率超过0.75,(16 * 0.75 = 12)就会扩大容量为原来的两倍
13.Set实现类之LinkedHashSet
- LinkedHashSet是HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但他同时使用双向链表维护元素的次序,这使得元素看起来是以插入的顺序保存的。
- **LinkHashSet插入性能略低于HashSet,**但在迭代访问Set里的全部元素时有很好的性能
- LinkHashSet不允许集合元素重复
14.Set实现类之TreeSet
-
TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态
-
向TreeSet中添加数据,要求是相同类的对象
-
TreeSet底层使用红黑树结构存储数据,红黑树具有的特点是:有序、查询速度比List快
-
新增的方法如下:
- Comparator comparator()
- Object first()
- Object last()
- Object lower(Object ele)
- Object higer(Object ele)
- SortedSet subSet(formElement, toElement)
- SortedSet headSet(toElement)
- SortedSet tailSet(fromElement)
-
TreeSet的两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
-
使用两种不同的方式完成TreeSet集合的元素添加操作
import org.junit.Test; import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 19:03 * @Description: */ public class TreeSetTest { /** 采用定制排序(实现Comparator接口) */ @Test public void test2(){ Comparator com = new Comparator() { /** 按照id从小到大排列 */ @Override public int compare(Object o1, Object o2) { if(o1 instanceof User && o2 instanceof User){ User u1 = (User)o1; User u2 = (User)o2; return Integer.compare(u1.getId(),u2.getId()); }else{ throw new RuntimeException("输入的数据类型不匹配"); } } }; TreeSet set = new TreeSet(com); set.add(new User("Genji", 22)); set.add(new User("Hana", 18)); set.add(new User("Zenyatta", 20)); set.add(new User("Maccree", 25)); set.add(new User("Ashe", 23)); Iterator iterator = set.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } /** 采用自然排序(实现Comparable接口) */ @Test public void test1(){ TreeSet set = new TreeSet(); set.add(new User("Genji", 22)); set.add(new User("Hana", 18)); set.add(new User("Zenyatta", 20)); set.add(new User("Maccree", 18)); set.add(new User("Ashe", 18)); Iterator iterator = set.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } }
User类
import java.util.Objects; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/22 19:05 * @Description: */ public class User implements Comparable{ private String name; private int id; public User() { } public User(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", id=" + id + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User)) return false; User user = (User) o; return getId() == user.getId() && Objects.equals(getName(), user.getName()); } @Override public int hashCode() { return Objects.hash(getName(), getId()); } /** 输入的性名按照从大到小的顺序排列 */ @Override public int compareTo(Object o){ if(o instanceof User){ User user = (User)o; return -this.name.compareTo(user.name); }else{ throw new RuntimeException("输入的类型不匹配"); } } }