一: Java集合框架的概述
集合和数组都是对多个数据进行存储操作的容器,简称 Java 容器(此时的存储主要指的是内存层面的存储,不涉及到持久化存储(持久化存储:硬盘,服务器))
二:集合与数组的比较
1.1数组在存储多个数据方面的特点
> 一旦初始化以后长度就确定了,不可以对长度进行修改
> 定义数组的时候需要指定数组的元素类型,数组定义好以后,数组元素的类型也就确定了
> 也会表现出多态性
1.2 数组的缺点
> 一旦初始化后长度不可修改
> 数组中提供的方法非常有限,对于添加,删除,插入数据等操作 非常不方便,同时效率也不高
> 获取数组中实际元素的个数,数组没有现成的属性和方法可以和使用
< 数组存储数据的特点是有序的,可重复的 对于无序的,不可重复的数据 数组则比较弊端
三:java集合可以分为 collection 和 Map 体系
Collection 体系:单例集合,用来存储一个一个的对象
List接口:存储有序的,可重复的 “动态数组,相较于数组本身静态”
<List接口下包含 ArrayList,LinkedList,Vector 三个接口实现类
Set接口: 存储无序的,不可重复的 “类似于高中讲的集合 ” 无序,确定,互异
<Set接口下包含 HashSet,LinkedHashSet,TreeSet 三个实现类
四:List接口下的三个实现类分析
package CollectionListUse;
import org.junit.Test;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @Author:CT
* @Date:2021/2/5
* @Description:CollectionListUse
* @Version 1.0
*/
/*
一:list接口:存储有序的,可重复的 “动态数组,相较于数组本身静态”
< arraylist “作为list接口的主要实现类,线程不安全,执行效率高 底层使用object[] element”
< LinkedList “底层使用双向链表进行存储”
< vector “作为list接口的古老实现类,效率低,线程安全”
二:面试题: 三种实现类的异同
同: 三个类都实现了list接口,都是有序的,可重复的
不同:见上
三:arraylist jdk7 当中的源码分析 类似于stringBuilder,stringBuffer
arraylist list=new arraylist();// 在底层创建一个类型为object 类型的数组 长度为10
list.add(123);// elementData[0]=new integer(123) 相当于elementData 中添加数据
.....
....
list.add(111)// 如果此次添加导致elementData的数组的最大长度不够,则会进行扩容。
默认情况下扩容为原来的1.5倍,再把原来的数据 调用copy() 方法复制到新的数组当中
结论: 在开发中我们建议使用带参的构造器 去初始化底层创建数组的长度,会避免在中间环节扩容 提高开发效率
四:arraylist jdk8 中源码变化
arraylist list=new arraylist();// 在底层创建一个类型为object 类型的数组,并没有初始化数组的长度,节省了内存空间
后续的add() 操作于JDk7 一致
小结:JDK7 和JDK8 的两者种初始化ArrayList 类似于单例模式中的 饿汉式和懒汉式两种设计模式
五:LinkedList源码分析 JDK8的源码分析
Linked list=new LinkedList(); 内部声明为Node类型的 first 和 Last 属性,默认值为null
List.add(123); 将123封装到Node中,创建了Node对象
其中Node 定义为:体现了Linked List 双向链表
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;
}
}
6.Vector 的源码分析:JDK7 和 JDK8 当中通过Vector() 构造器创建对象时,底层都要创建了长度为10的数组
,在扩容方面,默认扩容为原来是数组的两倍。。
*/
/*
List 接口中的抽象方法
总结; 常用方法
增: add(Object obj)
删: remove(int index)
改: set(int index,Object)
查: get(int index)
插入: add(int index, object ele)
长度: size()
遍历: ① Iterator 迭代器
② 增强for循环
③ 普通的循环
*/
public class CollectionList {
@Test
public void test(){
ArrayList list=new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
// 1.void add(int index, Object ele): 在Index 指定位置插入ele 元素
list.add(2,3);
System.out.println(list);//[123, 456, 3, AA]
//2. boolean addAll(int index,Collection ele): 从指定Index位置开始插入ele 集合当中所有的元素
List<Integer> integers = Arrays.asList(1, 2, 3);//可以初始化多个集合中的对象
list.addAll(2,integers);
System.out.println(list);//[123, 456, 1, 2, 3, 3, AA]
//3. Object get(int index): 获取指定index位置上的值
Object o = list.get(2);
System.out.println(o);//1
//4.int IndexOf(Object obj): 返回obj 在集合当中首次出现的问题 注意是首次出现的位置
int i = list.indexOf(2);
System.out.println(i);//3
//5. int Last IndexOf(object obj): 返回Obj当前集合中末次出现的位置
int i1 = list.lastIndexOf(3);
System.out.println(i1);//[123, 456, 1, 2, 3, 3, AA] 值为5
//6. 移除指定index位置的元素,并返回此元素
Object remove = list.remove(0);
System.out.println(remove);//123
//7. Object set(int index,Object ele): 设置指定index位置的元素为ele
Object set = list.set(2, 23);
System.out.println(list);//[456, 1, 23, 3, 3, AA]
//8. list sublist(int fromIndex, int toIndex): 返回从fromIndex到toIndex位置的子集合 左闭右开
List list1 = list.subList(2, 4);
System.out.println(list1);//[23, 3]
}
}
五:Set接口下三个实现类的源码和方法分析
package ColeectionSetUse;
import org.junit.Test;
import java.util.*;
/**
* @Author:CT
* @Date:2021/2/6
* @Description:ColeectionSetUse
* @Version 1.0
*/
/*
Set接口:是Collection的子接口
一:set接口的特点
< Set接口: 存储无序的,不可重复的 “类似于高中讲的集合 ” 无序,确定,互异
三个实现类
HashSet(set接口的主要实现类 线程不安全 可以存储 null值 )
LinkedHashset(作为HashSet的子类): 使得遍历其内部的数据时,可以按照添加的顺序去遍历
TreeSet: 可以按照添加对象的指定属性,进行排序
① set 接口的没有提供额外的方法
② set集合不允许包含相同的元素,如果试着把两个相同的元素加入同一个Set集合当中,则添加失败
③ set集合 判断两个对象是否相同不是使用 ==运算符,而是根据equals() 方法
二:向set类中添加元素,其所在类必须要重写两个方法 hashCode() 和equals() 方法
重写的两个方法尽可能保持一致性(相对的对象必须具有相对的散列码”散列码==哈希值“)
重写两个方法的小技巧(直接调方法,避免手动重写)
*/
public class CollectionSetuse {
@Test
/*
一:如何理解:
无序性:不等于随机性。 存储的数据在底层数组中,并非按照数组的索引顺序去做的添加,而是根据数据的哈希值决定的
不可重复性:往Set 集合当中添加到数据时不可重复的
二:添加元素的过程:以HashSet为例
我们向HashSet中添加元素a,首先计算出元素a所在类的hashCode(),计算元素a的哈希值,
此哈希值接着通过某种算法,算出元素a在底层HashSet当中存放的位置(位置:即为索引位置),判断
数组在此位置上是否已经存在元素
如果此位置上没有元素为Null 则直接添加成功 ----> 添加成功情况一
如果此位置上已经存放了元素b或其他元素
判断元素a与其他元素的哈希值是否相同
如果哈希值不同 则添加成功------> 添加成功情况二
如果哈希值相同
则比较元素a调用的equals() 方法比较两个元素的实体内容是否相同
如果不同则添加成功 equals() 方法返回值为false------> 添加成功情况三
如果相同则添加失败 equals()方法返回值为true
对于添加成功的两种情况二和三: 元素a与已经在指定位置上的数据以链表的方式存储
JDK 7:元素a放到数组中指向,指向原来的元素
Jdk 8:原来的元素在数组中,指向新的元素
(7上8下)
*/
public void test(){
Set set=new HashSet();
set.add(456);
set.add(1230);
set.add("AAA");
set.add("CC");
set.add(new Person("Jerry",12));// 此处为什么要重写equals()方法里面的 hasCode()
set.add(new Person("Jerry",12));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());//CC AAA 456 1230
}
}
/*
关于LinkedHashSet的使用
① LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,用于指向前一个数据和数
② 优点:对于频繁的遍历数组操作,LinkedHashSet效率高于HashSet
*/
@Test
public void test2(){
Set set=new LinkedHashSet();
set.add(456);
set.add(1230);
set.add("AAA");
set.add("CC");
set.add(new Person("Jerry",12));// 此处为什么要重写equals()方法里面的 hasCode()
set.add(new Person("Jerry",12));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());//456 1230 AAA person[name="Jerry",age=12]
}
}
/*
一:TreeSet 类的使用
① 向TreeSet当中添加数据时,我们必须确定是同一个类提供的,否则会添加失败
② 可以按照对象的添加顺序进行输出
③ TreeSet添加数据是按照compareTo 去比较添加数据 他判断数据相同与否是按照return 0 如果返回的是0 则相同
④ 所以我们在定制排序的时候可以指定哪些数据能进来
*/
@Test
public void test3(){
Set set=new TreeSet();
set.add(123);
set.add(456);
set.add(456);//此时会有一个数据添加不成功
System.out.println(set);
Set set1=new TreeSet();
/*
两种排序方式: 自然排序 定制排序
自然排序(Comparable)中比较两个对象是否相同的标准:CompareTo()方法
定制排序(Comparator)
*/
set1.add(new Person("Jerry",18));//此处会添加失败 原因是添加的对象不统一 java.lang.ClassCastException(报这个错误)
set1.add(new Person("Jrry",134));
set1.add(new Person("Jrry",54));
System.out.println(set1);
}
@Test
/*
定制排序的使用
*/
public void test5(){
Comparator objectComparator = new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Person&& o2 instanceof Person){
Person person=(Person)o1;
Person person1=(Person)o2;
int i = person.getName().compareTo(person1.getName());
if (i!=0){
return i;
}else{
return Integer.compare(person.getAge(),person1.getAge());
}
}else{
throw new RuntimeException("输入的数据有误");
}
}
};
Set set1=new TreeSet(objectComparator);
set1.add(new Person("Jerry",18));//此处会添加失败 原因是添加的对象不统一 java.lang.ClassCastException(报这个错误)
set1.add(new Person("Jrry",134));
set1.add(new Person("Jrry",54));
System.out.println(set1);
}
}
总结:
-
我们在使用集合当中List接口下的方法时,自己创建的类必须要重写equals()方法,因为向集合当中添加元素时会调用类中的equals()方法进行比较,看是否能添加成功
-
当我们使用集合当中Set‘接口下的HashSet()和LinkedHashSet()时,必须重写类中的equals()方法和Hashcode() ,原因是我们向集合当中添加元素时,首先会根据HashCode方法返回的哈希值去寻找此元素添加在底层数组当中的位置,再调用equals()方法比较是否相同,不同则可以添加
-
集合当中的无序性:表示的是集合当中的元素不是按照数组角标的顺序存储,而是按照元素的哈希值存放
-
List接口存放的元素是,有序的,可重复的 。Set接口存放的元素是无序的,不可重复的。