Java集合框架(含源码解读)-----B站千锋、韩顺平学习笔记

如何选取集合实现类

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
1)先判断存储的类型(一组对象[单列]或一组键值对[双列)
2)一组对象[单列]:Collection接口
允许重复:List
增删多:LinkedList[底层维护了一个双向链表]
改查多: ArrayList[底层维护Object类型的可变数组]
不允许重复:Set
无序:HashSet [底层是HashMap,维护了一个哈希表即(数组+链表+红黑树)]
排序:TreeSet[老韩举例说明]
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
3)一组键值对[双列]: Map
键无序: HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap[老韩举例说明]】
键插入和取出顺序一致:LinkedHashMap
读取文件Properties

集合线程安全问题总结

在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的。在jdk1.2之后,就出现许许多多非线程安全的类。 下面是这些线程安全的同步的类:

vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。

statck:堆栈类,先进后出

hashtable:就比hashmap多了个线程安全

enumeration:枚举,相当于迭代器

除了这些之外,其他的都是非线程安全的类和接口。

线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低。

泛型

Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。
常见形式有泛型类、泛型接口、泛型方法。
语法:
<T,…> T称为类型占位符,表示一种引用类型。
好处:
提高代码的重用性。
防止类型转换异常,提高代码的安全性。

泛型类

/**
 * 泛型类
 * 语法:类名<T>
 * T是类型占位符,表示一种引用类型,编写多个使用逗号隔开
 * 
 */
public class myGeneric<T>{
	//1.创建泛型变量
	//不能使用new来创建,因为泛型是不确定的类型,也可能拥有私密的构造方法。
	T t;
	//2.泛型作为方法的参数
	public void show(T t) {
		System.out.println(t);
	}
	//泛型作为方法的返回值
	public T getT() {
		return t;
	}
}
/**
 * 注意:
 * 1.泛型只能使用引用类型
 * 2.不同泛型类型的对象不能相互赋值
 */
public class testGeneric {
	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();
	}
}

泛型接口

/**
 * 泛型接口
 * 语法:接口名<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; 
	}
}
//测试
MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
myInterfaceImpl.server("xxx");
//xxx
/**
 * 实现接口时不确定泛型类
 */
public class MyInterfaceImpl2<T> implements MyInterface<T>{
	@Override
	public T server(T t) {
		System.out.println(t);
		return t;
	}
}
//测试
MyInterfaceImpl2<Integer> myInterfaceImpl2=new MyInterfaceImpl2<Integer>();
myInterfaceImpl2.server(2000);
//2000

泛型方法

在这里插入图片描述

/**
 * 泛型方法
 * 语法:<T> 返回类型
 */
public class MyGenericMethod {
	public <T> void show(T t) {
		System.out.println("泛型方法"+t);
	}
}
//测试
MyGenericMethod myGenericMethod=new MyGenericMethod();
myGenericMethod.show("tang");
myGenericMethod.show(200);
myGenericMethod.show(3.14);

在这里插入图片描述

泛型集合

概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。
特点:
编译时即可检查,而非运行时抛出异常。
访问时,不必类型转换(拆箱)。
不同泛型指尖引用不能相互赋值,泛型不存在多态。
之前我们在创建LinkedList类型对象的时候并没有使用泛型,但是进到它的源码中会发现:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{//略}

它是一个泛型类,而我之前使用的时候并没有传递,说明java语法是允许的,这个时候传递的类型是Object类,虽然它是所有类的父类,可以存储任意的类型,但是在遍历、获取元素时需要原来的类型就要进行强制转换。这个时候就会出现一些问题,假如往链表里存储了许多不同类型的数据,在强转的时候就要判断每一个原来的类型,这样就很容易出现错误。

泛型的继承和通配符

在这里插入图片描述
泛型不具有传递性,但是在给泛型指定类型后,可以传入该类型或者子类类型
在这里插入图片描述

泛型的使用注意点

在这里插入图片描述

代码举例说明

package com.hspedu.customgeneric;


import java.util.Arrays;

/**
 * @author 韩顺平
 * @version 1.0
 */
@SuppressWarnings({"all"})
public class CustomGeneric_ {
    public static void main(String[] args) {
        //给泛型指定类型
        //T=Double, R=String, M=Integer
        Tiger<Double,String,Integer> g = new Tiger<>("john");
        g.setT(10.9); //OK
        //g.setT("yy"); //错误,类型不对
        System.out.println(g);
        Tiger g2 = new Tiger("john~~");//OK T=Object R=Object M=Object
        g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是Object子类
        System.out.println("g2=" + g2);

    }
}

//老韩解读
//1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
//2, T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型标识符可以有多个.
//4. 普通成员可以使用泛型 (属性、方法)
//5. 使用泛型的数组,不能初始化
//6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
    String name;
    R r; //属性使用到泛型
    M m;
    T t;
    //因为数组在new 不能确定T的类型,不知道该开辟多大的空间
    //T[] ts=new T[8];不允许使用
    T[] ts;

    public Tiger(String name) {
        this.name = name;
    }

    public Tiger( T t,R r, M m) {//构造器使用泛型

        this.r = r;
        this.m = m;
        this.t = t;
    }

    public Tiger(String name, T t,R r, M m ) {//构造器使用泛型
        this.name = name;
        this.r = r;
        this.m = m;
        this.t = t;
    }

    //因为静态是和类相关的,在类加载时,对象还没有创建
    //所以,如果静态方法和静态属性使用了泛型,JVM(底层)就无法完成初始化
//    static R r2;
//    public static void m1(M m) {
//
//    }

    //方法使用泛型

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public R getR() {
        return r;
    }

    public void setR(R r) {//方法使用到泛型
        this.r = r;
    }

    public M getM() {//返回类型可以使用泛型.
        return m;
    }

    public void setM(M m) {
        this.m = m;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    @Override
    public String toString() {
        return "Tiger{" +
                "name='" + name + '\'' +
                ", r=" + r +
                ", m=" + m +
                ", t=" + t +
                ", ts=" + Arrays.toString(ts) +
                '}';
    }
}

在这里插入图片描述

/**
 *  泛型接口使用的说明
 *  1. 接口中,静态成员也不能使用泛型
 *  2. 泛型接口的类型, 在继承接口或者实现接口时确定
 *  3. 没有指定类型,默认为Object
 */

//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {

}
//当我们去实现IA接口时,因为IA在继承IUsb 接口时,指定了U 为String R为Double
//,在实现IUsb接口的方法时,使用String替换U, 是Double替换R
class AA implements IA {

    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {

    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }
}

//实现接口时,直接指定泛型接口的类型
//给U 指定Integer 给 R 指定了 Float
//所以,当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {

    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {

    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {

    }
}
//没有指定类型,默认为Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb { //等价 class CC implements IUsb<Object,Object> {
    @Override
    public Object get(Object o) {
        return null;
    }
    @Override
    public void hi(Object o) {
    }
    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {

    }

}

interface IUsb<U, R> {

    int n = 10;
    //U name; 不能这样使用

    //普通方法中,可以使用接口泛型
    R get(U u);

    void hi(R r);

    void run(R r1, R r2, U u1, U u2);

    //在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }
}

集合概述

概念:对象的容器,定义了对多个对象进项操作的的常用方法。可实现数组的功能。

和数组的区别:

数组长度固定,集合长度不固定。

数组可以存储基本类型和引用类型,集合只能存储引用类型。

位置: java.util.*;

Collection体系集合

在这里插入图片描述

Collection父接口

特点:代表一组任意类型的对象,无序、无下标、不能重复。

方法:

boolean add(Object obj) //添加一个对象。
boolean addAll(Collection c) //讲一个集合中的所有对象添加到此集合中。
void clear() //清空此集合中的所有对象。
boolean contains(Object o) //检查此集合中是否包含o对象。
boolean equals(Object o) //比较此集合是否与指定对象相等。
boolean isEmpty() //判断此集合是否为空。
boolean remove(Object o) //在此集合中移除o对象。
int size() //返回此集合中的元素个数。
Object[] toArray() //将此集合转换成数组。

/**
 * Collection接口的使用(一)
 * 1.添加元素
 * 2.删除元素
 * 3.遍历元素
 * 4.判断
 */
public class Test{
    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("榴莲");
        System.out.println("删除之后:"+collection.size());
//      * 3.遍历元素
        //3.1 使用增强for
        for(Object object : collection){
            System.out.println(object);
        }
        //3.2 使用迭代器(迭代器专门用来遍历集合的一种方式)
        //hasnext();判断是否有下一个元素
        //next();获取下一个元素
        //remove();删除当前元素
        Iterator iterator=collection.iterator();
        while(iterator.hasNext()){
            String object=(String)iterator.next();
            System.out.println(object);
            //删除操作
            //collection.remove(s);引发错误:并发修改异常
            //iterator.remove();应使用迭代器的方法
//      * 4.判断
            System.out.println(collection.contains("西瓜"));//true
            System.out.println(collection.isEmpty());//false
        }
    }
}
/**
 * Collection接口的使用(二)
 * 1.添加元素
 * 2.删除元素
 * 3.遍历元素
 * 4.判断
 */
public class Demo2 {
	public static void main(String[] args) {
		Collection collection=new ArrayList();
		Student s1=new Student("张三",18);
		Student s2=new Student("李四", 20);
		Student s3=new Student("王五", 19);
		//1.添加数据
		collection.add(s1);
		collection.add(s2);
		collection.add(s3);
		//collection.add(s3);可重复添加相同对象
		System.out.println("元素个数:"+collection.size());
		System.out.println(collection.toString());
		//2.删除数据
		collection.remove(s1);
		System.out.println("删除之后:"+collection.size());
		//3.遍历数据
		//3.1 增强for
		for(Object object:collection) {
			Student student=(Student) object;
			System.out.println(student.toString());
		}
		//3.2迭代器
		//迭代过程中不能使用collection的删除方法
		Iterator iterator=collection.iterator();
		while (iterator.hasNext()) {
			Student student=(Student) iterator.next();
			System.out.println(student.toString());
		}
		//4.判断和上一块代码类似。
	}
}
/**
 * 学生类
 */
public class Student {
	private String name;
	private int age;
	public Student(String name, int age) {
		super();
		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 "Student [name=" + name + ", age=" + age +"]";
	}
}

Collection子接口

List集合

特点:有序、有下标、元素可以重复。

方法:

void add(int index,Object o) //在index位置插入对象o。
void remove(int index)
boolean addAll(index,Collection c) //将一个集合中的元素添加到此集合中的index位置。
Object get(int index) //返回集合中指定位置的元素。
List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之间的集合元素。
int indexOf(0bject obj):返回obj在集合中首次出现的位置
int lastIndexOf(0bject obj):返回obj在当前集合中末次出现的位置

/**
 * List子接口的使用(一)
 * 特点:1.有序有下标 2.可以重复
 * 1.添加元素
 * 2.删除元素
 * 3.遍历元素
 * 4.判断
 * 5.获取位置
 */
public class Demo3 {
	public static void main(String[] args) {
		List list=new ArrayList<>();
		//1.添加元素
		list.add("tang");
		list.add("he");
		list.add(0,"yu");//插入操作
		System.out.println("元素个数:"+list.size());
		System.out.println(list.toString());
		//2.删除元素
		list.remove(0);
		//list.remove("yu");结果同上
		System.out.println("删除之后:"+list.size());
		System.out.println(list.toString());
		//3.遍历元素
		//3.1 使用for遍历
		for(int i=0;i<list.size();++i) {
			System.out.println(list.get(i));
		}
		//3.2 使用增强for
		for(Object object:list) {
			System.out.println(object);
		}
		//3.3 使用迭代器
		Iterator iterator=list.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		//3.4使用列表迭代器,listIterator可以双向遍历,添加、删除及修改元素。
		ListIterator listIterator=list.listIterator();
		//从前往后
		while (listIterator.hasNext()) {
			System.out.println(listIterator.next());		
		}
		//从后往前(此时“遍历指针”已经指向末尾)
		while(listIterator.hasPrevious()) {
			System.out.println(listIterator.previous());
		}
		//4.判断
		System.out.println(list.isEmpty());
		System.out.println(list.contains("tang"));
		//5.获取位置
		System.out.println(list.indexOf("tang"));
	}
}
/**
 * List子接口的使用(二)
 * 1.添加元素
 * 2.删除元素
 * 3.遍历元素
 * 4.判断
 * 5.获取位置
 */
public class Demo4 {
	public static void main(String[] args) {
		List list=new ArrayList();
		//1.添加数字数据(自动装箱)
		list.add(20);
		list.add(30);
		list.add(40);
		list.add(50);
		System.out.println("元素个数:"+list.size());
		System.out.println(list.toString());
		//2.删除元素
		list.remove(0);
		//list.remove(20);很明显数组越界错误,改成如下
		//list.remove(Object(20));
		//list.remove(new Integer(20));
		System.out.println("元素个数:"+list.size());
		System.out.println(list.toString());
		//3-5不再演示,与之前类似
		//6.补充方法subList,返回子集合,含头不含尾
		List list2=list.subList(1, 3);
		System.out.println(list2.toString());	
	}
}
List实现类
ArrayList【重点】(线程不安全)

数组结构实现,查询块、增删慢;
JDK1.2版本,运行效率快、线程不安全。

/**
 * ArrayList的使用
 * 存储结构:数组;
 * 特点:查找遍历速度快,增删慢。
 * 1.添加元素
 * 2.删除元素
 * 3.遍历元素
 * 4.判断
 * 5.查找
 */
public class Demo5 {
	public static void main(String[] args) {
		ArrayList arrayList=new ArrayList<>();
		//1.添加元素
		Student s1=new Student("唐", 21);
		Student s2=new Student("何", 22);
		Student s3=new Student("余", 21);
		arrayList.add(s1);
		arrayList.add(s2);
		arrayList.add(s3);
		System.out.println("元素个数:"+arrayList.size());
		System.out.println(arrayList.toString());
		//2.删除元素
		arrayList.remove(s1);
		//arrayList.remove(new Student("唐", 21));
		//注:这样可以删除吗(不可以)?显然这是两个不同的对象。
		//假如两个对象属性相同便认为其是同一对象,那么如何修改代码?
		//3.遍历元素
		//3.1使用迭代器
		Iterator iterator=arrayList.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		//3.2使用列表迭代器
		ListIterator listIterator=arrayList.listIterator();
		//从前往后遍历
		while(listIterator.hasNext()) {
			System.out.println(listIterator.next());
		}
		//从后往前遍历
		while(listIterator.hasPrevious()) {
			System.out.println(listIterator.previous());
		}
		//4.判断
		System.out.println(arrayList.isEmpty());
		//System.out.println(arrayList.contains(new Student("何", 22)));
		//注:与上文相同的问题。
		//5.查找
		System.out.println(arrayList.indexOf(s1));
	}
}

注:Object里的equals()用地址和当前对象比较,如果想实现代码中的问题,可以在学生类中重写equals方法:

@Override
public boolean equals(Object obj) {
	//1.是否为同一对象
	if (this==obj) {
		return true;
	}
	//2.判断是否为空
	if (obj==null) {
		return false;
	}
	//3.判断是否是Student类型
	if (obj instanceof Student) {
		Student student=(Student) obj;
		//4.比较属性
		if(this.name.equals(student.getName())&&this.age==student.age) {
			return true;
		}
	}
	//不满足,返回false
	return false;
}
ArrayList源码分析

默认容量大小:private static final int DEFAULT_CAPACITY = 10;

存放元素的数组:transient Object[] elementData;//transient 表示短暂的,该属性不会被序列化

实际元素个数:private int size;

当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,
第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

创建对象时调用的无参构造函数:

//这是一个空的数组
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);  //首先去判断需不需要进行扩容
    elementData[size++] = e;
    return true;
}

假设你new了一个数组,当前容量为0,size当然也为0。这时调用add方法进入到ensureCapacityInternal(size + 1);该方法源码如下:
此时minCapacity=size+1=1,该方法确定minCapacity
(1)第一次扩容minCapacity=10

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity= Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);//minCapacity=10
}

modCount是指当前集合被修改的次数,防止进程冲突
因为elementData数组长度为0,所以(minCapacity - elementData.length>0)条件成立
说明数组的长度不够真的需要扩容,需要调用grow方法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

这个方法先声明了一个oldCapacity变量将数组长度赋给它,其值为0;
又声明了一个newCapacity变量其值为oldCapacity+oldCapacity/2,(1.5倍扩容原理)但这里第一次使用 0*1.5还是=0
第一个if条件满足,newCapacity的值为10
第二个if条件不成立,也可以不用注意
最后一句话就是为elementData数组赋予了新的长度,Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象保留,该拷贝不会影响原来的数组。copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。

这时候再回到add的方法中,执行elementData[size++] = e;到这里为止关于ArrayList就讲解得差不多了,当数组长度为10的时候你们可以试着过一下源码,查一下每次的增量是多少(答案是每次扩容为原来的1.5倍)。

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);
}

因为MAX_ARRAY_SIZE的定义如下:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

这个值太大了以至于第二个if条件没有了解的必要。

vector(线程安全)

底层还是一个对象数组
在这里插入图片描述

基本操作
/**
 * Vector的演示使用
 * 
 *1.添加数据
 *2.删除数据
 *3.遍历
 *4.判断
 */
public class Demo1 {
	public static void main(String[] args) {
		Vector vector=new Vector<>();
		//1.添加数据
		vector.add("tang");
		vector.add("he");
		vector.add("yu");
		System.out.println("元素个数:"+vector.size());
		//2.删除数据
		/*
		 * vector.remove(0); vector.remove("tang");
		 */
		//3.遍历
		//使用枚举器
		Enumeration enumeration=vector.elements();
		while (enumeration.hasMoreElements()) {
			String s = (String) enumeration.nextElement();
			System.out.println(s);
		}
		//4.判断
		System.out.println(vector.isEmpty());
		System.out.println(vector.contains("he"));
		//5. Vector其他方法
		//firstElement()  lastElement()  ElementAt();
	}
}
扩容机制(vector和ArrayList比较)

在这里插入图片描述

public class Vector_ {
    public static void main(String[] args) {
        //无参构造器
        //有参数的构造
        Vector vector = new Vector(8);
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println("vector=" + vector);
        //老韩解读源码
        //1. new Vector() 底层
        /*
            public Vector() {
                this(10);
            }
         补充:如果是  Vector vector = new Vector(8);
            走的方法:
            public Vector(int initialCapacity) {
                this(initialCapacity, 0);
            }
         2. vector.add(i)
         2.1  //下面这个方法就添加数据到vector集合
            public synchronized boolean add(E e) {
                modCount++;
                ensureCapacityHelper(elementCount + 1);
                elementData[elementCount++] = e;
                return true;
            }
          2.2  //确定是否需要扩容 条件 : minCapacity - elementData.length>0
            private void ensureCapacityHelper(int minCapacity) {
                // overflow-conscious code
                if (minCapacity - elementData.length > 0)
                    grow(minCapacity);
            }
          2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法,总结就是扩容2倍
              //newCapacity = oldCapacity + ((capacityIncrement > 0) ?
              //                             capacityIncrement : oldCapacity);
            private void grow(int minCapacity) {
                // overflow-conscious code
                int oldCapacity = elementData.length;
                int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                                 capacityIncrement : oldCapacity);
                if (newCapacity - minCapacity < 0)
                    newCapacity = minCapacity;
                if (newCapacity - MAX_ARRAY_SIZE > 0)
                    newCapacity = hugeCapacity(minCapacity);
                elementData = Arrays.copyOf(elementData, newCapacity);
            }
         */

    }
}
LinkedList(线程不安全)

链表结构实现,增删快,查询慢。
在这里插入图片描述

基本操作
/**
 * LinkedList的用法
 * 存储结构:双向链表
 * 1.添加元素
 * 2.删除元素
 * 3.遍历
 * 4.判断
 * 5.修改某个结点对象
 * 6.得到某个结点对象
 */
public class Demo2 {
	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));
		 //5.修改某个结点对象
        linkedList.set(1999);
        //6.得到某个结点对象
        //get(1)是得到双向链表的第二个对象
        0bject o = linkedList.get(1);
        System.out.println(o) ; //999
	}
}
源码解读

LinkedList首先有三个属性:

链表大小:transient int size = 0;
(指向)第一个结点/头结点:transient Node first;
(指向)最后一个结点/尾结点:transient Node 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;
    }
}

首先item存放的是实际数据;next指向下一个结点而prev指向上一个结点。

Node带参构造方法的三个参数分别是前一个结点、存储的数据、后一个结点,调用这个构造方法时将它们赋值给当前对象。

LinkedList是如何添加元素的呢?先看看add方法:

public boolean add(E e) {
    linkLast(e);
    return true;
}

进入到LinkLast方法:

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++;
}

假设刚开始new了一个LinkedList对象,first和last属性都为空,调用add进入到linkLast方法。

首先创建一个Node变量 l 将last(此时为空)赋给它,
然后new一个newNode变量存储数据,并且它的前驱指向l,后继指向null;再把last指向newNode。如下图所示:
在这里插入图片描述
如果满足if条件,说明这是添加的第一个结点,将first指向newNode:
在这里插入图片描述
至此,LinkedList对象的第一个数据添加完毕。假设需要再添加一个数据,我们可以再来走一遍,过程同上不再赘述,图示如下:
在这里插入图片描述
在这里插入图片描述

ArrayList和LinkedList的区别

ArrayList和LinkedList区别
ArrayList:必须开辟连续空间,查询快,增删慢。
LinkedList:无需开辟连续空间,查询慢,增删快。
在这里插入图片描述
在这里插入图片描述

set接口概述

在这里插入图片描述

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.遍历【重点】
		//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());
	}
}
Set实现类
HashSet【重点】()

先上结论总结
在这里插入图片描述
在这里插入图片描述
HashSet无法保证插入顺序与取出顺序一致,LinkedHashSet可以
HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)
基于HashCode计算元素存放位置。
当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。
经典面试题
在这里插入图片描述

/**
 * 人类
 */
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));
        hashSet.add(new Persom("yu", 21));//任然添加成功,除非重写equals()方法
        //举例String类
        hashSet.add(new String ("zlh"));
        hashSet.add(new String ("zlh"));//添加失败,String类重写equals()方法
		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)));
	}
}

注:hashSet存储过程:
根据hashCode计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
执行equals方法,如果方法返回true,则认为是重复,拒绝存储,否则形成链表。
存储过程实际上就是重复依据,要实现“注”里的问题,可以重写hashCode和equals代码:

@Override
public int hashCode() {
    final int prime = 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;
}

hashCode方法里为什么要使用31这个数字大概有两个原因:
31是一个质数,这样的数字在计算时可以尽量减少散列冲突。
可以提高执行效率,因为31*i=(i<<5)-i,31乘以一个数可以转换成移位操作,这样能快一点;但是也有网上一些人对这两点提出质疑。
课堂练习
在这里插入图片描述
需要同时重写hashcode ()和equals()方法

public class Test1 {
    public static void main(String[] args) {
        HashSet<Employee> employeeHashSet = new HashSet<Employee>();
        employeeHashSet.add(new Employee("zlh", 19));
        employeeHashSet.add(new Employee("xyz", 18));
        employeeHashSet.add(new Employee("xyz", 18));
        System.out.println("hashSet="+employeeHashSet.size());//没重写前返回3,重写完返回2


    }
}

 class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

源码分析
1、首先HashSet
public HashSet(){
map=new HashMap<>();
}
2、执行add()方法
public boolean add(E e) {//e=“Java”
return map.put(e,PRESENT)==null;
}
3、执行put()方法
public V put(K key,v value) {//key=“Java”,value=PRESENT,PRESENT我们只需要知道它是一个静态的对象即可
return putVal(hash(key), key, value,false,true);
}

接下去和HashMap源码一致,在HashMap源码处会进行详细讲解(请下移至HashMap源码分析,一定要看啊!!!)
putVal()方法判重代码解读

else {
                    //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
                    Node<K,V> e; K k; //
                    //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
                    //并且满足 下面两个条件之一:
                    //(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象
                    //(2)  p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同
                    //就不能加入
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    //再判断 p 是不是一颗红黑树,
                    //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
                    else if (p instanceof TreeNode)
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                    else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较
                          //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
                          //    注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                          //    , 到了就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                          //    注意,在转成红黑树时,要进行判断, 判断条件
                          //    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                          //            resize();
                          //    如果上面条件成立,先table扩容.
                          //    只有上面条件不成立时,才进行转成红黑树
                          //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break

                        for (int binCount = 0; ; ++binCount) {
                            if ((e = p.next) == null) {
                                p.next = newNode(hash, key, value, null);
                                if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            p = e;
                        }
                    }
                    if (e != null) { // existing mapping for key
                        V oldValue = e.value;
                        if (!onlyIfAbsent || oldValue == null)
                            e.value = value;
                        afterNodeAccess(e);
                        return oldValue;//return oldValue不返回null,表示失败
                    }
                }
LinkedHashSet

使用LinkedHashSet可以使得插入顺序和取出顺序一致,而HashSet无法保证
每一个节点都有before和after属性
在这里插入图片描述
在这里插入图片描述

TreeSet

基于排序顺序实现不重复。
实现了SortedSet接口,对集合元素自动排序。
元素对象的类型必须实现Comparable接口,指定排序规则。
通过CompareTo方法确定是否为重复元素,CompareTo方法返回0认为是相同元素,加不进去。

/**
 * 使用TreeSet保存数据
 * 存储结构:红黑树
 * 要求:元素类必须实现Comparable接口,compareTo方法返回0,认为是重复元素 
 */
public class Demo4 {
	public static void main(String[] args) {
		TreeSet<Person> persons=new TreeSet<Person>();
		Person p1=new Person("tang",21);
		Person p2=new Person("he", 22);
		Person p3=new Person("yu", 21);
		//1.添加元素
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		//注:直接添加会报类型转换错误,需要实现Comparable接口
		System.out.println(persons.toString());
		//2.删除元素
		persons.remove(p1);
		persons.remove(new Person("he", 22));
		System.out.println(persons.toString());
		//3.遍历(略)
		//4.判断
		System.out.println(persons.contains(new Person("yu", 21)));
	}
}

查看Comparable接口的源码,发现只有一个compareTo抽象方法,在人类中实现它:

public class Person implements Comparable<Person>{
    @Override
	//1.先按姓名比
	//2.再按年龄比
	public int compareTo(Person o) {
		int n1=this.getName().compareTo(o.getName());
		int n2=this.age-o.getAge();
		return n1==0?n2:n1;
	}
}

除了实现Comparable接口里的比较方法,TreeSet也提供了一个带比较器Comparator的构造方法,使用匿名内部类来实现它:

/**
 * TreeSet的使用
 * Comparator:实现定制比较(比较器)
 */
public class Demo5 {
	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.getAge()-o2.getAge();
				int n2=o1.getName().compareTo(o2.getName());
				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);
		System.out.println(persons.toString());
	}
}

接下来我们来做一个小案例:

/**
 * 要求:使用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());
        //输出[tangrui, wangzixu, yuguoming, hechenyang, helloworld]
	}
}

Map体系集合

Map接口的特点:

用于存储任意键值对(Key-Value)。
键:无序、无下标、不允许重复(唯一)。
值:无序、无下标、允许重复。

在这里插入图片描述
在这里插入图片描述

Map集合概述

特点:存储一对数据(Key-Value),无序、无下标,键不可重复。

方法:
V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值。
Object get(Object key)//根据键获取相应的值。
remove(Object object,Value value) 或者remove(Object object)
containsKey//查找键是否存在
Set//返回所有的key
Collection values()//返回包含所有值的Collection集合。
Set<Map.Entry<K,V>>//键值匹配的set集合
在这里插入图片描述

1、一对key-value是放在HashMap$Node 里的
在这里插入图片描述

2、k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry,
而一个Entry对象就有k,v EntrySet<Entry<K,V>>
3、 entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap N o d e 这 是 因 为 s t a t i c c l a s s N o d e < K , V > i m p l e m e n t s M a p . E n t r y < K , V > 实 现 了 接 口 4. 当 把 H a s h M a p Node 这是因为 static class Node<K,V> implements Map.Entry<K,V>实现了接口 4. 当把 HashMap NodestaticclassNode<K,V>implementsMap.Entry<K,V>4.HashMapNode 对象 存放到 entrySet 为了方便我们的遍历, Map.Entry 提供了重要方法
K getKey(); V getValue();

/**
 * Map接口的使用
 * 特点:1.存储键值对 2.键不能重复,值可以重复 3.无序
 */
public class Demo1 {
	public static void main(String[] args) {
		Map<String,Integer> map=new HashMap<String, Integer>();
		//1.添加元素
		map.put("tang", 21);
		map.put("tang",22);//用22来更新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());
		}
	}
}

Map实现类

HashMap
HashMap【重点】

JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value。

  1. Map接口的常用实现类:HashMap、Hashtable和Properties.
  2. HashMap是 Map 接口使用频率最高的实现类。
  3. HashMap 是以 key-val对的方式来存储数据(HashMap$Node类型)[案例Entry ]
    4)== key不能重复,但是值可以重复,允许使用null键和null值。==
    5)如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换,val会替换)
    6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的. (jdk8的hashMap底层数组+链表+红黑树)
  4. HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
Map.Entry使用---->用于遍历HashMap方便

Map类提供了一个称为entrySet()的方法,这个方法返回一个Map.Entry实例化后的对象集
使用Map.Entry类,你可以得到在同一时间得到所有的信息
Map是java中的接口,Map.Entry又是Map的一个内部接口。
在这里插入图片描述
在这里插入图片描述

Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>1。它表示Map中的一个实体(一个key-value对)。

Hash Map的6种遍历方法

加了泛型以后使用entrySet遍历的方法:

//(1)
  System.out.println("通过Map.entrySet使用iterator遍历key和value:");  
  Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();  
  while (it.hasNext()) {  
   Map.Entry<String, String> entry = it.next();  
   System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());  
  }  
  //(2)
  System.out.println("通过Map.entrySet遍历key和value");  
  for (Map.Entry<String, String> entry : map.entrySet()) {  
   System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());  
  }  

没加泛型的遍历方法有助于理解

public class MapFor {
    public static void main(String[] args) {

        Map map = new HashMap();
        map.put("邓超", "孙俪");
        map.put("王宝强", "马蓉");
        map.put("宋喆", "马蓉");
        map.put("刘令博", null);
        map.put(null, "刘亦菲");
        map.put("鹿晗", "关晓彤");

        //第一组: 先取出 所有的Key , 通过Key 取出对应的Value
        Set keyset = map.keySet();
        //(1) 增强for
        System.out.println("-----第一种方式-------");
        for (Object key : keyset) {
            System.out.println(key + "-" + map.get(key));
        }
        //(2) 迭代器
        System.out.println("----第二种方式--------");
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key + "-" + map.get(key));
        }

        //第二组: 把所有的values取出
        Collection values = map.values();
        //这里可以使用所有的Collections使用的遍历方法
        //(1) 增强for
        System.out.println("---取出所有的value 增强for----");
        for (Object value : values) {
            System.out.println(value);
        }
        //(2) 迭代器
        System.out.println("---取出所有的value 迭代器----");
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            Object value =  iterator2.next();
            System.out.println(value);

        }

        //第三组: 通过EntrySet 来获取 k-v
        Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
        //(1) 增强for
        System.out.println("----使用EntrySet 的 for增强(第3种)----");
        for (Object entry : entrySet) {
            //entry是Object类,向下转型为Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        //(2) 迭代器
        System.out.println("----使用EntrySet 的 迭代器(第4种)----");
        Iterator iterator3 = entrySet.iterator();
        while (iterator3.hasNext()) {
            Object entry =  iterator3.next();//没加泛型所以iterator2.next()的值是Object类型所以需要转型,加了泛型就不要
            //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
            //向下转型 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}
HashMap怎么实现添加相同的key,则会覆盖原来的key-val

在这里插入图片描述

HashMap源码分析(解决HashSet源码问题)

在这里插入图片描述
putVal()方法定义的辅助变量
1、默认初始化容量:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
2、数组最大容量:
static final int MAXIMUM_CAPACITY = 1 << 30;
3、默认加载因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
4、链表调整为红黑树的链表长度阈值(JDK1.8):
static final int TREEIFY_THRESHOLD = 8;
5、红黑树调整为链表的链表长度阈值(JDK1.8):
static final int UNTREEIFY_THRESHOLD = 6;
6、链表调整为红黑树的数组最小阈值(JDK1.8):
static final int MIN_TREEIFY_CAPACITY = 64;
7、HashMap存储的数组:
transient Node<K,V>[] table;
8、HashMap存储的元素个数:
transient int size;

当我们往对象里添加元素时调用put方法:

public V put(K key, V value) {//key="Java",value=PRESENT,PRESENT我们只需要知道它是一个静态的对象即可
    return putVal(hash(key), key, value, false, true);//hash(key)是用于产生哈希值的方法,算法h = key.hashCode()^(h>>>16)
    //hash值与hashcode不一致,算法了解即可
}
}

put方法把key和value传给了putVal,同时还传入了一个hash(Key)所返回的值,这是一个产生哈希值的方法,再进入到putVal方法(部分源码):(重要工资能拿多少就靠这了
(tab==table),table是HashMap的属性,类型是Node[ ]

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
                Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                else {
                    Node<K,V> e; K k; 
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    else if (p instanceof TreeNode)
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                    else {
                        for (int binCount = 0; ; ++binCount) {
                            if ((e = p.next) == null) {
                                p.next = newNode(hash, key, value, null);
                                if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            p = e;
                        }
                    }
                    if (e != null) { // existing mapping for key
                        V oldValue = e.value;
                        if (!onlyIfAbsent || oldValue == null)
                            e.value = value;
                        afterNodeAccess(e);
                        return oldValue;
                    }
                }
                ++modCount;
                //size 就是我们每加入一个结点Node(k,v,h,next), size++
                if (++size > threshold)
                    resize();//扩容
                afterNodeInsertion(evict);
                return null;
            }

这里面创建了一个tab数组和一个Node变量p,第一个if( if ((tab = table) = = null || (n = tab.length) == 0))实际是判断table是否为空,
而我们现在只关注刚创建HashMap对象时的状态,此时tab和table都为空,满足条件,
执行内部代码(n=(tab=resize()).length),这条代码其实就是把resize()所返回的结果赋给tab,n就是tab的长度,
resize顾名思义就是重新调整大小。查看resize()源码(部分):

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    if (oldCap > 0);
    else if (oldThr > 0);
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;//1<<4 ,aka 16
        //DEFAULT_LOAD_FACTOR默认加载因子0.75
        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;
}

该方法首先把table及其长度赋值给oldTab和oldCap;threshold是阈值的意思,此时为0,
所以前两个if先不管,最后else里newCap的值为默认初始化容量16;
往下创建了一个newCap大小的数组并将其赋给了table,刚创建的HashMap对象就在这里获得了初始容量。
putval()方法的第二个if 判断
if ((p = tab[i = (n - 1) & hash]) = = null)
tab[i] = newNode(hash, key, value, null);
然后我们再回到putVal方法,第二个if
(1)就是根据key得到hash值计算这个key应该放到table表的哪个索引位置,并且把这个位置上的对象赋给p
(2)判断p是否为null
(2.1)如果p 为null,表示还没有存放元素,就创建一个Node (key=“java” , value=PRESENT)
(2.2)就放在该位置tab[i] = newNode(hash,key,value,null)
至此HashMap对象就完成了第一个元素的添加。
当添加的元素超过16*0.75=12时(0.75是默认加载因子DE4FAULT_LOAD_FACTOR),就会进行扩容:
假如table对应的索引位置,已经是一个链表且长度超过8同样需要扩容

在这里插入图片描述

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)
    }
}

核心部分是else if里的移位操作,也就是说每次扩容都是原来大小的两倍。
putVal()方法判重、扩容代码解读
扩容机制的解读
在这里插入图片描述

else {
                    //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
                    Node<K,V> e; K k; //
                    //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
                    //并且满足 下面两个条件之一:
                    //(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象(地址相同)
                    //(2)  p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同
                    //就不能加入
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    //再判断 p 是不是一颗红黑树,
                    //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
                    else if (p instanceof TreeNode)
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                    else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较
                          //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
                          //    注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                          //    , 到了就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                          //    注意,在转成红黑树时,要进行判断, 判断条件
                          //    链表长度是否达到8个节点但小于MIN_TREEIFY_CAPACITY=64?
                          //    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                          //            resize();
                          //    如果上面条件成立,先table扩容.*2
                          //    只有上面条件不成立时,才进行转成红黑树
                          //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break

                        for (int binCount = 0; ; ++binCount) {
                            if ((e = p.next) == null) {
                                p.next = newNode(hash, key, value, null);
                                if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            p = e;
                        }
                    }
                    if (e != null) { // existing mapping for key
                        V oldValue = e.value;
                        if (!onlyIfAbsent || oldValue == null)
                            e.value = value;
                        afterNodeAccess(e);
                        return oldValue;//return oldValue不返回null,表示失败
                    }
                }

在这里插入图片描述
注:额外说明的一点是在JDK1.8以前链表是头插入,JDK1.8以后链表是尾插入。

TreeMap

实现了SortedMap接口(是Map的子接口),可以对key自动排序。
在这里插入图片描述

/**
 * TreeMap的使用
 * 存储结构:红黑树
 */
public class Demo3 {
	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);
		Student s4=new Student("tang",23);//这里key值不变,但是value值更新了,需要注意
		//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 (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());		
	}
}

在学生类中实现Comparable接口:

public class Student implements Comparable<Student>{
    @Override
    public int compareTo(Student o) {
        int n1=this.id-o.id;
        return n1;
}

除此之外还可以使用比较器来定制比较:

TreeMap<Student, Integer> treeMap2=new TreeMap<Student, Integer>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // 略
        return 0;
    }			
});
HashTable

被弃用的方法了解下即可
在这里插入图片描述
//简单说明一下Hashtable的底层
//1。底层有数组 Hashtable$Entry[]初始化大小为11
//2.临界值threshold 8 = 11 * 0.75
//3。扩容:按照自己的扩容机制来进行即可.
//4.执行方法 addEntry(hashT key,value,index);添加K-V封装到Entry
//5。当 if (count >= threshold)满足时,就进行扩容
//5.按照int newCapacity = (oldCapacity <<1) +1;(11----->23)的大小扩容。

Properties

在I/O流处详细介绍这里先了解一下
在这里插入图片描述

基本使用:
@SuppressWarnings({"all"})
public class Properties_ {
    public static void main(String[] args) {

        //老韩解读
        //1. Properties 继承  Hashtable
        //2. 可以通过 k-v 存放数据,当然key 和 value 不能为 null
        //增加
        Properties properties = new Properties();
        //properties.put(null, "abc");//抛出 空指针异常
        //properties.put("abc", null); //抛出 空指针异常
        properties.put("john", 100);//k-v
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);//如果有相同的key , value被替换

        System.out.println("properties=" + properties);

        //通过k 获取对应值
        System.out.println(properties.get("lic"));//88

        //删除
        properties.remove("lic");
        System.out.println("properties=" + properties);

        //修改
        properties.put("john", "约翰");
        System.out.println("properties=" + properties);

        


    }
}

Collection工具类

概念:集合工具类,定义了除了存取以外的集合常用方法。

方法:

public static void reverse(List<?> list)//反转集合中元素的顺序
public static void shuffle(List<?> list)//随机重置集合元素的顺序
public static void sort(List list)//升序排序(元素类型必须实现Comparable接口)
查找、替换

  1. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  2. Object max(Collection, Comparator):根据Comparator 指定的顺序,返回给定集合中的最大元素
  3. Object min(Collection)
  4. Object min(Collection,Comparator)
  5. int frequency(Collection,Object):返回指定集合中指定元素的出现次数(该方法要求目标元素容量大于等于源目标)
  6. void copy(List dest,List src):将src中的内容复制到dest中
  7. boolean replaceAll(List list,Object oldVal, Object newVal):使用新值替换List 对象的所有旧值
/**
 * 演示Collections工具类的使用
 *
 */
public class Demo4 {
	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);
		
		//注:基本类型转成集合时需要修改为包装类
	}
}

练习

在这里插入图片描述

package CollectionStudy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

public class Homework1 {
    public static void main(String[] args) {
        ArrayList<News> newsArrayList = new ArrayList<News>();
        //
        News news1 = new News("新闻1:新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧");
        News news2 =new News("新闻2:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生");
        newsArrayList.add(news1);
        newsArrayList.add(news2);
        Collections.reverse(newsArrayList);
        //对文章标题处理,超过15个在后面加...
        Iterator<News> iterator = newsArrayList.iterator();
        while(iterator.hasNext()){
            String temp=iterator.next().getTile();
            if(temp.length()>15) {
                System.out.println(temp.substring(0,15)+"...");
            }
            else System.out.println(temp);
        }
    }
}
class News{
    private String tile;
    private String value;

    public String getTile() {
        return tile;
    }

    public void setTile(String tile) {
        this.tile = tile;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public News(String tile) {
        this.tile = tile;
    }

    @Override
    public String toString() {
        return "News{" +
                "tile='" + tile + '\'' +
                '}';
    }
}

在这里插入图片描述

package CollectionStudy;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Homework3 {
    public static void main(String[] args) {
        HashMap<String,Integer> workerHashMap = new HashMap<String,Integer>();
        workerHashMap.put("jack",650);
        workerHashMap.put("tom",1200);
        workerHashMap.put("smith",2900);
        //将Jack的工资改为2600
        workerHashMap.put("jack",2600);
        //将所有员工的工资加100
        Iterator<String> iterator1 = workerHashMap.keySet().iterator();
        while(iterator1.hasNext()){
            String key=iterator1.next();
            workerHashMap.put(key,workerHashMap.get(key)+100);
        }
        //遍历所有员工
        Iterator<Map.Entry<String ,Integer>> iterator = workerHashMap.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<String,Integer> entry=iterator.next();
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
        for (Map.Entry<String, Integer> entry : workerHashMap.entrySet()) {
            System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }
    }
}

在这里插入图片描述
由于Person类重写了hashCode和equals方法,
假定根据id=1001和name=AA得来的hashCode,规定p1在table表的索引1位置
同理规定p2在索引2位置,这时令p1.name=CC,再调用set.remove(p1)
但此时p1的name=CC所以hashCode也随之改变,要删除的索引位置很有可能不再是1,删除很有可能失败
set.add(new Person(1001,“CC”))同理,此时规定添加的索引位置很有可能不再是1,添加成功。
最有意思的来了set.add(new Person(1001,AA)),由于根据id=1001和name=AA找到的table索引位置一定是1,
但是由于在索引1上的p1的name变为CC,所以根据equals原则两者并不相同,这时只能生成链表将其加到p1后面(最后总共四个对象)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
B站上的韩顺平老师的《Linux学习笔记》系列课程非常值得推荐。通过这个课程,我学到了很多关于Linux操作系统的知识和技能。 首先,韩老师在课程中详细介绍了Linux的基本概念和特点。我清楚地了解到Linux是一个开源的操作系统,具有稳定性、安全性和可定制性强的特点。这让我对Linux有了更深入的理解,也更有信心去学习和使用它。 其次,韩老师从基础开始,逐步讲解了Linux的安装和配置。他用简单明了的语言和实际操作的示范,帮助我了解了如何在虚拟机上安装Linux系统,并设置网络、用户账户、文件系统等。这为我后续的学习和实践打下了坚实的基础。 此外,韩老师还讲解了Linux的常用命令和工具。他详细介绍了常用的文件和目录操作命令,比如cd、ls、mkdir、cp等。同时,他还讲解了grep、sed、awk等强大的文本处理工具的使用方法。这些内容帮助我更加高效地进行文件管理和数据处理。 最后,韩老师还介绍了Linux的网络管理和安全防护。他讲解了如何配置网络连接、使用ssh远程登录以及设置防火墙等内容。这些知识对我了解网络和保护系统安全非常有帮助。 总的来说,韩顺平老师的《Linux学习笔记》课程非常实用,对于初学者来说是入门学习Linux的好选择。他通过深入浅出的讲解和丰富的实操示范,让我可以轻松地学习到Linux的基本知识和操作技巧。我相信通过学习这个课程,我会在Linux领域有更进一步的发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值