Java集合框架

Java集合框架

参考视频:【千锋】Java集合框架详解 BV1zD4y1Q7Fw

0.集合概述

目的:对象的容器,实现了对对象常用的操作

与数组的区别:

  1. 数组长度固定,集合长度不固定。
  2. 数组刻意存储基本类型和引用类型,集合只能存储引用类型

位置: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等字母作为占位符。

优点:

  1. 提高代码的重用性
  2. 防疫类型转换异常,提高代码的安全性

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 泛型集合

定义:参数化类型、类型安全的集合,强制集合元素的类型必须一致

特点:

  1. 编译时即可检查,而非运行时抛出异常
  2. 访问时,不必类型转换(即拆箱)
  3. 不同泛型之间的引用不能相互赋值。泛型不存在多态
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)));
	}
}

在这里插入图片描述

内部哈希表存储过程
  1. 根据hashCode计算保存的位置,如果位置为空,则直接保存,否则执行第二步。

  2. 执行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接口

特点:

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

常见方法:

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);
		
		//注:基本类型转成集合时需要修改为包装类
	}
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值