Java集合框架
参考视频:【千锋】Java集合框架详解 BV1zD4y1Q7Fw
文章目录
0.集合概述
目的:对象的容器,实现了对对象常用的操作
与数组的区别:
- 数组长度固定,集合长度不固定。
- 数组刻意存储基本类型和引用类型,集合只能存储引用类型
位置:java.util.*;
1.Colleection接口
特点:代表一组任意类型的对象,无序、无下标、不能重复。
常见方法
boolean add(Object obj) //添加一个对象
boolean addAll(Collection c) //将一个集合中的所有对象添加到此集合中
void clear() //清空所有对象
boolean contaions(Object o) //检查此集合种是否包含o对象
boolean equals(Object o) //比较此集合是否与指定对象相等
boolean isEmpty() //判断集合是否为空
boolean remove(Object o) //移出集合中的对象o
int size() //当前集合中的元素个数
Object[] toArray() //将集合转换成数组
测试1
public class MainTest {
public static void main(String[] args) {
Collection testCollection=new ArrayList();
//添加元素
System.out.println("==============添加元素===============");
testCollection.add("橘子");
testCollectiwon.add("张三");
testCollection.add("Terminal");
testCollection.add("e");
System.out.println("元素个数:"+testCollection.size());
System.out.println(testCollection.toString());
//删除元素
System.out.println("==============删除元素===============");
testCollection.remove(1);
System.out.println("元素个数:"+testCollection.size());
System.out.println(testCollection.toString());
//普通遍历元素
System.out.println("=============普通遍历元素=============");
System.out.println("元素个数:"+testCollection.size());
for(Object x:testCollection){
System.out.println(x.toString());
}
//迭代器遍历元素
System.out.println("=============迭代器遍历元素=============");
System.out.println("元素个数:"+testCollection.size());
Iterator it=testCollection.iterator();
while(it.hasNext()){
System.out.println(it.next().toString());
// 迭代器遍历过程中不能对testCollection使用方法。如testClooection.remove(e);
// 不过可以执行it.remove()
}
//判断集合是否为空
System.out.println("===========判断集合是否为空===========");
System.out.println("集合为空?"+testCollection.isEmpty());
System.out.println("执行clear方法");
testCollection.clear();
System.out.println("集合为空?"+testCollection.isEmpty());
}
}
测试2
/*
学生类
*/
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
public class MainTest {
public static void main(String[] args) {
Collection testCollection=new ArrayList();
Student s1=new Student("张三",18);
Student s2=new Student("李四", 20);
Student s3=new Student("王五", 19);
//添加数据 同一个对象可以重复添加
System.out.println("=============添加数据================");
testCollection.add(s1);
testCollection.add(s2);
testCollection.add(s3);
System.out.println("元素个数:"+testCollection.size());
System.out.println(testCollection.toString());
//删除数据
System.out.println("=============删除数据================");
testCollection.remove(s3);
System.out.println("元素个数:"+testCollection.size());
System.out.println(testCollection.toString());
//遍历数据(迭代器)
System.out.println("=============迭代器遍历================");
Iterator it=testCollection.iterator();
while(it.hasNext()){
System.out.println(it.next().toString());
}
}
}
2.List接口与实现类
2.1 List接口
特点: 有序、有下标、元素可重复
常见方法
void add(int index, Object o) //在index位置插入对象o
boolean addAll(index,Collection c) //将集合c中的所有元素添加到该List集合的index位置
Object get(int index) //返回集合中指定位置的元素
List subList(int fromIndex, int toIndex) //返回fromIndex和toIndex之间的集合元素
测试
public class MainTest {
public static void main(String[] args) {
//添加数据 同一个对象可以重复添加
List testList=new ArrayList();
testList.add("a");
testList.add("b");
testList.add("d");
testList.add(0,"c");
System.out.println(testList.toString());
//删除数据
testList.remove(0); //等价于testList.remove("c");
//普通遍历
for(int i=0;i<testList.size();i++){
System.out.println(testList.get(i));
}
//迭代器遍历
System.out.println("从左往右遍历");
ListIterator it=testList.listIterator();
while(it.hasNext()){
System.out.println(it.next().toString());
}
System.out.println("上面遍历完成后从右往左遍历");
while (it.hasPrevious()){
System.out.println(it.previous().toString());
}
//判断有没有元素"c"
System.out.println(testList.contains("c"));
//获取位置
System.out.println("元素b的位置:"+testList.indexOf("b"));
//获取子串
List subTest=testList.subList(1,3);
System.out.println(subTest.toString());
}
}
2.2 List实现类
2.2.1 ArrayList
数组结构实现,查询快、增删慢。需要开辟连续空间。
ArrayList测试
public class MainTest {
public static void main(String[] args) {
//添加数据 同一个对象可以重复添加
ArrayList test=new ArrayList();
Student s1=new Student("唐", 21);
Student s2=new Student("何", 22);
Student s3=new Student("余", 21);
test.add(s1);
test.add(s2);
test.add(s3);
System.out.println("元素个数:"+test.size());
System.out.println(test.toString());
//删除数据
test.remove(s1);
//这里不能写成test.remove(new Student("唐",21));
//这里新建的对象与s1引用的是不同的对象
//迭代器遍历
System.out.println("从左往右遍历");
ListIterator it=test.listIterator();
while(it.hasNext()){
System.out.println(it.next().toString());
}
System.out.println("上面遍历完成后从右往左遍历");
while (it.hasPrevious()){
System.out.println(it.previous().toString());
}
//判断
System.out.println(test.isEmpty());
//获取位置 -1代表没找到
System.out.println(test.indexOf(s1));
}
}
源码分析
默认容量大小:private static final int DEFAULT_CAPACITY = 10;
存放元素的数组:transient Object[] elementData;
实际元素个数:private int size;
创建对象时调用的无参构造函数:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这段源码说明当你没有向集合中添加任何元素时,集合容量为0。
默认的10个容量与下面的add方法相关:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
假设new了一个数组,当前容量为0,size当然也为0。这时调用add方法进入到ensureCapacityInternal(size + 1)
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
elementData就是存放元素的数组,当前容量为0,if条件成立,返回默认容量DEFAULT_CAPACITY
也就是10。这个值作为参数又传入ensureExplicitCapacity()
方法中,进入该方法查看源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
已知elementData.length=0,故if条件为真,调用grow()函数
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
这个方法先声明了一个oldCapacity变量将数组长度赋给它,其值为0;又声明了一个newCapacity变量其值为oldCapacity+一个增量
,可以发现这个增量是和原数组长度有关的量,当然在这里也为0。
第一个if条件满足,newCapacity的值为10。
第二个if条件不成立,也可以不用注意,因为MAX_ARRAY_SIZE的定义如下(数值很大):
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
最后一句话是给elementData赋了一个新的长度,Arrays.copyOf()
方法返回的数组是新的数组对象,原数组对象不会改变,该拷贝不会影响原来的数组。copyOf()
的第二个变量用于指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。
这时候再回到add的方法中,接着就向下执行elementData[size++] = e;
2.2.2 Vector
大体内容、特性与ArrayList差不多,现在已不常用。
2.2.3 LinkedList
特点:增删快,查询慢。无需开辟连续空间。
大体方法与ArrayList相近
LinkedList测试
public class MainTest {
public static void main(String[] args) {
LinkedList linkedList=new LinkedList<>();
Student s1=new Student("唐", 21);
Student s2=new Student("何", 22);
Student s3=new Student("余", 21);
//1.添加元素
linkedList.add(s1);
linkedList.add(s2);
linkedList.add(s3);
linkedList.add(s3);
System.out.println("元素个数:"+linkedList.size());
System.out.println(linkedList.toString());
//2.删除元素
/*
* linkedList.remove(new Student("唐", 21));
* System.out.println(linkedList.toString());
*/
//3.遍历
//3.1 使用for
for(int i=0;i<linkedList.size();++i) {
System.out.println(linkedList.get(i));
}
//3.2 使用增强for
for(Object object:linkedList) {
Student student=(Student) object;
System.out.println(student.toString());
}
//3.3 使用迭代器
Iterator iterator =linkedList.iterator();
while (iterator.hasNext()) {
Student student = (Student) iterator.next();
System.out.println(student.toString());
}
//3.4 使用列表迭代器(略)
//4. 判断
System.out.println(linkedList.contains(s1));
System.out.println(linkedList.isEmpty());
System.out.println(linkedList.indexOf(s3));
}
}
源码分析
LinkedList首先有三个属性:
链表大小:transient int size = 0;
(指向)第一个结点/头结点:transient Node<E> first;
(指向)最后一个结点/尾结点:transient Node<E> last;
Node类型定义
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;
}
}
添加元素:
public boolean add(E e) {
linkLast(e); //尾插法
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null); //初始化要添加的新结点
last = newNode;
if (l == null) //添加结点前链表为空
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
3. 泛型和工具类
泛型概述
Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数进行传递
常见形式:泛型类、泛型接口、泛型方法
语法:<T,...>
T称为占位符,表示一种引用类型。习惯上会使用T、E、K、V等字母作为占位符。
优点:
- 提高代码的重用性
- 防疫类型转换异常,提高代码的安全性
3.1 泛型类
/**
* 泛型类
* 语法:类名<T>
* T是类型占位符,表示一种引用类型,编写多个使用逗号隔开
*
*/
public class myGeneric<T>{
//1.创建泛型变量
//不能使用new来创建,如T t=new T(),因为泛型是不确定的类型,也可能拥有私密的构造方法。
T t;
//2.泛型作为方法的参数
public void show(T t) {
System.out.println(t);
}
//泛型作为方法的返回值
public T getT() {
return t;
}
}
import java.util.*;
import java.lang.*;
public class MainTest {
public static void main(String[] args) {
//使用泛型类创建对象
myGeneric<String> myGeneric1=new myGeneric<String>();
myGeneric1.t="tang";
myGeneric1.show("he");
myGeneric<Integer> myGeneric2=new myGeneric<Integer>();
myGeneric2.t=10;
myGeneric2.show(20);
Integer integer=myGeneric2.getT();
}
}
3.2 泛型接口
3.2.1 实现接口时确定泛型类
/**
* 泛型接口
* 语法:接口名<T>
* 注意:不能创建泛型静态常量
*/
public interface MyInterface<T> {
//创建常量
String nameString="tang";
T server(T t);
}
/**
* 实现接口时确定泛型类
*/
public class MyInterfaceImpl implements MyInterface<String>{
@Override
public String server(String t) {
System.out.println(t);
return t;
}
}
测试
public class MainTest {
public static void main(String[] args) {
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
myInterfaceImpl.server("xxx");
}
}
3.2.2 实现接口时不确定泛型类
/**
* 实现接口时不确定泛型类
*/
public class MyInterfaceImpl2<T> implements MyInterface<T>{
@Override
public T server(T t) {
System.out.println(t);
return t;
}
}
public class MainTest {
public static void main(String[] args) {
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
myInterfaceImpl.server("xxx");
}
}
3.3 泛型方法
/**
* 泛型方法
* 语法:<T> 返回类型
*/
public class MyGenericMethod {
public <T> void show(T t) {
System.out.println("泛型方法"+t);
}
}
测试
public class MainTest {
public static void main(String[] args) {
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("bbbbb");
myGenericMethod.show(123);
myGenericMethod.show(3.14159);
}
}
3.4 泛型集合
定义:参数化类型、类型安全的集合,强制集合元素的类型必须一致
特点:
- 编译时即可检查,而非运行时抛出异常
- 访问时,不必类型转换(即拆箱)
- 不同泛型之间的引用不能相互赋值。泛型不存在多态
public class MainTest {
public static void main(String[] args) {
LinkedList<String>list=new LinkedList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//list.add(3); 会直接报错
System.out.println(list.toString());
}
}
相关声明
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{...}
假如使用泛型集合时不声明相关泛型参数,即new LinkedList<>();
,会默认传递Object类型。
4.Set接口与实现类
4.1 Set接口
特点:无序、无下标、元素不可重复
方法:全部继承自Collection中的方法
/**
* 测试Set接口的使用
* 特点:1.无序,没有下标;2.重复
* 1.添加数据
* 2.删除数据
* 3.遍历
* 4.判断
*/
public class Demo1 {
public static void main(String[] args) {
Set<String> set=new HashSet<String>();
//1.添加数据
set.add("tang");
set.add("he");
set.add("yu");
System.out.println("数据个数:"+set.size());
System.out.println(set.toString());//无序输出
//2.删除数据
set.remove("tang");
System.out.println(set.toString());
//3.1 使用增强for
for (String string : set) {
System.out.println(string);
}
//3.2 使用迭代器
Iterator<String> iterator=set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//4.判断
System.out.println(set.contains("tang"));
System.out.println(set.isEmpty());
}
}
4.2 Set实现类
4.2.1 HashSet
基于HashCode计算元素存放位置
当存入元素的hashcode相同时,会调用equals进行确认,若结果为true,则拒绝存储
测试
/**
* Person类
*/
public class Person {
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Peerson [name=" + name + ", age=" + age + "]";
}
}
/**
* HashSet集合的使用
* 存储结构:哈希表(数组+链表+红黑树)
* 1.添加元素
* 2.删除元素
* 3.遍历
* 4.判断
*/
public class Demo3 {
public static void main(String[] args) {
HashSet<Person> hashSet=new HashSet<>();
Person p1=new Person("tang",21);
Person p2=new Person("he", 22);
Person p3=new Person("yu", 21);
//1.添加元素
hashSet.add(p1);
hashSet.add(p2);
hashSet.add(p3);
//重复,添加失败
hashSet.add(p3);
//直接new一个相同属性的对象,依然会被添加,不难理解。
//假如相同属性便认为是同一个对象,怎么修改?
hashSet.add(new Person("yu", 21));
System.out.println(hashSet.toString());
//2.删除元素
hashSet.remove(p2);
//3.遍历
//3.1 增强for
for (Person person : hashSet) {
System.out.println(person);
}
//3.2 迭代器
Iterator<Person> iterator=hashSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//4.判断
System.out.println(hashSet.isEmpty());
//直接new一个相同属性的对象结果输出是false,不难理解。
//注:假如相同属性便认为是同一个对象,该怎么做?
System.out.println(hashSet.contains(new Person("tang", 21)));
}
}
内部哈希表存储过程
-
根据hashCode计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
-
执行equals方法,如果方法返回true,则认为是重复,拒绝存储,否则形成链表(当一个位置的链表过长时会改用红黑树存储)。
数组+链表的形式
数组+红黑树的形式
单个哈希地址的结点过多时,为了方便查找,会将同地址结点的存储方式改为红黑树
要实现一开始所说的两步存储过程,可以重载hashCode和equals代码
@Override
public int hashCode() {
final int prime = 31;
/*使用31的原因:
* 1.31是一个质数,这样的数字在计算时可以尽量减少哈希冲突
* 2.可以提高执行效率,31*i=(i<<5)-i,31乘以一个数可以转换成移位操作
*/
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
4.2.2 TreeSet
- 基于排序顺序实现不重复
- 实现了SortedSet接口,对集合元素自动排序
- 元素对象的类型必须实现Comparable接口,指定排序规则
- 通过CompareTo方法确定是否为重复元素
测试1
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +"name='" + name + '\'' +", age=" + age +'}';
}
//重载conpareTo方法
@Override
public int compareTo(Person o) {
int n1=this.getName().compareTo(o.getName());
int n2=this.getAge()-o.getAge();
return n1==0?n2:n1;
}
}
public class MainTest {
public static void main(String[] args) {
TreeSet<Person> persons=new TreeSet<Person>(new Comparator<Person>() {
//自定义排序方式
@Override
public int compare(Person o1, Person o2) {
int n1=o1.getName().compareTo(o2.getName());
int n2=o1.getAge()-o2.getAge();
return n1==0?n2:n1;
}
});
Person p1=new Person("tang",21);
Person p2=new Person("he", 22);
Person p3=new Person("yu", 21);
persons.add(p1);
persons.add(p2);
persons.add(p3);
for(Person person:persons){
System.out.println(person.toString());
}
}
}
测试2
/**
* 要求:使用TreeSet集合实现字符串按照长度进行排序
* helloworld tangrui hechengyang wangzixu yuguoming
* Comparator接口实现定制比较
*/
public class Demo6 {
public static void main(String[] args) {
TreeSet<String> treeSet=new TreeSet<String>(new Comparator<String>() {
@Override
//先比较字符串长度
//再比较字符串
public int compare(String o1, String o2) {
int n1=o1.length()-o2.length();
int n2=o1.compareTo(o2);
return n1==0?n2:n1;
}
});
treeSet.add("helloworld");
treeSet.add("tangrui");
treeSet.add("hechenyang");
treeSet.add("yuguoming");
treeSet.add("wangzixu");
System.out.println(treeSet.toString());
}
}
5.Map接口与实现类
5.1 Map接口
特点:
- 用于存储任意键值对(Key-Value)。
- 键:无序、无下标、不允许重复(唯一)。
- 值:无序、无下标、允许重复。
常见方法:
V put(K key,V value) //将对象存入到集合中,关联键值。key重复则覆盖原值。
Object get(Object key) //根据键获取相应的值。
Set<K> //返回所有的key
Collection<V> values() //返回包含所有值的Collection集合。
Set<Map.Entry<K,V>> //键值匹配的set集合
public class MainTest {
public static void main(String[] args) {
Map<String,Integer> map=new HashMap<String, Integer>();
//1.添加元素
map.put("tang", 21);
map.put("he", 22);
map.put("fan", 23);
System.out.println(map.toString());
//2.删除元素
map.remove("he");
System.out.println(map.toString());
//3.遍历
//3.1 使用keySet();
for (String key : map.keySet()) {
System.out.println(key+" "+map.get(key));
}
//3.2 使用entrySet();效率较高
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}
}
}
5.2 Map实现类
5.2.1 HashMap
JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value
测试
/**
* Student类
*/
public class Student {
private String name;
private int id;
public Student(String name, int id) {
super();
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 "Student [name=" + name + ", age=" + id + "]";
}
}
/**
* HashMap的使用
* 存储结构:哈希表(数组+链表+红黑树)
*/
public class MainTest {
public static void main(String[] args) {
HashMap<Student, String> hashMap=new HashMap<Student, String>();
Student s1=new Student("tang", 36);
Student s2=new Student("yu", 101);
Student s3=new Student("he", 10);
//1.添加元素
hashMap.put(s1, "成都");
hashMap.put(s2, "杭州");
hashMap.put(s3, "郑州");
//添加失败,但会更新值
hashMap.put(s3,"上海");
//添加成功,不过两个属性一模一样;
//注:假如相同属性便认为是同一个对象,怎么修改?
hashMap.put(new Student("he", 10),"上海");
System.out.println(hashMap.toString());
//2.删除元素
hashMap.remove(s3);
System.out.println(hashMap.toString());
//3.遍历
//3.1 使用keySet()遍历
for (Student key : hashMap.keySet()) {
System.out.println(key+" "+hashMap.get(key));
}
//3.2 使用entrySet()遍历
for (Map.Entry<Student, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}
//4.判断
//注:同上
System.out.println(hashMap.containsKey(new Student("he", 10)));
System.out.println(hashMap.containsValue("成都"));
}
}
和HashSet类似,可以重载hashCode和equals自定义哈希地址运算和处理
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
源码分析
内部存储过程可以回看HashSet部分,HashSet内部也是用map实现的
默认初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 或16
数组最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
默认加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
链表调整为红黑树的链表长度阈值(JDK1.8):static final int TREEIFY_THRESHOLD = 8;
红黑树调整为链表的链表长度阈值(JDK1.8):static final int UNTREEIFY_THRESHOLD = 6;
链表调整为红黑树的数组最小阈值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64;
HashMap存储的数组:transient Node<K,V>[] table;
HashMap存储的元素个数:transient int size;
- 默认加载因子是什么?
- 就是判断数组是否扩容的一个因子。假如数组容量为100,如果HashMap的存储元素个数超过了100*0.75=75,那么就会进行扩容。
- 链表调整为红黑树的链表长度阈值是什么?
- 假设在数组中下标为3的位置已经存储了数据,当新增数据时通过哈希码得到的存储位置又是3,那么就会在该位置形成一个链表,当链表过长时就会转换成红黑树以提高执行效率,这个阈值就是链表转换成红黑树的最短链表长度;
- 红黑树调整为链表的链表长度阈值是什么?
- 当红黑树的元素个数小于该阈值时就会转换成链表。
- 链表调整为红黑树的数组最小阈值是什么?
- 并不是只要链表长度大于8就可以转换成红黑树,在前者条件成立的情况下,数组的容量必须大于等于64才会进行转换。
HashMap的数组table存储的就是一个个的Node<K,V>类型,很清晰地看到有一对键值,还有一个指向next的指针:
static class Node<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Node<K,V> next;
}
刚创建HashMap时默认大小为0
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
往HashMap中添加元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断table是否为空
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //resize()用于重新设置大小
//判断根据hashcode的到的tab中的一个位置是否为空,为空便直接添加元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else{
//......
}
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; //threshold为阈值
if (oldCap > 0){/*......*/};
else if (oldThr > 0){/*......*/};
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
return newTab;
}
刚创建HashMap时,threshold=0,进入第三个if分支,newCap的值初始化为默认的16。
接下来创建了一个newCap大小的数组并将其赋给了table,刚创建的HashMap对象就在这里获得了初始容量。
回到putVal方法执行第二个if。
当添加的元素超过16*0.75=12时,就会进行扩容
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
if (++size > threshold)
resize();
}
final Node<K,V>[] resize() {
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//......
int newCap;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {/*略*/}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY){
newThr = oldThr << 1; // double threshold
}
}
//......
}
HashSet源码中的HashMap
HashSet的存储结构就是HashMap
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
可见,HashSet中的add方法调用的就是map的put方法,把元素作为map的key传入。
Hashtable
-
JDK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。
-
初始容量11,加载因子0.75。
这个集合在开发过程中已经不用了,稍微了解即可。
Properties
Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。它继承了Hashtable的方法,与流关系密切,此处不详解。
5.2.2 TreeMap
测试
/**
* Student类
*/
public class Student implements Comparable<Student>{
private String name;
private int id;
public Student(String name, int id) {
super();
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 "Student [name=" + name + ", age=" + id + "]";
}
@Override
public int compareTo(Student o) {
int n1 = this.id - o.id;
return n1;
}
}
public class MainTest {
public static void main(String[] args) {
TreeMap<Student, Integer> treeMap=new TreeMap<Student, Integer>();
Student s1=new Student("tang", 36);
Student s2=new Student("yu", 101);
Student s3=new Student("he", 10);
//1.添加元素
treeMap.put(s1, 21);
treeMap.put(s2, 22);
treeMap.put(s3, 21);
//不能直接打印,需要实现Comparable接口,因为红黑树需要比较大小
System.out.println(treeMap.toString());
//2.删除元素
treeMap.remove(new Student("he", 10));
System.out.println(treeMap.toString());
//3.遍历
//3.1 使用keySet()
for (Student key : treeMap.keySet()) {
System.out.println(key+" "+treeMap.get(key));
}
//3.2 使用entrySet()
for (Map.Entry<Student, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}
//4.判断
System.out.println(treeMap.containsKey(s1));
System.out.println(treeMap.isEmpty());
}
}
TreeSet中的TreeMap
类比HashSet和HasMap。TreeSet和TreeMap的关系也类似。
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
{
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
6.Collections工具类
内部集成了一些常用的静态方法。可以回想Arrays类的用法
public static void reverse(List<?> list)//反转集合中元素的顺序
public static void shuffle(List<?> list)//随机重置集合元素的顺序
public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)
测试
/**
* 演示Collections工具类的使用
*
*/
public class MainTest {
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
list.add(20);
list.add(10);
list.add(30);
list.add(90);
list.add(70);
//sort排序
System.out.println(list.toString());
Collections.sort(list);
System.out.println(list.toString());
System.out.println("---------");
//binarySearch二分查找
int i=Collections.binarySearch(list, 10);
System.out.println(i);
//copy复制
List<Integer> list2=new ArrayList<Integer>();
for(int i1=0;i1<5;++i1) {
list2.add(0);
}
//该方法要求目标元素容量大于等于源目标
Collections.copy(list2, list);
System.out.println(list2.toString());
//reserve反转
Collections.reverse(list2);
System.out.println(list2.toString());
//shuffle 打乱
Collections.shuffle(list2);
System.out.println(list2.toString());
//补充:list转成数组
Integer[] arr=list.toArray(new Integer[0]);
System.out.println(arr.length);
//补充:数组转成集合
String[] nameStrings= {"tang","he","yu"};
//受限集合,不能添加和删除
List<String> list3=Arrays.asList(nameStrings);
System.out.println(list3);
//注:基本类型转成集合时需要修改为包装类
}
}
boolean add(E e) {
return m.put(e, PRESENT)==null;
}
6.Collections工具类
内部集成了一些常用的静态方法。可以回想Arrays类的用法
public static void reverse(List<?> list)//反转集合中元素的顺序
public static void shuffle(List<?> list)//随机重置集合元素的顺序
public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)
测试
/**
* 演示Collections工具类的使用
*
*/
public class MainTest {
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
list.add(20);
list.add(10);
list.add(30);
list.add(90);
list.add(70);
//sort排序
System.out.println(list.toString());
Collections.sort(list);
System.out.println(list.toString());
System.out.println("---------");
//binarySearch二分查找
int i=Collections.binarySearch(list, 10);
System.out.println(i);
//copy复制
List<Integer> list2=new ArrayList<Integer>();
for(int i1=0;i1<5;++i1) {
list2.add(0);
}
//该方法要求目标元素容量大于等于源目标
Collections.copy(list2, list);
System.out.println(list2.toString());
//reserve反转
Collections.reverse(list2);
System.out.println(list2.toString());
//shuffle 打乱
Collections.shuffle(list2);
System.out.println(list2.toString());
//补充:list转成数组
Integer[] arr=list.toArray(new Integer[0]);
System.out.println(arr.length);
//补充:数组转成集合
String[] nameStrings= {"tang","he","yu"};
//受限集合,不能添加和删除
List<String> list3=Arrays.asList(nameStrings);
System.out.println(list3);
//注:基本类型转成集合时需要修改为包装类
}
}