学习之集合框架

@生活冷战士 #和我一起去战斗吧!!!学习Java进阶的朋友一定要锁定噢,@我噢,我为你解答。

一、认识集合

为解决数组的缺陷:

  1. 数组长度一旦确定无法更改
  2. 数组长度无法与size相等

在这里插入图片描述

二、List接口

  1. 可以储存有序,可重复,有索引,可为null的对象。
  2. ArrayList类可动态储存数据
  3. ArrayList类 方法

在这里插入图片描述
4. ArrayList类代码操作

public class ArrayListDemo {
	public static  void main(String[] args) {
		System.out.println("crate a list--");
		ArrayList list=new ArrayList();
		list.add("黄信");
		list.add("青信");
		list.add("黄成");
		for (int i = 0; i < list.size(); i++) {
			String name=(String) list.get(i);
			System.out.println(name);
		}
		System.out.println(list.contains("xiaoming"));
		list.remove(0);
		list.set(0, "宋江");
		for (Object obj : list) {
			String name=(String) obj;
			System.out.println(name);
		}
		int i=list.indexOf("黄河");
		System.out.println(i);
		list.clear();
		System.out.println("是否有元素");
		System.out.println(list.isEmpty());
		
	}

}

public class NewTitle {
	private int id;
	private String titleName;
	private String author;
	public NewTitle(int id, String titleName, String author) {
		super();
		this.id = id;
		this.titleName = titleName;
		this.author = author;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getTitleName() {
		return titleName;
	}
	public void setTitleName(String titleName) {
		this.titleName = titleName;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	



public class Student {
	//field
	private int age;
	private String name;
	private String gender;
	//method
	public Student(int age,String name,String gender) {
		super();
		this.name=name;
		this.age=age;
		this.gender=gender;
	}
	
	//重写equals
	@Override
	public boolean equals(Object obj) {
		//是否是同一引用
		if (this == obj)
			return true;
		//判断obj是否为空
		if (obj == null)
			return false;
		//obj是否属于当前类的对象
		if (!(obj instanceof Student)) {
			return false;
		}
		//转换为当前对象
		Student s = (Student) obj;
		//判断每一属性,基本类型用==,有引用类型用equals
		if (this.name.equals(s.name)&&this.age==s.age&&this.gender.equals(s.gender)) {
			return true;
		}
		//前面都没有进去
		return false;
	}

	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + ", gender=" + gender + "]";
	}
	
	

}


import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestStudent {
	public static void main(String[] args) {
		Student s1=new Student(25, "lrving", "man");
		Student s2=new Student(34, "james", "man");
		Student s3=new Student(31, "curry", "man");
		ArrayList <Student> list=new ArrayList<>();
		list.add(s1);
		list.add(s2);
		list.add(s3);
		Iterator <Student>it=list.iterator();
		while (it.hasNext()) {
			Student student=it.next();
			System.out.println(student);			
		}
	    boolean a=s1.equals(new Student(25, "lrving", "man"));
		System.out.println(a);
		int as=list.lastIndexOf(s3);
		System.out.println(as);
	}

}

5、ArrayList的底层实现

package cn.itlaobing.list;

import java.util.Arrays;
import java.util.Objects;

public class MyArray {
	/**
	 * 定义数组,保存数据
	 */
	private Object[] objects = null;
	/**
	 * 默认数组长度
	 */
	private final int DEFAULT_LENGTH = 10;
	/**
	 * 数组长度
	 */
	private int length;
	/**
	 * 数组元素个数
	 */
	private int size = 0;
	/**
	 * 长度的倍数
	 */
	private final int LOAD = 2;
	/**
	 * 默认构造函数,创建数组
	 */	
	/*
	 * trimsize中计数
	 */
	private int modCount;

	public MyArray() {
		length = DEFAULT_LENGTH;
		objects = new Object[length];
	}

	/**
	 * 构造函数,根据用户传入长度创建数组
	 * 
	 * @param length
	 */
	public MyArray(int length) {
		this.length = length;
		objects = new Object[length];
	}
	/**
	 * 如果长度等于大小时,扩容。
	 */
	private void addLength() {
		if (size == length) {
			// 元素个数等于数组长度,需要扩容
			length = length * LOAD;
			objects = Arrays.copyOf(objects, length);
		}
	}

	/*
	 * 向数组中添加元素
	 */
	public boolean add(Object o) {
		// 判断是否扩容
		addLength();
		objects[size] = o;
		size++;
		return true;
	}

	/**
	 * 添加数据
	 */
	public boolean add(int index, Object o) {
		if (index < 0 || index > size - 1) {
			throw new ArrayIndexOutOfBoundsException("越界");
		} else {
			// 判断是否需要扩容
			addLength();
			
			System.arraycopy(objects, index, objects, index + 1, size - index - 1);
			objects[index] = o;
			size++;
		}
		return true;
	}
	
	/**
	 * 在索引处移除
	 * @param index
	 * @return
	 */
     
	public  Object remove(int index) {
		Object o = null;
		if (index < 0 || index > size - 1) {
			throw new ArrayIndexOutOfBoundsException("越界");
		} else {
			o = objects[index];
			System.arraycopy(objects, index + 1, objects, index, size - index - 1);
			size--;
			objects[size] = null;
		}
		return  o;
	}
	
	/**
	 * 让长度length与个数size相等
	 */
	
	public void trimToSize() {
        modCount++;
        if (size < objects.length) {
        	objects = (size == 0) ? objects : Arrays.copyOf(objects, size);
        }
    }
	
	
	/**
	 * 重写tostring
	 */
	/**
	 * 
	 * @param o
	 * @return
	 */

//打印数组方法一
	public String toString(Object o) {		
			StringBuffer sb = new StringBuffer("[");			
			for (int i = 0; i < size; i++) {
				sb.append(objects[i]);
				if(i!=size-1)
			 	  sb.append(",");
			}			
			sb.append("]");			
			return sb.toString();	
	}
	
	/**
	 * 获得元素的个数
	 * @return
	 */
	
	public int size() {
		return size;
	}
	
	
	/**
	 * 获得索引处的元素
	 * @param index
	 * @return
	 */

	public Object get(int index) {
		if (index<0&&index>size) {
			throw new ArrayIndexOutOfBoundsException("越界");
		}
		return objects[index];
	}
	
	
	/**
	 * 索引元素所在的位置
	 * @param o
	 * @return
	 */
	
	public int indexOf(Object o) {
		if (o==null) {
			return -1;
		}
		for (int i = 0; i < size; i++) {
			if (o.equals(objects[i])) {
				return i;
			}
		}
		return -1;
	}
	
	/**
	 * 判断是否包含某个元素
	 * @param o
	 * @return
	 */
	public boolean contains(Objects o) {
		
		int i=indexOf(o);				
		return i>=0?true:false;
		
	}
	
	/**
	 * 移除所有元素
	 * @return
	 */
	public boolean removeAll() {
		Arrays.fill(objects, null);	
		size=0;
		return true;
	}
	
	/**
	 * 更新数组
	 * @param index
	 * @param o
	 * @return
	 */
	public boolean update(int index,Object o) {
		if (index<0&&index>size) {
			throw new ArrayIndexOutOfBoundsException("越界");
		}
		objects[index]=o;
		return true;
	}
	
	/**
	 * 打印数组方法二
	 */
	
	public String toString() {
		StringBuffer sb=new StringBuffer("[");
		for (int i = 0; i < size; i++) {
			sb.append(objects[i]);
			sb.append(",");
		}
		sb.append("]");
		return sb.toString().replace(",]", "]");
	}
	

}

5、使用lLinkedList类动态储存数据

  1. LinkedList类采用链表储存数据
  2. 新增加的方法
    在这里插入图片描述
  3. 代码操作
package cn.itlaobing.list;

import java.util.LinkedList;

public class DemoLinkedList {

	public static void main(String[] args) {
		NewTitle car1 = new NewTitle(1, "沃尔沃", "姚盖");
		NewTitle car2 = new NewTitle(2, "凯迪拉克", "姚明");
		NewTitle car3 = new NewTitle(3, "甲壳虫", "小佳");
		NewTitle car4 = new NewTitle(4, "MINI", "宋二娘");
		LinkedList<NewTitle> newsLinkedList = new LinkedList<NewTitle>();
		newsLinkedList.add(car2);
		newsLinkedList.add(car3);
		newsLinkedList.add(car4);
		newsLinkedList.add(car1);
		NewTitle first = (NewTitle) newsLinkedList.getFirst();
		System.out.println(first.getTitleName());
		NewTitle last = (NewTitle) newsLinkedList.getLast();
		System.out.println(last.getTitleName());
		newsLinkedList.removeFirst();
		newsLinkedList.removeLast();
		System.out.println("first and last remove");
		System.out.println("遍历所有新闻标题");
		for (Object object : newsLinkedList) {
			NewTitle newTitle = (NewTitle) object;
			System.out.println(newTitle.getTitleName());

		}

	}

}


4、底层实现


package cn.itlaobing.list;

import java.security.PublicKey;
import java.util.Iterator;
import java.util.LinkedList;

import javax.xml.soap.Node;

public class MyLinklist<E> {
	/**
	 * 链表元素个数
	 */
	private int size;
	/**
	 * 链表首节点
	 */
	private Node first;
	/**
	 * 链表尾节点
	 */
	private Node last;



	/**
	 * 默认构造创建一个空链表
	 */
	public MyLinklist() {
		size = 0;
		first = null;
		last = null;
	}

	private void checkLinkError(int index) {
		if (index < 0 || index > size) {
			throw new IndexOutOfBoundsException("访问越界");
		}

	}

	/**
	 * 创建一个节点
	 * 
	 * @author 神州战神
	 *
	 * @param <E>
	 */
	class Node<E> {
		private Node<E> pre;
		private E data;
		private Node<E> next;

		public Node(Node<E> pre, E data, Node<E> next) {
			super();
			this.pre = pre;
			this.data = data;
			this.next = next;
		}
	}

	/**
	 * 在链表末尾添加元素
	 */

	private void linkLast(E e) {
		// 1、创建新节点
		// 2、将新节点指向原来的尾节点
		Node<E> newNode = new Node<E>(last, e, null);
		if (last == null) {
			last = newNode;
			first = last;
		} else {
			// 3、将原来的尾节点指向新节点
			last.next = newNode;
			// 4、将新节点变成尾节点
			last = newNode;
		}
		size++;
	}

	/**
	 * addLst方法
	 */
	public void addLast(E e) {
		linkLast(e);
	}

	/**
	 * 获取链表大小
	 * 
	 * @return
	 */
	public int size() {
		return size;
	}

	/**
	 * 在链表表头添加元素
	 */
	private void linkfirst(E e) {
		// 1、创建首节点
		// 2、将新节点指向首节点
		// 3、原先的首节点pre指向新节点
		// 4、将新节点变为首节点
		Node<E> newNode = new Node<E>(null, e, first);
		if (first == null) {
			last = newNode;
			first = newNode;
		} else {

			first.pre = newNode;
			first = newNode;
		}
		size++;

	}

	/**
	 * addFirst方法
	 * 
	 * @param e
	 */
	public void addFirst(E e) {
		linkfirst(e);
	}

	/**
	 * 链表中间添加
	 * 
	 * @param index
	 * @param e
	 */
	private void linkafter(int index, E e) {
		checkLinkError(index);
		// 1、新建节点Node;先找到索引处节点node,
		Node<E> node = new Node(null, e, last);
		if (size == 0 && index == 0) {

			first = node;

		}
		// 当来链表只有一个元素或者索引为最后一个元素时

		else if (size == 1 || index == size - 1) {

			last.next = node;

		} else {
			// 获得索引处节点
			Node<E> indexnode = getNodeByIndex(index);
			// 2、下个节点为Next
			Node<E> Next = indexnode.next;
			// 新建节点,pre指向索引处节点,next指向下节点Next
			Node<E> newNode = new Node(indexnode, e, Next);
			// 索引处节点指向新建节点
			indexnode.next = newNode;
			// 下节点指向新建节点
			Next.pre = newNode;
		}

		size++;
	}

	public void add(int index, E e) {
		linkafter(index, e);
	}

	/**
	 * 删除索引处元素
	 * 
	 * @param index
	 * @return
	 */

	private boolean remove(int index) {
		checkLinkError(index);
		if (size == 1 || size == 0) {
			first = null;
			last = null;
		} else if (index == 0) {
			Node<E> removeNode = first;
			first = first.next;
			first.pre = null;
			removeNode.next = null;
		} else if (index == size - 1) {
			Node<E> removeNode = last;
			last = last.pre;
			last.next = null;
			removeNode.pre = null;
		} else {
			// 1、找到索引处的节点
			Node<E> Node = getNodeByIndex(index);
			// 2、找到索引处下一个节点
			Node<E> Next = Node.next;
			// 3、找到索引处前置节点
			Node<E> Pre = Node.pre;
			// 4、将索引处前个节点指向Next节点
			Pre.next = Next;
			// 4、将Next节点指向索引处前个节点
			Next.pre = Pre;
		}

		size--;
		return true;
	}

	/**
	 * remove方法
	 * 
	 * @param index
	 */
	public void Remove(int index) {
		remove(index);
	}

	/**
	 * 修改索引处的节点
	 * 
	 * @param index
	 * @param e
	 * @return
	 */
	private void set(int index, E e) {
		Node<E> node = first;
		for (int i = 0; i < size && node != null; i++, node = node.next) {
			if (i == index) {
				node.data = e;
				break;
			}
		}
	}

	/**
	 * Set方法
	 * 
	 * @param index
	 * @param e
	 * @return
	 */
	public void Set(int index, E e) {
		set(index, e);
	}

	/**
	 * 根据索引找到节点
	 */
	private Node<E> getNodeByIndex(int index) {
		checkLinkError(index);
		if (index == 0) {
			return first;
		} else if (index == size - 1) {
			return last;
		} else {
			Node<E> current = first;
			for (int i = 0; i < size && current != null; i++) {
				if (i == index) {
					return current;
				}
				current = current.next;
			}
		}
		return first;
	}

	/**
	 * 根据索引找到节点
	 */

	public E get(int index) {
		Node current = getNodeByIndex(index);
		return (E) current.data;
	}

	/**
	 * 展示链表
	 * 
	 * @return
	 */

	@Override
	public String toString() {
		Node<E> p = first;
		StringBuffer sb = new StringBuffer("{");
		for (int i = 0; i < size && p != null; i++, p = p.next) {
			sb.append(p.data);
			if (i != size - 1) {
				sb.append(",");
			}

		}
		sb.append("}");
		return sb.toString();
	}

}

三、Set接口

  1. 储存一组唯一、无序的对象。
  2. HashSet类动态储存数据
  3. HashSet类方法
    在这里插入图片描述
  4. 代码操作
1、
import java.util.LinkedList;
public class DemoHashSet {
	public static void main(String[] args) {
		NewTitle car1 = new NewTitle(1, "沃尔沃", "姚盖");
		NewTitle car2 = new NewTitle(2, "凯迪拉克", "姚明");
		NewTitle car3 = new NewTitle(3, "甲壳虫", "小佳");
		NewTitle car4 = new NewTitle(4, "MINI", "宋二娘");
		LinkedList<NewTitle> newsLinkedList = new LinkedList<NewTitle>();
		newsLinkedList.add(car1);
		newsLinkedList.add(car2);
		newsLinkedList.add(car3);
		newsLinkedList.add(car4);
		System.out.println(newsLinkedList.size());
		System.out.println(newsLinkedList.contains(car3));
		newsLinkedList.remove();
		System.out.println();
		System.out.println(newsLinkedList.isEmpty());
		for (Object  obj : newsLinkedList) {
			NewTitle newTitle=(NewTitle) obj;
			System.out.println(newTitle.getTitleName());
		}
	}

}

2、Set练习		
	
	public class HashSetTest {
public static void main(String[] args) {
    Set<String> set = new HashSet<>();
	//添加元素
	set.add("陈忠实");
	set.add("贾平凹");
	set.add("路遥");
	set.add("张爱玲");
	set.add("林徽因");
	boolean a = set.add("徐志摩");
	System.out.println(a);
	boolean b = set.add("徐志摩");
	System.out.println(b);
	//set遍历 显示set的无序性
	//foreach
	for (String s : set) {
		System.out.println(s);
	}
	//iterator
	Iterator<String> it = set.iterator();
	while (it.hasNext()) {
		String s = it.next();
	   System.out.println(s);
	   }
	}
}

package cn.itlaobing.java;

public class Student {
	private int age;
	private String name;
	private String gender;
	
	
	public Student(int age, String name, String gender) {
		super();
		this.age = age;
		this.name = name;
		this.gender = gender;
	}
	
	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + ", gender=" + gender + "]";
	}

	@Override
	public int hashCode() {
		return age*17+name.hashCode()*11+gender.hashCode()*31;
	}
	@Override
	public boolean equals(Object obj) {
		//1.判断是否是同一引用
		if(this==obj) {
			return true;
		}
		//2.判断是否为null
		if(obj==null) {
			return false;
		}
		//3.判断是否为同一类型
		if(!(obj instanceof Student)) {
			return false;
		}
		//4.向下转型
		Student s = (Student) obj;
		//5.比较实例变量
		if(this.age==s.age&&this.name.equals(s.name)&&this.gender.equals(s.gender)) {
			return true;
		}
		return false;
	}
}

public class Test {
	public static void main(String[] args) {
		
		Student s1 = new Student(18, "翠花", "男");
		Student s2 = new Student(18, "翠花", "男");
		Student s3 = new Student(20, "达叔", "男");
		HashSet<Student> set = new HashSet<>();
		
		
		String a = new String("佟年");
		Set<String> set2 = new HashSet<>();
		set2.add("佟年");
//		boolean  d = set2.remove("佟年");
		boolean k = set2.remove("年");
		System.out.println(k);
//		boolean s = set2.contains(a);
//		System.out.println(s);
//		
//		set.add(s1);
//		set.add(s2);
//		set.add(s3);
//		set.clear();
//		int i = set.size();
//		set.isEmpty();
//		System.out.println(i);
		
		

	}

5.底层实现

import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set;

import javax.swing.text.html.HTMLDocument.Iterator;

public class HashSet<E>  
          extends AbstractSet<E>  
          implements Set<E>, Cloneable, java.io.Serializable  
{  
static final long serialVersionUID = -5024744406713321676L;  

// 底层使用HashMap来保存HashSet中所有元素。  
private transient HashMap<E,Object> map;  
  
// 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。  
private static final Object PRESENT = new Object();  

 
//  默认的无参构造器,构造一个空的HashSet。 
//   
//  实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。 
  
public HashSet() {  
map = new HashMap<E,Object>();  
}  

 
//  构造一个包含指定collection中的元素的新set。 
//  
//  实际底层使用默认的加载因子0.75和足以包含指定 
//  collection中所有元素的初始容量来创建一个HashMap。 
//  @param c 其中的元素将存放在此set中的collection。 
 
public HashSet(Collection<? extends E> c) {  
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));  
addAll(c);  
}  

 
//  以指定的initialCapacity和loadFactor构造一个空的HashSet。 
//  
//  实际底层以相应的参数构造一个空的HashMap。 
// @param initialCapacity 初始容量。 
//  @param loadFactor 加载因子。 
   
public HashSet(int initialCapacity, float loadFactor) {  
map = new HashMap<E,Object>(initialCapacity, loadFactor);  
}  

 
//  以指定的initialCapacity构造一个空的HashSet。 
//  
//  实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。 
//  @param initialCapacity 初始容量。 
 
public HashSet(int initialCapacity) {  
map = new HashMap<E,Object>(initialCapacity);  
}  


//  以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。 
//  此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。 
//  
//  实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。 
//  @param initialCapacity 初始容量。 
//  @param loadFactor 加载因子。 
//  @param dummy 标记。 
   
HashSet(int initialCapacity, float loadFactor, boolean dummy) {  
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);  
}  

 
//  返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。 
//  
// 底层实际调用底层HashMap的keySet来返回所有的key。 
//  可见HashSet中的元素,只是存放在了底层HashMap的key上, 
//  value使用一个static final的Object对象标识。 
//  @return 对此set中元素进行迭代的Iterator。 

 public Iterator<E> iterator() {  
     return map.keySet().iterator();  

}  
 
// 返回此set中的元素的数量(set的容量)。 
//  
//  底层实际调用HashMap的size()方法返回Entry的数量,就得到该Set中元素的个数。 
//  @return 此set中的元素的数量(set的容量)。 
  
public int size() {  
return map.size();  
}  

 
//  如果此set不包含任何元素,则返回true。 
//  
//  底层实际调用HashMap的isEmpty()判断该HashSet是否为空。 
//  @return 如果此set不包含任何元素,则返回true。 
 
public boolean isEmpty() {  
return map.isEmpty();  
}  

 
//  如果此set包含指定元素,则返回true。 
//  更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e)) 
//  的e元素时,返回true。 
// 
//  底层实际调用HashMap的containsKey判断是否包含指定key。 
// @param o 在此set中的存在已得到测试的元素。 
// @return 如果此set包含指定元素,则返回true。 
  
public boolean contains(Object o) {  
return map.containsKey(o);  
}  


// 如果此set中尚未包含指定元素,则添加指定元素。 
// 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2)) 
// 的元素e2,则向此set 添加指定的元素e。 
// 如果此set已包含该元素,则该调用不更改set并返回false。 
// 
// 底层实际将将该元素作为key放入HashMap。 
// 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key 
//与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true), 
//新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, 
//  因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, 
//  原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。 
//  @param e 将添加到此set中的元素。 
//  @return 如果此set尚未包含指定元素,则返回true。 
 
public boolean add(E e) {  
       return map.put(e, PRESENT)==null;  
}  

//  如果指定元素存在于此set中,则将其移除。 
//  更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e, 
//  则将其移除。如果此set已包含该元素,则返回true 
// (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。 
//  
//  底层实际调用HashMap的remove方法删除指定Entry。 
//  @param o 如果存在于此set中则需要将其移除的对象。 
//  @return 如果set包含指定元素,则返回true。 
 
public boolean remove(Object o) {  
return map.remove(o)==PRESENT;  
}  

 
//  从此set中移除所有元素。此调用返回后,该set将为空。 
//  
//  底层实际调用HashMap的clear方法清空Entry中所有元素。 
 
public void clear() {  
map.clear();  
}  
 
//  返回此HashSet实例的浅表副本:并没有复制这些元素本身。 

//  底层实际调用HashMap的clone()方法,获取HashMap的浅表副本,并设置到HashSet中。 
 
public Object clone() {  
    try {  
        HashSet<E> newSet = (HashSet<E>) super.clone();  
        newSet.map = (HashMap<E, Object>) map.clone();  
        return newSet;  
    } catch (CloneNotSupportedException e) {  
        throw new InternalError();  
    }  
}  
}

对实现原理进行一个总结:
(1)基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

(2)当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。

(3)HashSet的其他操作都是基于HashMap的。
补充:
*重写hashcode规则
1、同一对象多次调用hashcode返回值应该相同。
2、equals返回true,hashcode返回值也应该相同。
3、所参与equals的实例变量,也应该参与重写hashcode
*重写equals的步骤
1、判断是否为同一引用
2、判断是否为同一类型
3、向下转型
4、比较成员变量(引用equals基本为==)
*重写equals的特性
1、自反性:x.equals(x)
2、对称性:x->y,y->x
3、传递性:x->y,y->z,x->z
4、一致性:多次调用,值一致。
5、任何非空x,x.equals(null)返回false

四、Iterator接口

  1. hasNext():判断是否存在下一个可以访问的元素,如果仍有元素可迭代,则返回true。
  2. next():返回要访问的下一个元素。
        Iterator <Student>it=list.iterator();
		while (it.hasNext()) {
			Student student=it.next();
			System.out.println(student);			
		}

Map接口

  1. HashMap:
    HashMap基于Map接口实现,元素以键值对的方式存储,并且允许使用null 建和null值,因为key不允许重复,因此只能有一个键为null,另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。HashMap是线程不安全的。
  2. 继承关系:
public class HashMap<K,V>extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

基本属性:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化大小 16 
static final float DEFAULT_LOAD_FACTOR = 0.75f;     //负载因子0.75
static final Entry<?,?>[] EMPTY_TABLE = {};         //初始化的默认数组
transient int size;     //HashMap中元素的数量
int threshold;          //判断是否需要调整HashMap的容量

注:HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容。HashMap的线程是不安全的,多线程环境中推荐是ConcurrentHashMap。
3. 方法

在这里插入图片描述
3. 遍历

//利用keySet()方法获取keySet
public static void main(String[] args) {
	Map<String,Integer> map = new HashMap<String, Integer>();
	map.put("乔峰",30);
	map.put("虚竹", 20);
	map.put("李秋水", 20);
	map.put("天山童姥", 60);
	map.put("梦姑",18);
	//先获取keySet
	Set<String> set = map.keySet();
	//获取迭代器
	Iterator<String> it = set.iterator();
	//利用迭代器遍历
	while (it.hasNext()) {
		String key = it.next();
		int i = map.get(key);
		System.out.println("key==========="+key);
		System.out.println("value========="+i);
	}
}
//利用entrySet()获取entrySet
public static void main(String[] args) {
	Map<String,Integer> map = new HashMap<String, Integer>();
	map.put("乔峰",30);
	map.put("虚竹", 20);
	map.put("李秋水", 20);
	map.put("天山童姥", 60);
	map.put("梦姑",18);
	//获取键值对集合
	Set<Entry<String, Integer>> set = map.entrySet();
	//遍历set
	for (Entry<String, Integer> entry : set) {
		String key = entry.getKey();
		Integer value = entry.getValue();
		System.out.println("key=========="+key);
		System.out.pintln("key=========="+value);
	}
}

HashMap和Hashtable的区别

1、线程安全

两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全。

Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。

注:

Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理。

2、针对null的不同

HashMap可以使用null作为key,而Hashtable则不允许null作为key

虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事。
注:HashMap以null作为key时,总是存储在table数组的第一个节点上。

3、继承结构

HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类。

4、初始容量与扩容

HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。

HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1即:capacity2+1。

5、两者计算hash的方法不同

Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模。

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。

int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
 
static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
 
 static int indexFor(int h, int length) {
        return h & (length-1);

HashMap的数据存储结构

1、HashMap由数组和链表来实现对数据的存储
HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题。

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

数据结构由数组+链表组成,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key.hashCode())%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

HashMap里面实现一个静态内部类Entry,其重要的属性有 hash,key,value,next。

HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。

 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value); //null总是放在数组的第一个链表中
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        //遍历链表
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果key在链表中已存在,则替换为新value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
 
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
 
 
 
void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next
    //如果size超过threshold,则扩充table大小。再散列
    if (size++ >= threshold)
            resize(2 * table.length);
}

重要方法深度解析

1、构造方法

HashMap()    //无参构造方法
HashMap(int initialCapacity)  //指定初始容量的构造方法 
HashMap(int initialCapacity, float loadFactor) //指定初始容量和负载因子
HashMap(Map<? extends K,? extends V> m)  //指定集合,转化为HashMap

HashMap提供了四个构造方法,构造方法中 ,依靠第三个方法来执行的,但是前三个方法都没有进行数组的初始化操作,即使调用了构造方法此时存放HaspMap中数组元素的table表长度依旧为0 。在第四个构造方法中调用了inflateTable()方法完成了table的初始化操作,并将m中的元素添加到HashMap中。
2、添加方法

public V put(K key, V value) {
        if (table == EMPTY_TABLE) { //是否初始化
            inflateTable(threshold);
        }
        if (key == null) //放置在0号位置
            return putForNullKey(value);
        int hash = hash(key); //计算hash值
        int i = indexFor(hash, table.length);  //计算在Entry[]中的存储位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
 
        modCount++;
        addEntry(hash, key, value, i); //添加到Map中
        return null;
}

在该方法中,添加键值对时,首先进行table是否初始化的判断,如果没有进行初始化(分配空间,Entry[]数组的长度)。然后进行key是否为null的判断,如果key==null ,放置在Entry[]的0号位置。计算在Entry[]数组的存储位置,判断该位置上是否已有元素,如果已经有元素存在,则遍历该Entry[]数组位置上的单链表。判断key是否存在,如果key已经存在,则用新的value值,替换点旧的value值,并将旧的value值返回。如果key不存在于HashMap中,程序继续向下执行。将key-vlaue, 生成Entry实体,添加到HashMap中的Entry[]数组中。
3、addEntry()

/*
 * hash hash值
 * key 键值
 * value value值
 * bucketIndex Entry[]数组中的存储索引
 * / 
void addEntry(int hash, K key, V value, int bucketIndex) {
     if ((size >= threshold) && (null != table[bucketIndex])) {
         resize(2 * table.length); //扩容操作,将数据元素重新计算位置后放入newTable中,链表的顺序与之前的顺序相反
         hash = (null != key) ? hash(key) : 0;
         bucketIndex = indexFor(hash, table.length);
     }
 
    createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

添加到方法的具体操作,在添加之前先进行容量的判断,如果当前容量达到了阈值,并且需要存储到Entry[]数组中,先进性扩容操作,空充的容量为table长度的2倍。重新计算hash值,和数组存储的位置,扩容后的链表顺序与扩容前的链表顺序相反。然后将新添加的Entry实体存放到当前Entry[]位置链表的头部。在1.8之前,新插入的元素都是放在了链表的头部位置,但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的元素都放在了链表的尾部。

4、获取方法:get

public V get(Object key) {
     if (key == null)
         //返回table[0] 的value值
         return getForNullKey();
     Entry<K,V> entry = getEntry(key);
 
     return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
     if (size == 0) {
         return null;
     }
 
     int hash = (key == null) ? 0 : hash(key);
     for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
         Object k;
         if (e.hash == hash &&
             ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
      }
     return null;
}

在get方法中,首先计算hash值,然后调用indexFor()方法得到该key在table中的存储位置,得到该位置的单链表,遍历列表找到key和指定key内容相等的Entry,返回entry.value值。

5、删除方法

public V remove(Object key) {
     Entry<K,V> e = removeEntryForKey(key);
     return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
     if (size == 0) {
         return null;
     }
     int hash = (key == null) ? 0 : hash(key);
     int i = indexFor(hash, table.length);
     Entry<K,V> prev = table[i];
     Entry<K,V> e = prev;
 
     while (e != null) {
         Entry<K,V> next = e.next;
         Object k;
         if (e.hash == hash &&
             ((k = e.key) == key || (key != null && key.equals(k)))) {
             modCount++;
             size--;
             if (prev == e)
                 table[i] = next;
             else
                 prev.next = next;
             e.recordRemoval(this);
             return e;
         }
         prev = e;
         e = next;
    }
 
    return e;
}

删除操作,先计算指定key的hash值,然后计算出table中的存储位置,判断当前位置是否Entry实体存在,如果没有直接返回,若当前位置有Entry实体存在,则开始遍历列表。定义了三个Entry引用,分别为pre, e ,next。 在循环遍历的过程中,首先判断pre 和 e 是否相等,若相等表明,table的当前位置只有一个元素,直接将table[i] = next = null 。若形成了pre -> e -> next 的连接关系,判断e的key是否和指定的key 相等,若相等则让pre -> next ,e 失去引用。

6、containsKey

public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

containsKey方法是先计算hash然后使用hash和table.length取摸得到index值,遍历table[index]元素查找是否包含key相同的值。

7、containsValue

public boolean containsValue(Object value) {
    if (value == null)
            return containsNullValue();
 
    Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
    return false;
    }

JDK 1.8的 改变

1、HashMap采用数组+链表+红黑树实现。
在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,下面来看一下这些改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。
2、put方法简单解析:

public V put(K key, V value) {
    //调用putVal()方法完成
    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;
    //计算存储的索引位置,如果没有元素,直接赋值
    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);
                    //链表长度8,将链表转化为红黑树存储
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //key存在,直接覆盖
                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;
    //判断是否需要扩容
    if (++size > threshold)
        resize();
    //空操作
    afterNodeInsertion(evict);
    return null;
}

如果存在key节点,返回旧值,如果不存在则返回Null。

Collections类常用方法总结

  1. sort
    对集合进行排序
1 public static <T extends Comparable<? super T>> void sort(List<T> list)
2 
3 public static <T> void sort(List<T> list,Comparator<? super T> c)

在使用List时想根据List中存储对象的某一字段进行排序,那么我们要用到Collections.sort方法对list排序,用Collections.sort方法对list排序有两种方法:

  • 第一种是list中的对象实现Comparable接口;
  • 第二种方法是根据Collections.sort重载方法来实现。

示例如下:

public class SortTest {
    public static void main(String[] args) {
        List<String> listS = new ArrayList<String>();
        List<Employer1> list1 = new ArrayList<Employer1>();
        List<Employer2> list2 = new ArrayList<Employer2>();
        List<Employer3> list3 = new ArrayList<Employer3>();

        //一.将String类型的变量插入到listS中并排序
        //listS中的对象String 本身含有compareTo方法,所以可以直接调用sort方法,按自然顺序排序,即升序排序
        listS.add("5");
        listS.add("2");
        listS.add("9");
        Collections.sort(listS);

        //二.将Employer1类的对象插入到list1中并排序
        //将已创建的实现了Comparator接口的比较类MyCompare传入Collections的sort方法中即可实现依照MyCompare类中的比较规则。
        Employer1 a1 = new Employer1();
        Employer1 b1 = new Employer1();
        Employer1 c1 = new Employer1();
        a1.setName("a1");   a1.setAge(44);
        b1.setName("b1");   b1.setAge(55);
        c1.setName("b1");   c1.setAge(33);
        list1.add(a1);
        list1.add(b1);
        list1.add(c1);//Collections类的sort方法要求传入的第二个参数是一个已实现Comparator接口的比较器
        Collections.sort(list1, new MyCompare());

        //三.将Employer2类的对象插入到list2中并排序
        //其实原理和上面的二类似,只是没有单独创建MyCompare类,而是用匿名内部类来实现Comparator接口里面的具体比较。
        Employer2 a2 = new Employer2();
        Employer2 b2 = new Employer2();
        Employer2 c2 = new Employer2();
        a2.setName("a2");   a2.setAge(66);
        b2.setName("b2");   b2.setAge(33);
        c2.setName("b2");   c2.setAge(22);
        list2.add(a2);
        list2.add(b2);
        list2.add(c2); //Collections类的sort方法要求传入的第二个参数是一个已实现Comparator接口的比较器
        Collections.sort(list2,new Comparator<Employer2>(){
            @Override
            public int compare(Employer2 a2, Employer2 b2) {
                return a2.getOrder().compareTo(b2.getOrder());
            }

        });

        //四.将Employer3类的对象插入到list3中并排序
        //被排序的类Employer3实现了Comparable接口,在类Employer3中通过重载compareTo方法来实现具体的比较。
        Employer3 a3 = new Employer3();
        Employer3 b3 = new Employer3();
        Employer3 c3 = new Employer3();
        a3.setName("a3");   a3.setAge(77);
        b3.setName("b3");   b3.setAge(55);
        c3.setName("b3");   c3.setAge(99);
        list3.add(a3);
        list3.add(b3);
        list3.add(c3);
        Collections.sort(list3);//Collections类的sort方法要求传入的List中的对象是已实现Comparable接口的对象

        System.out.println(listS);
        System.out.println(list1);
        System.out.println(list3);
        System.out.println(list2);
    }
}
class Employer1{
    private String name;
    private Integer age;
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override//重载了Object类里的toString方法,使之可以按照我们要求的格式打印
    public String toString() {
        return "name is "+name+" age is "+ age;
    }
}
class MyCompare implements Comparator<Employer1> {
    @Override//重载了Comparator接口里面的compare方法实现具体的比较
    public int compare(Employer1 o1, Employer1 o2) {
        return o1.getAge().compareTo(o2.getAge());
    }
}
class Employer2{
    private String name;
    private Integer age;
    public void setName(String name) {
        this.name = name;
    }
    public Integer getOrder() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override//重载了Object类里的toString方法,使之可以按照我们要求的格式打印
    public String toString() {
        return "name is "+name+" age is "+age;
    }
}
class Employer3 implements Comparable<Employer3>{
    private String name;
    private Integer age;
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override//重载了Object类里的toString方法,使之可以按照我们要求的格式打印
    public String toString() {
        return "name is "+name+" age is "+age;
    }
    @Override//重载了Comparable接口里的compareTo方法来实现具体的比较
    public int compareTo(Employer3 a) {
        return this.age.compareTo(a.getAge());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值