Java 集合与泛型

文章来源

https://blog.csdn.net/weixin_41121133/article/details/109409268

https://www.bilibili.com/video/BV1zD4y1Q7Fw


  • 概念 :对象的容器,定义了对多个对象进项操作的的常用方法。可实现数组的功能。
  • 和数组的区别
  1. 数组长度固定,集合长度不固定。
  2. 数组可以存储基本类型和引用类型,集合只能存储引用类型。
  • 位置 : 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() //姜此集合转换成数组。
    public class Demo1{
        pubic static void main(String[] args){
    
            Collection collection=new ArrayList();    
    
            Collection.add("苹果");
            Collection.add("西瓜");
            Collection.add("榴莲");
            System.out.println("元素个数:"+collection.size());
            System.out.println(collection);
    
            collection.remove("榴莲");
            System.out.println("删除之后:"+collection.size());
    
    
            for(Object object : collection){
                System.out.println(object);
            }
    
    
    
    
            Iterator iterator=collection.Itertor();
            while(iterator.hasnext()){
                String object=(String)iterator.next();
                System.out.println(s);
    
    
    
    
            System.out.println(collection.contains("西瓜"));
            System.out.println(collection.isEmpty());
            }
        }
    }
    
    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);
    
    		collection.add(s1);
    		collection.add(s2);
    		collection.add(s3);
    
    		System.out.println("元素个数:"+collection.size());
    		System.out.println(collection.toString());
    
    		collection.remove(s1);
    		System.out.println("删除之后:"+collection.size());
    
    
    		for(Object object:collection) {
    			Student student=(Student) object;
    			System.out.println(student.toString());
    		}
    
    
    		Iterator iterator=collection.iterator();
    		while (iterator.hasNext()) {
    			Student student=(Student) iterator.next();
    			System.out.println(student.toString());
    		}
    
    	}
    }
    
    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。
    • boolean addAll(index,Collection c) //将一个集合中的元素添加到此集合中的index位置。
    • Object get(int index) //返回集合中指定位置的元素。
    • List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之间的集合元素。
    public class Demo3 {
    	public static void main(String[] args) {
    		List list=new ArrayList<>();
    
    		list.add("tang");
    		list.add("he");
    		list.add(0,"yu");
    		System.out.println("元素个数:"+list.size());
    		System.out.println(list.toString());
    
    		list.remove(0);
    
    		System.out.println("删除之后:"+list.size());
    		System.out.println(list.toString());
    
    
    		for(int i=0;i<list.size();++i) {
    			System.out.println(list.get(i));
    		}
    
    		for(Object object:list) {
    			System.out.println(object);
    		}
    
    		Iterator iterator=list.iterator();
    		while (iterator.hasNext()) {
    			System.out.println(iterator.next());
    		}
    
    		ListIterator listIterator=list.listIterator();
    
    		while (listIterator.hasNext()) {
    			System.out.println(listIterator.next());
    		}
    
    		while(listIterator.hasPrevious()) {
    			System.out.println(listIterator.previous());
    		}
    
    		System.out.println(list.isEmpty());
    		System.out.println(list.contains("tang"));
    
    		System.out.println(list.indexOf("tang"));
    	}
    }
    
    public class Demo4 {
    	public static void main(String[] args) {
    		List list=new ArrayList();
    
    		list.add(20);
    		list.add(30);
    		list.add(40);
    		list.add(50);
    		System.out.println("元素个数:"+list.size());
    		System.out.println(list.toString());
    
    		list.remove(0);
    
    
    
    		System.out.println("元素个数:"+list.size());
    		System.out.println(list.toString());
    
    
    		List list2=list.subList(1, 3);
    		System.out.println(list2.toString());
    	}
    }
    

List 实现类
ArrayList【重点】
  • 数组结构实现,查询块、增删慢;
  • JDK1.2 版本,运行效率快、线程不安全。
public class Demo5 {
	public static void main(String[] args) {
		ArrayList arrayList=new ArrayList<>();

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

		arrayList.remove(s1);





		Iterator iterator=arrayList.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}

		ListIterator listIterator=arrayList.listIterator();

		while(listIterator.hasNext()) {
			System.out.println(listIterator.next());
		}

		while(listIterator.hasPrevious()) {
			System.out.println(listIterator.previous());
		}

		System.out.println(arrayList.isEmpty());



		System.out.println(arrayList.indexOf(s1));
	}
}

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

@Override
public boolean equals(Object obj) {

	if (this==obj) {
		return true;
	}

	if (obj==null) {
		return false;
	}

	if (obj instanceof Student) {
		Student student=(Student) obj;
	
		if(this.name.equals(student.getName())&&this.age==student.age) {
			return true;
		}
	}

	return false;
}
ArrayList 源码分析
  • 默认容量大小: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);  
        elementData[size++] = e;
        return true;
    }
    

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

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    

    该方法中的参数 minCapacity 传入的值为 size+1 也就是 1,接着我们再进入到 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++;
    
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    因为 elementData 数组长度为 0,所以 if 条件成立,调用 grow 方法, 重要的部分来了 ,我们再次进入到 grow 方法的源码中:

    private void grow(int minCapacity) {
    
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
    
        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;
    

这个值太大了以至于第二个 if 条件没有了解的必要。
最后一句话就是为 elementData 数组赋予了新的长度,Arrays.copyOf() 方法返回的数组是新的数组对象,原数组对象不会改变,该拷贝不会影响原来的数组。copyOf() 的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。
这时候再回到 add 的方法中,接着就向下执行 elementData[size++] = e; 到这里为止关于 ArrayList 就讲解得差不多了,当数组长度为 10 的时候你们可以试着过一下源码,查一下每次的增量是多少(答案是每次扩容为原来的 1.5 倍)。

Vector
  • 数组结构实现,查询快、增删慢;
  • JDK1.0 版本,运行效率慢、线程安全。
    public class Demo1 {
    	public static void main(String[] args) {
    		Vector vector=new Vector<>();
    
    		vector.add("tang");
    		vector.add("he");
    		vector.add("yu");
    		System.out.println("元素个数:"+vector.size());
    
    
    
    
    
    
    		Enumeration enumeration=vector.elements();
    		while (enumeration.hasMoreElements()) {
    			String s = (String) enumeration.nextElement();
    			System.out.println(s);
    		}
    
    		System.out.println(vector.isEmpty());
    		System.out.println(vector.contains("he"));
    
    
    	}
    }
    

LinkedList
  • 链表结构实现,增删快,查询慢。
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);

		linkedList.add(s1);
		linkedList.add(s2);
		linkedList.add(s3);
		linkedList.add(s3);
		System.out.println("元素个数:"+linkedList.size());
		System.out.println(linkedList.toString());







		for(int i=0;i<linkedList.size();++i) {
			System.out.println(linkedList.get(i));
		}

		for(Object object:linkedList) {
			Student student=(Student) object;
			System.out.println(student.toString());
		}

		Iterator iterator =linkedList.iterator();
		while (iterator.hasNext()) {
			Student student = (Student) iterator.next();
			System.out.println(student.toString());
		}


		System.out.println(linkedList.contains(s1));
		System.out.println(linkedList.isEmpty());
		System.out.println(linkedList.indexOf(s3));
	}
}
LinkedList 源码分析

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

首先 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:无需开辟连续空间,查询慢,增删快。


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


	T t;

	public void show(T t) {
		System.out.println(t);
	}

	public T getT() {
		return t;
	}
}
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();
	}
}
泛型接口
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");
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);
泛型方法
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 类,虽然它是所有类的父类,可以存储任意的类型,但是在遍历、获取元素时需要原来的类型就要进行强制转换。这个时候就会出现一些问题,假如往链表里存储了许多不同类型的数据,在强转的时候就要判断每一个原来的类型,这样就很容易出现错误。


Set 集合概述
Set 子接口
  • 特点 :无序、无下标、元素不可重复。
  • 方法 :全部继承自 Collection 中的方法。
public class Demo1 {
	public static void main(String[] args) {
		Set<String> set=new HashSet<String>();

		set.add("tang");
		set.add("he");
		set.add("yu");
		System.out.println("数据个数:"+set.size());
		System.out.println(set.toString());






		for (String string : set) {
			System.out.println(string);
		}

		Iterator<String> iterator=set.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}

		System.out.println(set.contains("tang"));
		System.out.println(set.isEmpty());
	}
}
Set 实现类
HashSet【重点】
  • 基于 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 + "]";
	}
}
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);

		hashSet.add(p1);
		hashSet.add(p2);
		hashSet.add(p3);
    
        hashSet.add(p3);
    
    
        hashSet.add(new Person("yu", 21));
		System.out.println(hashSet.toString());

		hashSet.remove(p2);


		for (Person person : hashSet) {
			System.out.println(person);
		}

		Iterator<Person> iterator=hashSet.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}

		System.out.println(hashSet.isEmpty());
    
    
		System.out.println(hashSet.contains(new Person("tang", 21)));
	}
}

:hashSet 存储过程:

  1. 根据 hashCode 计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
  2. 执行 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 这个数字大概有两个原因:

  1. 31 是一个质数,这样的数字在计算时可以尽量减少散列冲突。
  2. 可以提高执行效率,因为 31*i=(i<<5)-i,31 乘以一个数可以转换成移位操作,这样能快一点;但是也有网上一些人对这两点提出质疑。

TreeSet
  • 基于排序顺序实现不重复。
  • 实现了 SortedSet 接口,对集合元素自动排序。
  • 元素对象的类型必须实现 Comparable 接口,指定排序规则。
  • 通过 CompareTo 来指定排序规则。
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);

		persons.add(p1);
		persons.add(p2);
		persons.add(p3);

		System.out.println(persons.toString());

		persons.remove(p1);
		persons.remove(new Person("he", 22));
		System.out.println(persons.toString());


		System.out.println(persons.contains(new Person("yu", 21)));
	}
}

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

public class Person implements Comparable<Person>{
    @Override


	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 的构造方法,使用匿名内部类来实现它:

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

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

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

Map 集合概述

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

  • 方法

    • V put(K key,V value)// 将对象存入到集合中,关联键值。key 重复则覆盖原值。
    • Object get(Object key)// 根据键获取相应的值。
    • Set<Map.Entry<K,V>>// 键值匹配的 set 集合
    • Collection<V> values()// 返回包含所有值的 Collection 集合。
    • Set<K>// 返回所有的 key
  • public class Demo1 {
    	public static void main(String[] args) {
    		Map<String,Integer> map=new HashMap<String, Integer>();
    
    		map.put("tang", 21);
    		map.put("he", 22);
    		map.put("fan", 23);
    		System.out.println(map.toString());
    
    		map.remove("he");
    		System.out.println(map.toString());
    
    
    		for (String key : map.keySet()) {
    			System.out.println(key+" "+map.get(key));
    		}
    
    		for (Map.Entry<String, Integer> entry : map.entrySet()) {
    			System.out.println(entry.getKey()+" "+entry.getValue());
    		}
    	}
    }
    
  • Map 接口的特点:

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

Map 集合的实现类

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

    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 + "]";
      	}
      }
    
    public class Demo2 {
      	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);
    
      		hashMap.put(s1, "成都");
      		hashMap.put(s2, "杭州");
      		hashMap.put(s3, "郑州");
    
      		hashMap.put(s3,"上海");
    
    
      		hashMap.put(new Student("he", 10),"上海");
      		System.out.println(hashMap.toString());
    
      		hashMap.remove(s3);
      		System.out.println(hashMap.toString());
    
    
      		for (Student key : hashMap.keySet()) {
      			System.out.println(key+" "+hashMap.get(key));
      		}
    
      		for (Entry<Student, String> entry : hashMap.entrySet()) {
      			System.out.println(entry.getKey()+" "+entry.getValue());
      		}
    
    
      		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;
      }
    
    HashMap 源码分析
  • 默认初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 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;
      }
    

    之前的代码中在 new 对象时调用的是 HashMap 的无参构造方法,进入到该构造方法的源码查看一下:

    public HashMap() {
          this.loadFactor = DEFAULT_LOAD_FACTOR; 
      }
    

    发现没什么内容,只是赋值了一个默认加载因子;而在上文我们观察到源码中 table 和 size 都没有赋予初始值,说明刚创建的 HashMap 对象没有分配容量,并不拥有默认的 16 个空间大小,这样做的目的是为了节约空间,此时 table 为 null,size 为 0。
    当我们往对象里添加元素时调用 put 方法:

    public V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
      }
    

    put 方法把 key 和 value 传给了 putVal,同时还传入了一个 hash(Key) 所返回的值,这是一个产生哈希值的方法,再进入到 putVal 方法(部分源码):

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

    这里面创建了一个 tab 数组和一个 Node 变量 p,第一个 if 实际是判断 table 是否为空,而我们现在只关注刚创建 HashMap 对象时的状态,此时 tab 和 table 都为空,满足条件,执行内部代码,这条代码其实就是把 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 {           
              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;
      }
    

    该方法首先把 table 及其长度赋值给 oldTab 和 oldCap;threshold 是阈值的意思,此时为 0,所以前两个 if 先不管,最后 else 里 newCap 的值为默认初始化容量 16;往下创建了一个 newCap 大小的数组并将其赋给了 table,刚创建的 HashMap 对象就在这里获得了初始容量。然后我们再回到 putVal 方法,第二个 if 就是根据哈希码得到的 tab 中的一个位置是否为空,为空便直接添加元素,此时数组中无元素所以直接添加。至此 HashMap 对象就完成了第一个元素的添加。当添加的元素超过 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)
          }
      }
    

    核心部分是 else if 里的移位操作, 也就是说每次扩容都是原来大小的两倍

    • 注 :额外说明的一点是在 JDK1.8 以前链表是头插入,JDK1.8 以后链表是尾插入。

HashSet 源码分析

了解完 HashMap 之后,再回过头来看之前的 HashSet 源码,为什么放在后面写你们看一下源码就知道了(部分):

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

可以看见 HashSet 的存储结构就是 HashMap,那它的存储方式是怎样的呢?可以看一下 add 方法:

public boolean add(E e) {
      return map.put(e, PRESENT)==null;
  }

很明了地发现它的 add 方法调用的就是 map 的 put 方法,把元素作为 map 的 key 传进去的。。

Hashtable
  • JDK1.0 版本,线程安全,运行效率慢;不允许 null 作为 key 或是 value。
  • 初始容量 11,加载因子 0.75。
    这个集合在开发过程中已经不用了,稍微了解即可。
Properties
  • Hashtable 的子类,要求 key 和 value 都是 String。通常用于配置文件的读取。

它继承了 Hashtable 的方法,与流关系密切,此处不详解。

TreeMap
  • 实现了 SortedMap 接口(是 Map 的子接口),可以对 key 自动排序。
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);

		treeMap.put(s1, 21);
		treeMap.put(s2, 22);
		treeMap.put(s3, 21);

		System.out.println(treeMap.toString());

		treeMap.remove(new Student("he", 10));
		System.out.println(treeMap.toString());


		for (Student key : treeMap.keySet()) {
			System.out.println(key+" "+treeMap.get(key));
		}

		for (Entry<Student, Integer> entry : treeMap.entrySet()) {
			System.out.println(entry.getKey()+" "+entry.getValue());
		}

		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;
    }	
});
TreeSet 源码

和 HashSet 类似,放在 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>());
    }
}

TreeSet 的存储结构实际上就是 TreeMap,再来看其存储方式:

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

它的 add 方法调用的就是 TreeMap 的 put 方法,将元素作为 key 传入到存储结构中。


Collections 工具类

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

  • 方法

    • public static void reverse(List<?> list)// 反转集合中元素的顺序
    • public static void shuffle(List<?> list)// 随机重置集合元素的顺序
    • public static void sort(List<T> list)// 升序排序(元素类型必须实现 Comparable 接口)
    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);
    
    
    		System.out.println(list.toString());
    		Collections.sort(list);
    		System.out.println(list.toString());
    		System.out.println("---------");
    
    
    		int i=Collections.binarySearch(list, 10);
    		System.out.println(i);
    
    
    		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());
    
    
    		Collections.reverse(list2);
    		System.out.println(list2.toString());
    
    
    		Collections.shuffle(list2);
    		System.out.println(list2.toString());
    
    
    		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);
    
    
    	}
    }
    
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值