Java集合框架主要内容:
1、集合框架的接口和类
集合框架接口:
集合框架分两大类Collecttion 和 Map
Collection存储一个元素集合,Map存储 键/值对映射
Collection 又有 3 种子类型,List、Set 和 Queue
集合框架中的类
2、Collection体系集合
Collection测试
元素是String
package collectionsFramework.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* Collection接口的使用
* (1)添加元素
* (2)删除
* (3)遍历
* (4)判断
*/
public class TestCollection {
public static void main(String[] args) {
//创建集合
Collection collection = new ArrayList();
//(1)添加元素
collection.add("椰子");
collection.add("西瓜");
collection.add("橙子");
System.out.println("元素个数 " + collection.size());
System.out.println(collection);
//(2)删除
//collection.remove("西瓜");
//collection.clear();
//System.out.println("删除后 " + collection);
//(3)遍历【重点】
//3.1使用增强for
for (Object object : collection) {
System.out.println(object);
}
//3.2 forEach 的lambda 形式遍历
collection.forEach(str->{
System.out.println(str);
});
/**
* 3.3 使用迭代器(迭代器是专门用来遍历集合的方式)
* next();获取下一个元素
* remove();删除当前元素
*/
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
String next = (String)iterator.next();
System.out.println(next);
//迭代器在使用过程,除了自己,不允许别的对象改动里面的元素,不然抛异常
//并发修改异常java.util.ConcurrentModificationException
//collection.remove(next);
if (next == "西瓜"){//删除内容为西瓜的元素
iterator.remove();//只能用迭代器自己的删除方法
}
}
System.out.println(collection);
//(4)判断
System.out.println(collection.contains("橙子"));
System.out.println(collection.isEmpty());
}
}
元素是Student类
package collectionsFramework;
public class Student {
private String name;
private int stuNo;
public Student(String name, int stuNo) {
this.name = name;
this.stuNo = stuNo;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
/*@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return getStuNo() == student.getStuNo() &&
getName().equals(student.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getStuNo());
}
*/
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", stuNo=" + stuNo +
'}';
}
}
public class TestCollection2 {
public static void main(String[] args) {
//创建集合和对象
Collection collection = new ArrayList();
Student student1 = new Student("Piter", 1);
Student student2 = new Student("Marry", 2);
Student student3 = new Student("Jen", 3);
//1、添加数据
collection.add(student1);
collection.add(student2);
collection.add(student3);
collection.add(student3);//可加两个一样的
System.out.println(collection.toString());
//2、删除
collection.remove(student3);//只删除一个,可再删除
//删除不掉,因为new的地址不一样,除非修改hashCode和equals方法
collection.remove(new Student("Marry", 2));
System.out.println(collection.toString());
//3、遍历
//3.1 增强for
System.out.println("增强for");
for (Object object : collection) {
Student student=(Student) object;
System.out.println(student.toString());
}
//3.2 forEach 和lambda
System.out.println("forEach 和lambda");
collection.forEach(student->{
System.out.println(student.toString());
});
//3.3 迭代器
System.out.println("迭代器");
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
Student student = (Student) iterator.next();
System.out.println(student);
}
//4、判断
System.out.println(collection.contains(student1));
System.out.println(collection.isEmpty());
}
}
3、List接口与实现类
3个List实现类的总结:
-
ArrayList 【重点】
1、 数组结构实现,必须要连续空间,查询快、增删慢、jdk1.2版本、运行效率块、线程不安全。 (当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高)
2、 对比collection,它有列表迭代器,能向前或向后访问,还能用subList()获取List 的子集。重写equals(),如对比属性值,能删除属性一样但对象不一样的元素
3、 查看源码的总结: 没有添加元素时数组大小是0,添加后元素小于10 就默认是10,超过10需要扩容,且每次扩容是原来的1.5倍,比如原来是10,扩容规则是:10+10/2(原码里是右移一位)。 -
Vector
数组结构实现,查询快、增删慢
jdk1.0版本,运行效率慢、线程安全 -
LinkedList
双向链表结构实现,无需连续空间,增删快,查询慢,线程不安全
LinkedList不要用get(i)遍历,它每次都从头开始找,效率极低
ArrayList和LinkedList结构对比:
1)ArrayList
ArrayList的一些属性:
1 public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3 {
4 // 版本号
5 private static final long serialVersionUID = 8683452581122892189L;
6 // 添加一个元素前是0,添加后 默认容量 10
7 private static final int DEFAULT_CAPACITY = 10;
8 // 空对象数组
9 private static final Object[] EMPTY_ELEMENTDATA = {};
10 // 缺省空对象数组
11 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
12 // 存放元素的数组
13 transient Object[] elementData;
14 // 实际元素大小,默认为0
15 private int size;
16 // 最大数组容量
17 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
18 }
ArrayList的add()源码 6步 分析:
public boolean add(E e) {
//1、最开始size=0
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//2、此处minCapacity = size+1 = 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//3、elementData 默认是空对象数组
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//(DEFAULT_CAPACITY=10) > (minCapacity=1),所以返回 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//4、返回的10 传入这个方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 10-0=10 > 0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//6、扩容的核心方法
//此时 minCapacity=10
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//0
int newCapacity = oldCapacity + (oldCapacity >> 1);//0
//0-10 < 0条件成立
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//10
//10-0x7fffffff <0 条件不成立,不走这
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//创建一个长度为newCapacity的对象数组, copy原有数组到newCapacity,再把newCapacity赋值给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList测试代码
/**
* ArrayList的使用
* 特点:有序,有下标,可重复
*/
public class TestList {
public static void main(String[] args) {
//创建集合对象
ArrayList list = new ArrayList<>();
Student student1 = new Student("Piter", 1);
Student student2 = new Student("Marry", 2);
Student student3 = new Student("Jen", 3);
//1、添加数据,如果装的是基本数据类型会被自动装箱
list.add(student1);
list.add(student2);
list.add(student3);
//2、遍历
//2.1 使用for循环,效率极低,因为get()每次从头开始
System.out.println("普通for循环");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//2.2 增强for
System.out.println("增强for");
for (Object object : list) {
System.out.println(object);
}
//增强for 的 lambda写法
System.out.println("增强for 的 lambda写法");
list.forEach(stu->{
System.out.println(stu);
});
//2.3 迭代器
System.out.println("迭代器");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//2.4 列表迭代器ListIterator,可以向前或向后,添加、删除、修改元素
System.out.println("列表迭代器从前往后ListIterator");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
System.out.println(listIterator.nextIndex()+": " +listIterator.next());
}
System.out.println("列表迭代器从后往前ListIterator");
while (listIterator.hasPrevious()){
System.out.println(listIterator.previousIndex()+": " +listIterator.previous());
}
//3、判断
System.out.println(list.contains(student1));
System.out.println(list.isEmpty());
//4、获取下标位置
System.out.println(list.indexOf(student2));
//5、subList:返回子集,包头不包尾
System.out.println("子集");
List subList = list.subList(0, 2);
System.out.println(subList.toString());
//6、删除
list.remove(student3);
list.remove(0);
System.out.println("删除后");
System.out.println(list.toString());
}
}
2)Vector
数组结构实现,查询快、增删慢
jdk1.0版本,运行效率慢、线程安全
现在开发用得少,功能大部分和list相同
遍历有自己特殊的枚举器
public class TestVector {
public static void main(String[] args) {
Vector vector = new Vector<>();
vector.add("huahau");
vector.add("nono");
vector.add("yeye");
//遍历使用枚举器
Enumeration en = vector.elements();
while (en.hasMoreElements()){
System.out.println(en.nextElement());
}
//vector的其他方法......
System.out.println(vector.firstElement());
System.out.println(vector.lastElement());
}
}
3)LinkedList
LinkedList源码分析
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
//初始大小
transient int size = 0;
//第一个节点
transient Node<E> first;
//最后一个节点
transient Node<E> last;
}
//分析add()方法
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//最开始last=null,所以l=null
final Node<E> l = last;
//新建一个节点,且l是前一个节点值,e是存入的元素,null是下一个节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//第一次l=null,之后就不是了
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
测试 LinkedList
public class TestLinkedList {
public static void main(String[] args) {
//创建集合对象,size默认0,数组大小0
LinkedList<Student> linkedList = new LinkedList<>();
Student student1 = new Student("Piter", 1);
Student student2 = new Student("Marry", 2);
Student student3 = new Student("Jen", 3);
//1、添加数据,如果装的是基本数据类型会被自动装箱
linkedList.add(student1);
linkedList.add(student2);
linkedList.add(student3);
System.out.println(linkedList.toString());
//2、遍历
//2.1 增强for
System.out.println("增强for");
for (Object obj : linkedList) {
Student s=(Student)obj;
System.out.println(s);
}
System.out.println("增强for lambda");
linkedList.forEach(stu->{
System.out.println(stu);
});
//2.2迭代器
System.out.println("迭代器");
Iterator it = linkedList.iterator();
while (it.hasNext()){
Student stu = (Student)it.next();
System.out.println(stu);
}
//2.3列表迭代器
System.out.println("列表迭代器");
ListIterator lit = linkedList.listIterator();
while (lit.hasNext()){
Student stu = (Student)lit.next();
System.out.println(stu);
}
//3、删除
linkedList.remove(student1);
linkedList.remove(1);
//重写equals才可删除
linkedList.remove(new Student("Marry", 2));
System.out.println("删除后"+ linkedList.toString());
linkedList.clear();
System.out.println("clear后"+linkedList.toString());
}
}
4.Set接口与实现类
- HashSet 的使用
- 存储结构:哈希表(数组+链表+红黑树)
- 特点:无序、无下标、不能重复
- new HashSet时 其实是new HashMap,且它的add()方法是 调用HashMap的put(key)
- 重点:存储过程中判断重复元素的依据(可以根据需要重写这两个方法)
(1)根据hashCode计算保存的位置,如果该位置为空直接保存如果不为空执行第二步
(2)再执行equals方法,如果equals方法为true则认为是重复,否则加入形成链表
重写的 hashCode() 源码分析:
@Override
public int hashCode() {
return Objects.hash(getName(), getStuNo());
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
//(1)把结果加上 31倍数 是考虑到它是质数,可以尽量减少哈希碰撞
//(2)31 可以提高执行效率,它可以用位移表示来计算 31*i=(i<<5)-i
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
HashSet测试的代码:
/**
* HashSet集合使用
* 存储结构:哈希表(数组+链表+红黑树(防止二叉排序树退化成链表,优化排序树))
* 特点:无序、无下标、不能重复
* (1)根据hashCode计算保存的位置,如果该位置为空直接保存,如果不为空执行第二步
* (2)再执行equals方法,如果equals方法为true则认为是重复,否则加入形成链表
*
*/
public class TestHashSet {
public static void main(String[] args) {
HashSet<Student> hashSet= new HashSet<>();
Student student1 = new Student("Piter", 1);
Student student2 = new Student("Marry", 2);
Student student3 = new Student("Jen", 3);
//1、加数据,key不同,value可以相同
hashSet.add(student1);
hashSet.add(student2);
hashSet.add(student3);
//不重写hashCode() 和 equals()时,虽然这个学生的对象内容和 3 相同,但new出来hash不一样,可加
//重写hashCode() 和 equals()后,定义hash根据名字和学号的值生成,所以内容一样就加不了
hashSet.add(new Student("Jen", 3));
System.out.println("元素个数 " + hashSet.size());//3
System.out.println(hashSet.toString());//3条信息
}
}
- TreeSet 的使用
存储结构:红黑树(红黑树放弃了追求完全平衡,追求大致平衡)
要求:学生类 必须要实现 Comparable接口 或 Comparator接口,compareTo()方法返回值是0就认为是重复元素
实现Comparable接口方式:
//学术类实现Comparable 时重写compareTo 的代码示例
@Override
public int compareTo(Student o) {
//this是 add 进来的那个对象,按学号从小到大,学号相同就比较名字
// 每次加入一个对比次数是logn
int n1 = this.stuNo - o.getStuNo();
int n2=this.name.compareTo(o.getName());
//先比较号数大小,如果相同就返回名字的比较结果
return n1==0 ? n2:n1;
}
/**
* TreeSet的使用
* 存储结构:红黑树
* :学生类 必须要实现 Comparable接口,compareTo()方法返回值是0就认为是重复元素
*/
public class TestTreeSet {
public static void main(String[] args) {
TreeSet<Student> treeSet = new TreeSet<>();
Student student1 = new Student("Piter", 3);
Student student2 = new Student("Marry", 1);
Student student3 = new Student("Jen", 2);
Student student4 = new Student("Hua", 4);
Student student5 = new Student("Hua", 4);
//1、加数据,key不同,value可以相同
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
treeSet.add(student4);
//compareTo()方法返回值是0 就认为是重复元素,加不了
treeSet.add(student5);
System.out.println(treeSet);
}
}
实现Comparator接口方式:
/**
* Comparator:实现自己定制的比较器
* Comparator:可比较的
*/
public class TestTreeSet2 {
public static void main(String[] args) {
//创建集合时就指定比较规则,实现自己定制的比较器,以匿名内部类方式
TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//this是 add 进来的那个对象,按学号从小到大,学号相同就比较名字
// 每次加入一个对比次数是logn
int n1 = o1.getStuNo() - o2.getStuNo();
int n2=o1.getName().compareTo(o2.getName());
//先比较号数大小,如果相同就返回名字的比较结果
return n1==0 ? n2:n1;
}
});
Student student1 = new Student("Piter", 3);
Student student2 = new Student("Marry", 1);
Student student3 = new Student("Jen", 2);
Student student4 = new Student("Hua", 4);
Student student5 = new Student("Hua", 4);
//1、加数据,key不同,value可以相同
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
treeSet.add(student4);
//compareTo()方法返回值是0 就认为是重复元素,加不了
treeSet.add(student5);
System.out.println(treeSet);
}
}
5.Map接口与实现类
1)HashMap:
- (1) 存储结构:哈希表(数组+链表+红黑树)
- (2)HashMap刚创建时,table是null,为了节省空间,当添加第一个元素时,table容量是16
- (3)扩展因子默认0.75,达到该阈值后数组扩展为原来的2倍,减少哈希碰撞。
- (4)扩容逻辑:创建一个新的桶数组,大小是原桶数组的两倍;重新分配元素:遍历旧桶数组,将每个元素重新计算哈希值,并分配到新桶数组中
- (5)为什么是2倍:下标index = hash & (数组大小 - 1),方便做与运算,传统的取模运算效率低
- (6)jdk1.8 数组容量达到64,且达到某链表达到 8时链表变为红黑树,小于6时变回链表 ,目的提高查询效率
- (7)jdk 1.8 以前是链表头插入,1.8后是尾插入。
- (8)允许 null 作为key 或 value
Map底层工作流程图:
HashMap源码分析
//一些属性值
//初始化数组大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大数组大小2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子,当数组元素被占用超过75% 时就扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//数组容量达到64,且达到某链表达到 8时链表变为红黑树
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;
//红黑树小于6时变回链表
static final int UNTREEIFY_THRESHOLD = 6;
//table 数组默认为null
transient Node<K,V>[] table;
//无参构造函数,直接赋值加载因子 0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
2)HashTable:
- 线程安全,运行效率慢;不允许 null 作为 key 或 value
3)Properties:
- 是HashTable 的子类,要求key 和 value 都是 String 。通常用于配置文件的读取。
4)TreeMap
- 实现了SortedMap接口(是Map的子接口),可以对key 自动排序,和TreeSet差不多, TreeSet在new时其实是 new TreeMap ,需要重写CompareTo 方法等,方法也是类似。
6.泛型集合与工具类
1、泛型的设计:
当 Student 没有传入时,默认是Object类型,如果一个链表加了String,Student (都是一种Object所以可以加),
当遍历时要转为Student ,那String因为不是Student 报错。
如下的差别:
//没规定传入什么类,默认是Object
LinkedLis= linkedList = new LinkedList<>();
//规定只能存Student类型
LinkedList <Student> linkedList = new LinkedList<>();
泛型测试代码:
/**
* 泛型类
* 语法:类名<T> ,可以多个参数<T,K,L>
* T是类型占位符,只能表示一种引用类型
*/
public class MyGeneric<T> {
//使用泛型T
//1、创建变量
T t;
//2、泛型T作为方法参数
public void show(T t){
System.out.println(t.toString());
}
//3、泛型T作为方法返回值
public T getT(){
return t;
}
}
/**
* 泛型接口
* 语法:接口名<T>
* 注意:不能给泛型 静态常量
*/
public interface MyInterface<T> {
String name="haha";
T server(T t);
}
/**
* 泛型方法
* 语法:<T> 返回值
*/
public class MyGenericMethod {
//泛型方法
public <T> void show(T t){
System.out.println("泛型方法"+t);
}
}
//当实现类知道是传 String 类型
public class MyInterfaceImpl implements MyInterface<String> {
@Override
public String server(String str) {
System.out.println(str);
return str;
}
}
/**
* 当实现类还不确定要传什么类型时,根据实现类的T 赋值给接口的T
*/
public class MyInterfaceImpl2<T> implements MyInterface<T> {
@Override
public T server(T t) {
System.out.println(t);
return t;
}
}
//测试类
public class TestGeneric {
public static void main(String[] args) {
//1、测试使用泛型类创建对象
//注意:泛型只能是引用类型,不同泛型对象之间不能相互赋值
MyGeneric<Student> myGeneric = new MyGeneric<>();
//传入相应学生类型,调用show方法
myGeneric.show(new Student("it", 01));
//给属性 t 赋值,再通过get取出
Student student = new Student("haha", 20);
myGeneric.t=student;
System.out.println(myGeneric.getT());
//2、测试使用泛型接口,确定只能String
MyInterfaceImpl impl = new MyInterfaceImpl();
impl.server("sdfsoufo");
//3、测试使用泛型接口2
MyInterfaceImpl2<Integer> impl2 = new MyInterfaceImpl2<>();
impl2.server(1000);
MyInterfaceImpl2<String> impl22 = new MyInterfaceImpl2<>();
impl22.server("sdffd");
//4、测试使用泛型方法
MyGenericMethod myGenericMethod = new MyGenericMethod();
myGenericMethod.show("huahau");
myGenericMethod.show(100);
myGenericMethod.show(21.3);
}
}
2、Collections工具类的使用:
//Collections工具类的使用
public class TestCollectionsUtils {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(201);
list.add(2);
list.add(1);
list.add(21);
list.add(34);
list.add(71);
//1、sort 从小到大排序
System.out.println("排序前 "+list.toString());
Collections.sort(list);
System.out.println("排序后 "+list.toString());
//2、binarySearch 使用二叉搜索算法搜索指定对象的指定列表。找到返回正数,否则返回负数
int i = Collections.binarySearch(list, 34);
System.out.println(i);
//3、复制,要求两个集合大小必须一样大
ArrayList<Integer> list2 = new ArrayList<>();
//让两个集合大小一样大
for (int j = 0; j <list.size() ; j++) {
list2.add(0);
}
Collections.copy(list2,list);
System.out.println("Copy后 "+list2.toString());
// 4、reverse反转
Collections.reverse(list);
System.out.println("反转后 "+ list.toString());
//5、shuffle 打乱,每次运行都不一样
Collections.shuffle(list);
System.out.println("打乱后 "+ list);
//6、list 转为数组,Integer[0] 给0小了它自己会根据大小补,但是给大了,就有一些没用到的是null元素
Integer[] integers = list.toArray(new Integer[0]);
System.out.println("集合转数组 "+Arrays.toString(integers));
//7、数组转为集合
String[] names = {"Piter","Mary","Faa"};
System.out.println(names);
//转换完是一个受限集合,不能添加和删除,需要转换基本类型时应该写其对应的包装类
List<String> list3 = Arrays.asList(names);
System.out.println("数组转为集合"+list3);
}
}