Collection(集合)的interface(接口)and实现类+相关题型

1.java集合框架图

请添加图片描述

  • 在这个集合框架图中,虚线箭头代表的意思是接口、实线框代表的意思是实现类。在此,我重点介绍几个接口,比如:Collection接口,Map接口;

    1.1集合与数组的区别:

1:数组长度是固定的,而集合的长度是可变的;
2:数组可以是基本数据类型,也可以是引用数据类型,而集合仅仅只是引用数据类型;
3:数组只能存储同一类型的数据;而集合既可以存储同一类型的元素数据,也可以存储不同类型的数据元素。

1.2主要一些类的简介

  • Collectio接口:Collection接口的实现类的集合存储的都是单个数据,其子接口分别是List、Set、Quene;
    • List接口:存储的数据是可以 重复的、按照插入顺序存储;
    • Set接口:存储的元素是唯一的,不能重复的、数据是无序的;
    • Quene接口:数据是可以重复,也可以进行排序;
  • Map接口:该接口的实现类集合中存储的元素都是键值对存在

2.Collection接口

  • 该接口Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用。
  • Collect接口方法的介绍:
boolean add(E e);//确保此collection包含指定元素(可选操作);
boolean addAll(Collection<? extends E>c);//将指定collection中的所有元素都添加到此collection中(可选操作);
void clear();//移除此collection中的所有元素(可选操作)
boolean contains(Object o);//如果此collection包含指定的元素,则返回true
boolean contionsAll(Collection<?> c);//如果此collection包含指定的collection中所有的元素,则返回true
boolean equals(Object o);//比较此collection与指定的元素是否相等
int hashCode();//返回此collection的哈希码值
boolean isEmpty();//如果此collection不包含元素,则返回true
Iterator<E> iterator();//返回在此collection的元素上进行迭代的迭代器
boolean remove(Object o);//从此collection中移除指定元素的单个实例,如果存在的话(可选操作)
boolean removeAll(Collection<?> c);//移除此collection中包含指定collection中的所有元素(可选操作)
boolean retainAll(Collection<?> c);//仅保留此collection中那些也包含在指定collection中的元素(可选操作)
int size();//返回此collection中的元素数
<T> T[] toArray(T[] a);//返回包含此collection中所有元素的数组;返回数组运行时类型与指定数组的运行时类型相同
  • Collection 接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作。 定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。该接口的实现类的集合中存储的元素都是单个数据。Iterator接口时Collection接口的超级接口,通过Collection接口下的iterator方法返回Iterator方法:
    • Iterator方法:

      • boolean hasNext();//如果有元素仍可以迭代,就返回true;
      • E next();//返回迭代器的下一个元素;
      • void remove();//移除迭代器返回的最后一个元素;
    • 迭代器的简介:

      • 迭代器是行为型设计模式,提供了一种方法来遍历一个聚合的容器(集合)中的元素,而不用暴露其内部的表示;对于容器的访问而不需要关注容器内部的实现细节,可以使用迭代器,需要具备功能;
        • 1.能够遍历的访问一个聚合容器;
        • 2.不需要了解聚合容器的内部结构;
        • 3.能够提供多种不同的遍历方式;
  • 在Java中,需要使用的迭代器遍历的容器需要实现Iterator接口;Iterator接口的声明如下:
public interface Iterator<T>{//该接口声明了iterator方法,要使用迭代器类需要实现Iterator接口,即实现iterator方法,返回的是Iterator实现类
  Iterator<T> iterator();
//迭代器返回实例Iterator的实现类
public interface Iterator<E>{
	boolean hasNext();//判断集合中是否还有下一个元素;true:集合中有元素;false:集合中没有元素
	E next();//返回当前的一个元素,每调用一次集合元素会移动一位(hasNext和next需要一次循环调用)
	void remove();//删除容器元素
}

  • 迭代器访问容器demo:
ArrayList<Interger> a=new ArrayList<Interger>();
a.add(1);
a.add(2);
a.add(3);

//获取迭代器实例
Iterator<Integer> integer1=a.iterator();
//首先判断容器中是否还有元素hasNext
while(iterator1.hasNext()){
	//获取当前的元素next();
	Integer value=iterator.next();
	System.out.println(value);
}
  • ArrayList集合中使用数组作为存储数据结构,迭代器来遍历Array List本质上来遍历数组,arrayList中存储的数据个数size,假如当前索引index,ArrayList中迭代器的源码实现
//iterator()是Iterator接口提供的方法,而ArrayList是Iterable接口的实现类,因此具有iterator方法
public Iterator<E> iterator(){
	return new Itr();
}
//Itr类是Iterator接口的实现类
private class Itr implements Iterator<E>{
	int cursor;//返回下一个元素的位置
	int lastRet;//上一个位置,即cursor前一个位置
	int expectedModCount=modCount;
}
public boolean hasNext(){
	//判断索引位置不是size,即还没有到尾部
	return cursor!=size;
}
@SuppressWarnings("unchecked")
public E next(){
	//校验modCount属性
	checkForComodification();
	int i=cursor;
	if(i>=size)
		throw new NoSushElementException();
		Object[] elementData=ArrayList.this.elementData;
		if(i>=elementData.length)
		throw new ConcurrentModificationException();
		cursor=i+1;
		return (E)elementData[lastRet=i];
}
public void remove(){
	if(lastRet<0)
		throw new IllegalStateException();
		//校验modCount属性
		checkForComodification();
		try{
			//调用的是ArrayList中提供的删除方法
			ArrayList.this.remove(lastRet);
			cursor=lastRet;
			lastRet=-1;
			expectedModCount=modCount;
}catch{
	throw new ConcurrentModificationException();
}
}
final void checkForComodification(){
	if(modCount!=expectedMOdCount)
	throw new ConcurrentModificationException();
}

  • 如何在集合中自定义迭代器:
//自定义实现Iterator接口,才能具有iterator()方法
class DIYColletion implement Iterator{
	
	//获取迭代实例,获取的是Iterator的实例对象
	Iteroter<T>iterator(){
		return new DIYCollection();
}

class DIYIterator implament Iterator{
	boolean hasNext(){
		
	}
	E next(){}
		void remove(){
			to do something
		}
	}
}
  • 自定义的集合中实现迭代器的要点:

    • 1.自定义集合必须实现Iterabler接口;
    • 2.自定义一个迭代器的内部类,该类实现Iterator接口;
    • 3.迭代器的内部类分别实现了hasNext()、next()、remove()方法;
    • 4.自定义集合类中iterator()方法中实现创建迭代器的内部类实例;
  • 调用集合本身的remove操作(集合变更)会发生ConcurrentModificationExpecttion的异常,而调用迭代器中的remove操作不会发生异常,其根本原因在于迭代器中的变量expectedModCount和集合中的modCount的值不一致引起的;

  • expectModCount是迭代器的内部属性,在迭代器实例化时将当前的集合的modCount的值赋值给expectModCount,后续当前的next操作和remove操作都会检测expectModCount和modCount是否一致,当调用迭代器的remove操作,会引起modCount值发生变化,这个改变在迭代器的remove方法中同步给expectModCount让其保持一致,不会抛出异常,而直接调用集合本身的remove操作会引起modCount的修改,而修改是不会同步给expectModCount,会导致expectModCount和ModCount值不一致抛出异常。

  • 主要是考虑线程并发问题,一个线程对集合进行遍历的同时,另一个线程对集合做修改,集合对modCount等版本号做判断,如果不同,说明存在县城并发操作集合的问题,会直接抛出异常,不在进行后续处理,这个也是Fast-fair机制:快速失败机制;

  • ListIterator迭代器的特点:

    • ListIterator迭代器是Iterator的子接口,只适合用于List接口下的实现类,具有Iterator接口的方法,能够实现集合从前往后遍历;还具有其他特点:
      • 1.可以对集合实现从后往前遍历;
      • 2.提供特有方法:获取当前索引位置、修改集合元素、对集合添加元素。
public iterface ListIterator<E>extends Iterator<E>{
	boolean hasNext();
	E next();
	boolean hasPrevious();
	E previous();
	int nextIndex();
	int previousIndex();
	void remove();
	void set(E e);
	void add(E e);
}
`ArrayList arrayList=new ArrayList();
           arrayList.add(1);
           arrayList.add(2);
           arrayList.add(3);
           arrayList.add(4);        
           Iterator iterator=List.iterator();
           while(iterator.hasNext()){//判断是否还有下一个元素
         System.out.printtln(iterator,next);//输出下一个元素  
           }`

2.1 List接口

  • List接口用于定义线性表结构,存储的数据是可以重复的,它存储数据是按照插入顺序进行存储,类似于数组的存储方式(数组开辟的空间够用,无需考虑扩容等),只要时想对数据进行保存,就直接依次顺序插入即可。在这里当插入图片描述
  • 通过编辑器点进去我们就可以看见List接口它继承自Collection接口,它返回的都是该List接口中的一些元素。
  • 常用的操作就是插入和删除操作:
    • void add(int index,E element);//这个方法就是在指定的位置插入元素Element,在底层的数据中,它在第一步会判断开辟的存储空间是否够用,如果不够用就需要进行扩容,然后从扩容后的倒数第二个数据开始,依次将数据向后进行覆盖,最后在所要插入的位置插入想要插入的元素。
    • E remove(int index);//这个方法就是删除指定位置的元素,然后从删除位置的下一个元素开始,依次将每一个数据向前进行覆盖,最后再将数据元素的个数进行自减;然后将删除的元素进行返回。
    • List subList(int fromIndex;int toIndex)方法:该方法是为了获取子List;从这个方法就可以看出,这个方法获取子List时是从下标入手的,及从开始数据的下标开始到截至位置的下标结束。(但是值得注意的是,这个方法获取的子List数据它是一个前闭后开的形式:[BeginValue,endValue));

2.1.1ArrayList接口:

- ArrayList是一个动态修改的数组,它没有固定的大小,不需要自己分配空间,java本身已经初始化容量为10的大小,可以直接添加删除等一系列操作;
- 数据的插入是有序的,并且是可以重复的,可以存储null值,底层的数据结构是数组
- ArrayList底层封装数组,提供了丰富的API操作
ArrayList<Character> aList1=new ArrayList<Character>();
//相继添加单个元素
aList1.add('a');
aList1.add('b');
alist1.add('c');
aList1.add('d');
//批量添加元素
ArrayList<Character> aList2=new ArrayList<Character>();
aList2.addAll(aList1);
//删除指定位置的元素
aList1.remove(Character.valueOf('a'));
//获取集合中的元素个数
aList1.size();//获取aList中的元素个数
aList.size();//获取aList2中的元素个数
//获取元素
aList1.get(0);//获取aList的0号下标的元素
//删除集合中的所有元素
aList.clear();//删除aList中的所有元素
//查询集合中是否包含指定的元素
boolean b=aList1.contains('m');//查询aList中是否包含m这个元素
//判断当前集合是否为空
aList1.isEmpty();//判断aList1是否为空
//保留当前集合及参数集合相同的部分,删除当前集合的其他元素
boolean b1=aList1.retainAll(aList2);

//求交集,并集,差集
aList1.addAll(aList2);//求并集
aList.retainAll(aList2);//求交集
aList.removeAll(aList2);//求差集
  • ArrayList的源码实现:继承关系,构造函数,属性信息,默认值或默认属性,底层数据结构,扩容机制,常用方法研究。
    • 继承关系:
      • ArrayList继承自AbstractList,AbstractList类是抽象类,实现自List接口,对接口中通用的方法做了实现,子类可以不用实现,子类如果有特殊需求可以重写对应的方法;
      • ArrayList实现接口List,RandomAcces,Cloneablee,Serializable;
      • List接口是ArrayList,LinkedList的接口,定义了集合中的大部分方法;
      • RandomAccess接口表明当前类是可以被克隆;
      • Serialization接口表明当前类是可以支持序列化和反序列化;
    • 构造函数:
public ArrayList(int initialCapacity){//通过初始化容量参数来实例化
super();
//参数检验
	if(initialCapacity<0)
	{
	throw new IllegalArgumentExection("Illegal Caapacity:"+initialCapacity);
	//创建指定大小的数组实例
		this.elementData=new Object[initialCapacity];
	}
	//无参构造函数
	public ArrayList(){
		super();
		//给定空的数组
		this.elementData=EMPTY_ELEMENTDATA;
	}
	//通过饥饿和实例来实例化
	public ArrayList(Collection<? extends E>c){
		elementData=c.toArrray();
		size=elementData.length;
		if(elemeentDtat.getClass()!=Object[].class//完成数据拷贝
		elementData=Arrays.copyOf(elementData.size.Object[].class);
	}
}
  • 属性信息:
    • private transient Object[] elementData;//存储的元素位置底层存储在数组中;
    • private int size;//父类提供的属性,记录集合数据变更版本值,即进行新增、修改、删除等,与业务无关;
    • private int modcount;//修改版本号
  • 默认值或默认属性:
    • private static final int DEFAULT_CAPACITY=10;//默认的数组初始容量是10
    • private static final Object[] EMPTY_ELEMENTDATA={};//空数组实例
  • 底层数据结构:底层数据结构是数组
  • 扩容机制:
    • int newCapacity=oldCapacity+(oldCapacity>>1);//扩容大小按照原数组的1.5倍进行扩容;>>:表示的是右移,是按照二进制数进行操作,即除2操作;<<:左移,乘2操纵;
  • 常用方法研究:
    • add(E e);
      • public void add(int index,E element);添加数据,在指定位置上添加元素,需要保证index的合法性,并将index之后的数据后移一位,然后在插入新值。
public boolean add(E e){
	ensureCapacityInternal(size+1);//表示当数组存满之后,再往数组中增加数据时,就多开辟一个新的内存
	elementData[size++]=e;//将新增的数据插入到elementData的数组中
	return true;
}
private void ensureCapacityInternal(int minCapacity){
	//如果数组为空时,获取当前的**容量值**
	if(elementData==EMPTY_ELEMENTDATA){
		//当无参构造的实例时,第一次会进入该方法
		minCapacity=Math.max(DEFAULT_CAPACITY.minCapacity);
	}
	ensureExplcitCapacity(minCapacity);
}
private void eensureExplicitCapacity(int minCapacity){//Explicit:精确的,准确的
	modCount++;
	//数据空间不足时,考虑扩容
	if(minCapacity>elementData.length)
		grow(minCapacity);
}
privata 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);
		//创建新的指定大小的集合,将原有数据拷贝到新数组中
		elementData=Arrays.copyOf(elementData.newCapacity);
}
private static int hugeCapacity(int minCapacity){
	if(minCapacity<0){//溢出
		throw new OutOfMemoryError();
		return(minCapacity>MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
}

  • get (int index);
public E get(int index){
	//检查下标的合法性
	rangcheck(index);
	//通过下标指定位置来获取数据
	return elementData(index);
}
private void rangeCheck(int index){
	if(index>=size){
		throw new IndexOutOfBoundsException(outOfBoundsMag(index));
}
}
  • remove(Object o);//删除
  • remove(int index);//删除
public boolean remove(Object o){
	if(o==null){
		//元素为空时
		for(int index=0;index<size;index++){
			if(elementData(index)==null){
				fastRemove(index);
				return true;
			}
		}else{
			//不为空
			for(int index=0;index<size;index++){
				if(o.equals(elementData(index))){
						fastRemove(index);
					}
			}
			return false;
		}
}
}
//将index后续的数据向前移一位
private void fastRemove(int index){
	modCount++;
	int numMoved=size-index-1;
	if(numMoved>0){
	System.arraycopy(elementData,index+1,elementData.index,numMoved);
	elementData[--size]=null;
	}
}


  • ArrayList的引用场景:
    • 在查询操作的时间复杂度O(1),对于数据操作时间复杂度是O(n),ArrayList的适用于多查询、修改较少的业务场景。

2.1.2LinkedList接口

  • LinkedList是List接口的实现类,具有List接口提供的所有方法,并且还提供了一些额外的方法,LinkedList实现了Deque接口,提供的方法如下所示:
public interface Deque<E> extends Queue<E>{
	void addFirst(E e);//将元素添加在队列的第一个位置
	void addLast(E e);//将元素添加在队列中的最后一个位置
	boolean offerFirst(E e);//判断是否将元素添加在队列的第一个位置
	boolean offerLast(E e);//判断是否将元素添加在了队列的最后一个位置
	E removeFirst();//删除队列的第一个元素
	E removeLast();//删除队列的最后一个元素
	E pollFirst();//删除第一个元素
	E pollLast();//删除最后一个元素
	E getFirst();//获取第一个元素
	E getLast();//获取最后一个元素
	E peekFirst();//获取第一个元素
	E peekLast();//获取最后一个元素
	boolean removeFirstOccurrence(Object o);
	boolean removeLastOccurrence(Object o);
	boolean add(E e);
	boolean offer(E e);
	E remove();
	E poll();
	E element();
	E peek();
	void push(E e);
	E pop();
	boolean remove(Object o);
	boolean contains(Object o);
	public int size();
	Iterator<E>iterator();
	Iterator<E>descendingIterator();
}
  • LinkedLIst的基本特点:

    • LinkedList具有队列的特征,还可以作为队列和栈使用, 而且也是Queue接口下的实现类
    • 数组插入有序;
    • 数据可以重复
    • 可以存储null值;
    • 底层数据结构是链表;
  • LinkedList的方法的使用:
    - LinkedLsit是一个由节点构成的链表,每个都包含自己的节点信息并且能够指向下一个节点;
    - LinkedList的特点就是在找到需要插入或删除的节点的情况下,插入,删除所花费的代价是非常小的。但是对于get操作来说,LinkedList的查找效率就特别的低,因为查找某一个节点的时候就需要一个接一个的进行遍历(针对双向链表);

  • LinkedList 的源码分析:

    • 继承关系:
//1.LinkedList实现了双端队列接口Deque,因此具有双端队列的所有功能;
//2.实现了克隆接口Cloneable,因此具有克隆功能;
//3.实现了可序列化接口Serializable,因此具有可序列化功能;
//4.实现了队列List,因此具有队列功能;
//5.继承了双向链表抽象类AbstractSequentialList,因此具有双向链表的功能
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>,Deque<E>,Cloneable,java.io.Serializable
  • 构造函数
//无参构造函数。此时的双向链表的状态是:size=0,first=null,last=null
public LinkedList(){
throw new RuntimeException("Stub");
} 
//待机和参数的构造函数,先将该集合转换成相对用的数组,然后再将数组中的元素按照索引顺序一个一个的从双向链表的尾部插入到空双向链表中
public LinkedList(Collection<? extends E> c){
	throw new RuntimeException("Stub");
}
 
  • 属性信息

    • 1.transient int size=0;//大小等于0
    • 2.transient Nodefirst;//指向第一个节点
    • 3.transient Nodelast;//指向最后一个节点
    • 4.private static final long serialVersionUID=876323262645176354L;//序列号
  • 默认值或默认属性:无

  • 底层数据结构

    • LinkedList的底层使用了一个Node数据结构,有前后两个指针,双向链表实现的。相对数组来说,链表的插入效率是比较高的,只需要更改前后两个指针就行了,另外链表不存在扩容问题,应为链表不需要存储空间的连续,只是物理意义上的连续,每次插入数据时都只是改变last指针;另外,链表需要的内存比数组要更多,因为它要维护前后两个指针;它只适合删除,插入较多的场景LinkedList还实现了Deque接口。
public 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;
	}
}
transient Node<E> first;
transient Node<E> last;

public boolean add(E e){
//在最后面添加元素
linkLast(e);
return true;
}
void linkedList(E e){
	//获取添加前的最后一个元素
	final Node<E> l=last;
	//创建一个新的节点,并且将此节点之前的一个元素,新元素的传入
	//由于是最后一个元素,所以其后面的元素是空的,因此后一个元素为null
	final Node<E> newNode=new Node<>(l,e,null);
	//更新最后一个元素的引用
	last=newNode;
	if(l==null){
	first=newNode;//将新创建的节点赋值给first
}else{
l.next=newNode;
size++;//刷新总数
modCount++;
	}
}
//在first节点前面插入一个节点,插入完之后,还要更新first节点为新插入的节点,并且同时维持last节点的不变量
private void linkedFirst(E e){
	final Node<E> f=first;
	final Node<E> newNode=new Node<>(null,e,f);
	first=newNode;
	if(f==null){
		last=newNode;
}else{
f.prev=newNode;
size++;
modCount++;
	}
}
//查询的方法
public E get(int index){
	checkElementIndex(index);
	return node(index).item;
}
Node<E> node(int index){
	if(index<(size>>1)){
			Node<E> x=first;
			for(int i=0;i<index;i++){
				x=x.nextt;
				return x;
				}else{
				Node<E> x=last;
				for(int i=size-1;i>index;i--){
					x=x.prev;
					return x;
	}
}
	//删除操作
	E unlink(NOde<E> x){
		final E element=x.item;
		final Node<E>next =x.next;//获取指定节点的前驱
		final Node<E> prev=x.prev;//获取指定节点的后继

		if(prev==null){
			first=next;//如果前驱为null,说明此节点是头节点
}else{
prev.next=next;//前驱节点的后继节点指向当前节点的后继节点
x.prev=null;//当前节点的前驱置空
}
if(next==null){//如果当前节点的后继节点为null,说明此节点是尾节点
last=prev;
}else{
next.prev=prev;//当前节点的后继节点的前驱指向当前节点的前驱节点
x.next=null;
}
x.item=null;//当前节点的元素设置尾null,等待垃圾回收
size--;
modCount++;
return element;
				}			
		}
}
  • 扩容机制

    • 由于它的底层是用双向链表实现的,没有初始化大小,所以也就没有扩容机制。
  • 常用方法研究

    • add(E e);//向集合中添加一个元素;
<strong> LinkedList<String> linkedList=new LinkedList<String>();
linkedList.add("zhangsan");
  • add(int index,E element);//向集合index的位置上添加一个元素
<strong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.add(0,"zhangsan");
  • addAll(Collection<? extends E> c);//将某集合全部添加到另外一个集合
<stong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.add("猫");
linkedList.add("狗");
linkedList.add("鸟");

LinkedList<String> alllinkedList=new LinkedList<String>();
alllinkedList.addAll(linkedList);
  • addAll(int index,Collection<? extends E> c);//将某集合全部添加到另外一个集合的index处;
	<stong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.add("猫");
linkedList.add("狗");
linkedList.add("鸟");

LinkedList<String> alllinkedList=new LinkedList<String>();
alllinkedList.addAll(2,linkedList);
  • set(int index,E element);//修改index位置对应的Object
<strong>LinkedList<Strign> linkedList=new LinkedList<String>();
linkedList.set(0,"大象");
  • remove(int index);//移除此链表中指定位置上的元素
<strong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.remove(0);
  • remove(Object 0);//如果存在移除此列表中首次出现的指定元素
<strong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.remove("猫");
  • remove();//删除第一个元素
<strong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.add("猫");
linkedList.add("狗");
linkedList.add("猴子");

String result=linkedList.remove();

Iterator<String> iterator=linkededList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next()+" ");
}
  • get(int index);//获取index位置上的所对应的元素
<strong>LinkedList<String> linkedList=new LinkedList<String>();
String s=linkedList.get(0);
  • size();//集合的长度即元素的个数
<strong>LinkedList<String>linkedList=new LinkedList<String>();
int num=linkedList.size();
  • isEmpty();//判单是否为空
LinkedList<String> linkedList=new LinkedList<String>();
boolean a=linkedList.isEmpty();
  • contains(Object o);//集合中是否包含目标元素
LinkedList<String>linkedList=new LinkedList<String>();
linkedList.contains("猴");
  • indexOf(Object o);//集合中第一次出现目标元素的位置
linkedList.add("猫");
linkedList.add("狗");
linkedList.add("大象");
linkedList.add("猴子");
linkedList.add("猫");
linkedList.add("蛇");
linkedList.add("猴子");
linkedList.add("狗");

int a=linkedList.indexOf("猫");
int b=linkedList.indexOf("狗");
int c=linkedList.indexOf("鸟");

  • lastIndexOf(Object o);//目标元素最后一次出现的位置
linkedList.add("猫");
linkedList.add("狗");
linkedList.add("大象");
linkedList.add("猴子");
linkedList.add("猫");
linkedList.add("蛇");
linkedList.add("猴子");
linkedList.add("狗");

int a=linkedList.lastIndexOf("猫");
int b=linkedList.lastIndexOf("狗");
int c=linkedList.lastIndexOf("鸟");

  • clear();//清空集合中的所有元素
<strong>LinkedList<String> linkedList=new LinkedList<String>();
linkedList.add("猫");
linkedList.add("狗");
linkedList.add("猴子");
linkedList.add("猴子");
linkedList.add("猫");
linkedList.add("蛇");
linkedList.add("猴子");
linkedList.add("狗");

linkedList.clear();

Iterator<String> iterator=linkededList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next()+" ");

2.1.3vector类:

  • 由于Java中的数组只能保存固定数目的元素,且必须把所有需要的内存单元一次性的申请出来,而不能创建数组再加数组元素,为了解决这个问题,Java中就引入了向量类;
  • vector是STL中最常见的容器,它是一种顺序容器,支持随机访问。vector是一块连续分配的内存,从数据安排的角度来说,和数组非常相似,但是与数组不同的是:数组是静态分配空间,一旦分配了空间的大小,就不能再改变了;而vector是动态分配空间,随着元素的不断插入,他会按照自身的一套机制不断扩充自己的容量。
  • vector扩充是按照容器现在容量的一倍进行增长,Vector容器分配的是一块连续的内存空间,每次容器的增长,并不是在原有连续的空间内存空间后再进行简单的叠加,而是重新申请一块更大的新内存,并把现有容器中的元素逐个复制过去,然后再销毁旧的内存。这时原有指向旧内存空间的迭代器已经失效,所以当操作容器时,迭代器要及时更新。新内存的容量大小相比原有容量的增加一倍。
  • vector类的三种构造方法:
    • 1.Vector v1=new Vector();//构造一个空向量;
    • 2.Vector v2=new Vector(10);//指定初始存储量10
    • 3.Vector v3=new Vector(10,5);//指定初始存储量10和容量增量5;
  • 常用方法的研究:
    • 1.addElement(Object o);//尾部增加元素
    • 2.insertElement(“that”,1);//向指定位置添加元素
    • 3.setElemet(Object obj,int index);//指定位置对元素进行修改
    • 4.removeElement(Object obj);//该方法将删除向量序列中第一个与指定的obj对象相同的元素,同时将后面的元素向前移一个位置,补上位置,该方法返回的值类型是一个Boolean值;
    • 5.removeElement(int index);//该方法在指定位置删除一个元素;
    • 6.removeAllElements();//删除所有的元素
    • 7.Object elementAt(int index);//返回一个指定位置的元素,不过这个元素是一个Object类型的对象,在使用之前要进行强制类型转换;
    • 8.boolean coontains(Object obj);//检查向量序列中是否包含obj这个元素
    • 9.int indexOf(Object obj);//该方法返回obj序列中的下标
Vector v1=new Vector();
Vector v2=new Vector(10);
Vector v3=new Vector(10,5);
Vector<String>v4=new Vector();//拿到元素进行强转,转成String类型后才能使用。

v1.addElement(10);//添加到尾部
v1.insertElement(10,1);//将10添加到1号位置
v1.setElement(2,1);//将1号位置上的值修改成2
v1.removeElement(10);//删除10这个元素,返回值true:删除成功;返回值false,删除失败
v1.removeElementAt(1);//将1号位置上的元素删除
v1.removeAllElements()//将所有元素都删除;
String str=(String)v1.element(0);//将0号位置上的元素强制转换成String类型进行输出
System.out.println(v1.coontains(10));//检验向量序列中是否包含10这个元素
System.out.println(v1.indexOf(10));//返回10号元素的下标
  • vector数据结构:
    • vector数据结构采用的是连续的线性空间,属于线性存储。它采用3个迭代器_First,_last,_End指向分配来的线性空间的不同范围,_Fast指向使用空间的头部,_Last指向使用空间大小的尾部,_End指向使用空间容量的尾部。
template<class_Ty.class_A=allocator<_Ty>>
class vector{
...
protecteed;
iterator_first,_last,_End;
};



int data[6]={2,3,6,5,7,9};
vctor<int> vData(dada,data+6);
vData.push_back(6);

2.2 Set接口

  • Set接口是Collection接口的一个子接口,它有两个特点,一个是无序性,另一个是不可重复性。无序不等于说是有随机性,它的底层数据结构虽然是数组,但是存储的数据在底层数组当中并非按照数组索引的顺序添加元素,而是根据哈希值来添加数据元素,元素的存入和取出的顺序不一定一致;对于不可重复性而言,要保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个。set集合与Collection基本上相同的,它本身没有提供额外的方法。

2.2.1HashSet

  • HashSet的数据结构是哈希表;
  • Hash为每一个对象计算出一个整数称为哈希值,哈希表是按照哈希值来进行存储的。当我们添加元素的时候,哈希值是一样的,这时候会进行是否是同一对象的判断equals,如果不是同一个对象,那么会在当前对象下进行顺延。哈希表是一链接式列表的矩阵。每一个列表称为一个哈希表元(buckets);
  • HashSet保证元素的唯一性主要是通过元素的两个方法,分别是hashCode和equals方法来完成的。
    (1)先计算哈希值,如果元素的哈希值不同,就不会调用equals,添加成功
    (2)如果元素的hashCode值相同,才会判断equals是否为true,为true则表明添加失败。
    所以,当自定义对象的时候,一般情况下要复写hashCode和equals方法,因为自定义对象可能要存放到hashCode集合中,此外,复写hashCode要尽量保证哈希值的唯一性,一般根据判断条件生成。
    • 复写的原则:例如:当hashCode存自定义对象Person,当姓名和年龄一致,元素重复:
public static void main(String[] args){
HashSet<Person> hs=new HashSet<Person>();
hs.add(new Person("张三""12"));
hs.add(new Person("李四""13"));
hs.add(new Person("wang","14"));

Iterator<Person> irerator=hs.iterator();
while(iterator.hasNext()){
Person person=(Person)iterator.next();
sop(person.getName()+"::"+person.getAge());
	}
}
/**
1.重写了equals,没有重写hashCode方法,发现equals没有被调用,而且都存入成功,说明计算得到的是不同的哈希值。
2.鉴于需求,我们要去重写,我们需要覆盖hashCode方法,建立自己的哈希值。
3.哈希值的生成根据判断条件
4.重写hashCode方法,去重成功了,而且equals方法运行了。
*/
calss Person{
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public boolean equals(Object obj){
if(Obj instanceOf Person){
return false;
Person=Person(obj);
System.out.println(this.name"..."+this.age);
return this.name.equals(p.getName())&&this.age==p.getAge();
}
public int hashCode(){
System.out.println(this.name+"...hashCode");
//return 60;会有很多重复比较
return this.name.hashCode()+this.age*3;
			}
		}
}
  1. HashSet的特点:

    • 1.集合中存储的元素是无序的;
    • 2.集合中的元素可以是null;
    • 3.HashSet不是同步的,如果多个线程同时访问一个set,只要有一个线程修改了set中的值,就必须进行同步处理,通常通过同步封装set对象来完成同步,如果不存在这样的对象,可以使用Collection.synchronizedSet()方法来实现。
    • 4.和数组相比,为什么要HashSet:首先,数组的索引是连续的并且数组的长度是固定的无法自由增加数组的长度 ,而HashSet中HashCode表用的hashset值来计算其存储位置,这样就可以自由增加HashCode的长度,并根据元素的HashCode值来访问元素,而不需要一个个的去遍历索引去访问,这就导致它比数组快。
  2. LinkedHashSet类:
    LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是可以插入的顺序来保存的,也就是说当遍历集合LinkedHashSet集合中的元素时,集合将会按照元素添加的顺序来访问集合中的元素。输出集合里的元素的时候,元素顺序总是与添加顺序一致,但是LinkedHashSet依然是HashSet,因此它不允许集合重复。

  3. HashSet的源码实现:
    源码研究思路
    1、继承关系:
    可以看出,TreeSet继承了AbstractSet接口,并且实现了NavigableSet,Cloneable,java.io.Serializable接口;

可以看出

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

  TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<>());
    }
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }
    public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }
    public Iterator<E> descendingIterator() {
        return m.descendingKeySet().iterator();
    }
    public NavigableSet<E> descendingSet() {
        return new TreeSet<>(m.descendingMap());
    }
  • 常用方法:
 TreeSet(NavigableMap<E,Object> m) {
       this.m = m;
   }
   public TreeSet() {
       this(new TreeMap<>());
   }
   public TreeSet(Comparator<? super E> comparator) {
       this(new TreeMap<>(comparator));
   }
   public TreeSet(Collection<? extends E> c) {
       this();
       addAll(c);
   }
   public TreeSet(SortedSet<E> s) {
       this(s.comparator());
       addAll(s);
   }
   public Iterator<E> iterator() {
       return m.navigableKeySet().iterator();
   }
   public Iterator<E> descendingIterator() {
       return m.descendingKeySet().iterator();
   }
   public NavigableSet<E> descendingSet() {
       return new TreeSet<>(m.descendingMap());
   }

2.2.2TreeSet类:

TreeSet可以确保集合中元素处于排序状态。

  • 内部存储机制:
    • TreeSet内部实现的是红黑树,默认整形排序为从小到大,与HashSet集合相比,TreeSet还提供了及格额外的方法:
//如果TreeSet采用了定制顺序,则方法返回定制顺序所使用的Comparator,如果TreeSet采用自然排序,则返回null;
Comparator comparator();
//返回集合中的第一个元素;
Object first();
//返回集合中的第一个元素
Object last();
//返回指定元素之前的元素
Object lower(Object e);
//返回指定元素之后的元素
Object higher(Object e);
//返回Set的子集合,含头不含尾
SortedSet subSet(Object fromEkement,Object toElement);
//返回此Set的子集,由小于toElement的元素组成
SortSet headSet(Object toElement);
//返回此Set的子集,由大于fromElement的元素组成
SortSet tailSet(Object fromElement);
  • TreeSet针对两种排序算法:自然排序和定制排序,在默认情况下,采用的是自然排序;
    • 自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合按照升序排列。
    • 定制排序:TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列,如果需要定制排序,则可以通过Comparator接口的帮助,该接口里包含一个int Comare(T t1,T t2);方法,用于比较t1和t2的大小。由于Comparator是一个函数式接口,因此还可以使用Lambda表达式来代替Comparator子类对象。

2.3Queue接口

  • 队列是一种比较特殊的线性结构,它只允许在表的前端进行删除操作,而在表的后端,进行插入操作。进行插入操作的端称为队尾,进行删除操作的端成为队头,队列中最先插入的元素也将最先被删除,对应最后插入的元素将最后删除,因此队列又称为先进先出的线性表,与栈的特性刚好相反。
public interface Queue<E> extends Collection<E> {
    boolean add(E e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
}

3.Map接口

  • Map接口:存储的方式是以键值对的形式进行存储,Key—Value值存在,key值是不可以重复的,而Value值是可以重复的。
  • Map接口的定义:
package java.util;
//Map接口位于java.util包中
public interface Map<K,V>{}
  • 内部接口;
abstract K getKey();//获得键
abstract V getValue();//获得值
abstract V setValue(V value);//设置值
boolean equals(Object o);//判断与另一个对象是否相等
abstract int hashCode();//获取哈希值
static <K,V extends Comparable<? super <K>,V>Comarator<Map.Entry<K,V>>comparingByKey();//根据键比较大小,要求键的类型继承Compparable
static <K,V extends Comparable<? super V>>Comparator<Map.Entry<K,V>>comparingByValue();//根据值比较大小,要求值的类型继承Comparalbe
static <K,V>Comparator<Map.Entry<K,V>>comparingByKey(Comparator<? super K> cmp);//自定义比较器,根据键比较大小
static <K,V>Comparator<Map.Entry<K,V>>comparingByValue(Coomparator<? super V>cmp);//自定义比较器,根据值比较大小


  • Map接口的抽象方法:
void clear();//从此映射中移除所有的映射关系(可选操作)
boolean containsKey(Object key);//如果此映射包含指定键的映射关系,则返回true;
boolean containsKey(Object value);//如果此映射将一个或多个键映射到指定值,则返回true
Set<Map,Entry<K,V>> entry Set();//返回此映射中包含的映射关系的Set视图
boolean equals(Object o);//比较指定对象与此映射是否相等
V get(Object key);//返回指定键所映射的值,如果此映射不包含改键的映射关系,则返回null
int hashCode();//返回此映射的哈希码值
boolean isEmpty();//如果此映射未包含键-值的映射关系,则返回true
Set<K> keySet();//返回此映射中包含的键的Set视图
V put(K key,V value);//将指定的值与此映射中的指定键关联(可选操作)
void putAll(Map<? extends K,? extends V> m);//从指定映射中将所有映射关系复制到此映射中(可选操作)
V remove(Object key);//如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
int size();//返回此映射中键-值映射关系数
Collection<V> value();//返回此映射中包含的值的Collection视图

  • 默认实现方法:基于JDK1.8在Map中实现了一些方法:

default V getOrDefault(Object key,V defaultValue);//通过键获取一个值,如果不存在设置默认值defaultValue
default void forEach(BiFunction<? super K,? super V>action);//遍历Map,通过函数式接口的lambda表达式实现遍历操作,如打印,求和等;
default void replaceAll(BiFunction<? super K,? super V,? extends V>function);//替换所有的键值对
default V putlfAbsent(K key,V value);//如果不存在键值对就添加键值对否则就不添加
default boolean remove(Object key,Object value);//移除键值对,键值对必须全部匹配,否则失败
default  boolean replacea(K key,V oldValue,V newValue);//类似于乐观锁,如果根据键值得到的键值和期望的就7值oldValue相等,就替换为新值,否则就失败
default V replace(K key,V value);//替换已经存在的键值对
default V computelfAbsent(K key,Function<? super K,? extends V>mappingFunction);//如果对应键不存在键值对,则使用自定义lambda[Function<K,V>]指定键返回值,并且存入Map;如果返回null;,就不放入Map,并且返回null
default V computelfPersent(K key,BiFunction<? super K,? super V,? extends V>remappingFunction);//如果对应键存在键值对,使用自定义lambda[BiFunction<T,U,R>],根据key和U生成键值,并将键值对放入Map;如果生成新值null,如果不存在键值对,返回null;如果生成对应值为null,会将key对应键值对删除
default V compute(K key,BiFunction<? super K,? super V,? extends V>remappingFunction);//根据键key获得旧值,自定义lambda[BiFunction<T,U,R>],通过key和旧值生成新值将新键值对放入Map;如果生成新值为null,并且旧值存在则删除旧的键值对,旧的键值对不存在就返回null
default V merge(K key,V value,BiFunction<? super V,? super V,?extends V>remappingFunction);//如果存在旧的键值对,设置新值为新的键值对,并且返回新值(value);如果旧的键值对不存在,自定义lambda[BiFunction<T,U,R>],根据旧值和value生成新值,新值不为null就将键值对放入Map,新的键值为null就删除旧的键值对
  • 为什么要有接口默认方法:因为业务需要我们需要修改项目中的接口,在JDK1.8之前,修改了接口必须一个一个实现类去修改,不然会产生编译错误。而某些实现类并不需要实现这个方法,但是还是要写一个空实现。自JDK1.8之后,接口中支持有default默认方法后,所以,默认接口方法就是为了解决这个问题,只要一个接口添加了一个默认方法,所有的实现类就自动继承,不需要改动任何继承类,也不会影响业务。

  • 为什么要有接口静态方法:接口静态方法和默认方法类似,只是接口静态方法不可以被接口实现类重写,接口静态方法只可以直接通过静态方法所在的接口名。静态方法名类调用。

  • 默认方法冲突问题->继承的多个接口有相同的默认方法:因为接口默认方法可以继承并重写,如果继承多个接口都存在相同的默认方法,那就存在冲突问题。

  • Map接口下集合的遍历形式:

//通过entrySet遍历键值对
Iterator<Map.Entry<String,String>> Iterator=hashMap.entrySet().iterator();
while(iterator.hashNext()){
Map.Entry<String,String >entry=iterator.next();
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue()+" ");

}
//通过keySet遍历键
Iterator<String> iterator1=hashMap.keySet().iterator();
while(iterator1.hashNext()){
String key1=iterator1.next();
System.out.print(key1+ " ");

}


//通过values()遍历值
Iterator<String>iterator2=hashMap.values().iterator();
while(iterator2.hasNext()){
String value=iterator2.next();
System.out.print(value+ " ");

}

3.1HashMap

  • HashMap的内部数据结构:
    • 底层的数据结构是用的哈希表,主要是数组与链表结合在一起来存储数据,JDK1.8版本的,内部使用的是数组与链表红黑树;
    • HashMap的数据插入原理(以链表为主):在这里插入图片描述常用的方法是put(K key,V value);
    • 1.HashMap插入元素使用的方法是put(K key,V value);这个方法底层的方法使用的是泛型;这个方法首先是对table数组进行判空操作,并对table数组进行初始化,接下来是对Key进行判空操作,当整个数组中都没有找到Key的时候,key为空将哈希位置的0号数组下标位置,并且遍历0号卡槽后面的链表上key为空是否存在,如果存在的话就将entry更新返回旧的value值,不存在则建立entry实体,存放put的数据。下来就是对key不为空的操作,通过寻找key来找到对应的存放卡槽的位置,对key进行hash,通过key的哈希值找到在哈希表中的位置,并让键的哈希值与表长进行按位与操作,这样做的目的就是为了保证它找到的位置的下标永远比表长小,下来就对table数组进行遍历entry键值对,通过遍历去找key是否存在如果i位置的哈希值与key的哈希值相等并且需要找的key与想找的键相等,就进行值得更新。接下来就是当存放的数据size大于扩容阈值的时候,就对表进行二倍扩容,然后再对所有的数据重新进行hash,将所有的值以头插法把所有的数据插入到新创建的数组当中去。
public V put(K key,V value){
	if(table==EMPTY_TABLE){//当table数组为null时,进入初始化table数据,当第一次调用put操作会进入
		inflateTable(threshold);
}
//key为null,将数据存放在0号卡槽的位置
if(key==null){
return putForNullKey(value);
//key不为null时进行的操作,通过key找到卡槽的位置
int hash=hash(key);
int i=indexFor(hash,table.length);

for(Entry<K,V> e)=table[i];e!=null;e=e.next){
//通过位置i找到卡槽i 的位置的数据链表,从头遍历,找key是否存在,判断条件是hash和key,相等值更新
Object k;
if(e.hash==hash&&((k=e.key)==key||key.equals(k))){//这个地方的(k=e.key)==key||key.equals(k)在不需要判空的时候需要,不需要判空的时候其实实际上就需要equals就行了,(k=e.key)==key)这个条件就是一个判空的条件
V oldValue=e.value;
e.value=valuee;
e.recordAccess(this);
return oldValue;
			}
		}
		//key在i位置不存在,需要新增entry节点存放数据
		modCount++;
		addEntry(hash,key,value,i);
		return null;
		}
}

private V putForNullKey(V value){
//key为null将哈希位置为0号位置需要遍历0号卡槽,判断key为null是否存在,存在九江entry中value进行更新,返回旧的value中,不存在则建立新的entry实体,存放put的数据
for(Entry<K,V> e=table[0];e!=null;e=e.next){
if(e.key==null){
V oldValue=e.value;
e.value=value;
e.recordAccess(this);
return oldValue;
		}
	}
	modCount++;
	addEntry(0,null,value,0);
	retturn null;
}
//通过key哈希找到对应卡槽
final int hash(Object k){
int h=hashSeed;
if(0=h&&k instanceof String){
return sun.misc.Hashing,stringHash32(String k);
}
h^=k.hachCode();
h^=(h>>>20)^(h>>>12);//这个叫扰动函数,这么设计就是为了尽可能的降低hash碰撞,越分散越好,算法一定要高效,因为是高频操作,因此采用位运算
return h^(h>>>7)^(h>>>4);//这儿的几个数据原因是因为可能是jdk编码人员经过大量的实验得出的结果,这样就可降低哈希冲突的概率

}
//通过key的哈希值找到在哈希表中的位置
static int indexFor(int h,int length){
return h&(length-1);//这儿进行按位与操作的目的是为了让存储数据的容量在表厂范围之内

}
void addEntry(int hash, K key,V value,int bucketIndex){
//当存放数据size大于扩容阈值threshold时,进行扩容
if((size>=threshold)&&(null!=table[bucketIndex]){
//对HashMap进行2倍扩容
resize(2*table.length);
hash=(null!=key)? hash(key) :0;
//获取当前key应该插入的新卡槽位置
bucketIndex=indeexFor(hash,table.length);
}
treatEntry(hash,key,value,bucketIndex);
}
//扩容
void resize(int newCapacity){
Entry[] oldTable=table;
int olCapacity=oldTable.length;
if(oldCapacity=MAX_CAPACITY){
threshold=Integer.MAX_VALUE;
return;
	}
//新创建的数组为原来的2倍
Entry[] newTable=new Entry[newCapacity];
//将元Map中每一个entry实体进行重新哈希到新的map中
transfer(newTable,initHashSeedAsNeeded(new Capacity));
table=newTable;
threshold=(int)Map.min(newCapacity*loadFactor,MAXIMUM_CAPACITY)}
}
//采用头插法将数据插入
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++;
}
  • HashMap的特点

      	2.1:当存放的数据大于表长时,进行二倍扩容;
      	
      	2.2:创建出新数组的时候,对所有的数据hash时使用的是头插法;
      	
      	2.3:加载因子的默认值是0.75;
      	
      	2.4:HashMap的初始容量是16(2的4次方);最大容量是2的30次方;
      	
      	2.5:当数据存储的容量达到2的30次方后,就不再进行扩容,因为最大的容量值也就是2的30次方;
      	
      	2.6:底层存储数据的方式使用的是链地址法进行操纵;
      	
      	2.7:HashMap继承了Serializable接口,是可以序列化的,线程不安全;
      	
      	2.8:HashMap底层主要是基于数组和链表实现的;
      	
      	2.9:HashMap主要通过hashCode和Key来计算哈希值的;
      	
      	2.10:底层存储的数据是无序的,在存储过程中会将原有数据的顺序打乱;
      	
      	2.11:HashMap遍历数据是通过Iterator迭代器遍历Set集合实现的 ;
      	
      	2.12:Key可以存储null值;value也可以为null;
      	
      	2.13:key是唯一的,不能重复的;
    
  • 基于JDK1.8来说:它基本上与上述的过程大致一样,只不过插入节点时红黑树节点,在证明链表已经转成了红黑树,新插入的直接节点直接插入到树中;

    • 在扩容的时候,1.8版本的采用了更简单的判断逻辑,位置不变或索引+旧容量大小;
    • 再插入数据的时候,1.7先判断是否需要扩容,然后再插入;1.8版本的是先进行插入,插完后再判断是否需要扩容。
    • 其次在防止hash冲突的时候,链表的长度过长,就会导致时间复杂度由O(n)降为O(logn);
    • 1.7版本下进行头插法进行扩容时,头插法会使链表发生反转,多线程环境下会产生环;
  • 获取元素

public V get(Object key){
//key为null,知道0号卡槽位置获取
if(key==null{
return getForNullKey();
//key不为null
Entry<K,V> entry=getEntry(key);
return null=rentry?null:entry.getvalue();
	}
}

private V grtForNullKey(){
//如果map为null,返回null
if(size==0){
renturn null;
}
//在0号卡槽位置,对链表遍历,查找key为null是否存在,存在则找entry中value返回,否则返回null
for(Entry<K,V> e=table[0];e!=null;e=e.next){
if(e.key==null){
return .value;
		}
	return null;
	}
}
//通过key找到对应的entry实例
final Entry<K,V>getEntry(Object key){
if(size==0){
return null;
}
//通过key的哈希找到key对应的卡槽位置
int hash=(key==null)?0:hash(key);
for(Entry<K,V> e =table[indexFor(hash,table.lenth)];e!=null;e=e.next){
Object k;
if(e.hash==hash&&(k=e.key)==key||(key!=null&&key.equals(k))){
return e;
		}
}
return null;
}
//查询过程:
	1.如果key为nill,在0号卡槽位置遍历链表查询,key存在则返回entry的value,否则返回null2.如果key不为null,对key进行hash,找到对用的卡槽,遍历链表,判断可以是否存在(hash,key,equals),返回entry的value否则返回null;
  • 删除元素 remove(key); 通过key删除可能存在的entry实体
public V remove(Object key){
Entry<K,V> e=removeEntryForKey(key);
return (e==null?null:e.value);
}
final Entry<K,V> removeEntryForKey(Object key){
//如果集合为null,直接返回null
if(size==0){
return null;
}
}
//通过节点及解决单向链表的删除问题,解决思路,给定两个指针,两指针一前一后,前指针表示要删除的节点,
//通过后一个指针来将节点和前节点指针的next建立联系

while (e!=null){
Entry<K,V>next=e.next;
Object k;
if(e.hash==hash&&(k=e.key)==key||(key!=null&&k.equals(k))){
modCound++;
size--;
//如果删除的是头节点,将后一个节点组为当前卡槽头节点
if(prev===e) table[i]=next;
else
//后一个指针prex来将节点和前节点指针的next建立联系
prev.next=next;
e.recrdRemove(this);
return 0;
}
prev=e;
e=next;
}
return e;
}
  • 删除过程:
    1.通过key哈希来找到卡槽位置(key为nunll在0号卡槽)
    2.对应卡槽的链表惊醒便利查找,给定两个指针一前一后,前指针找到删除节点,后指针建立和下一个节点关系。

  • LinkedHashMap的特点:

    • 1.LinkedhashMap继承了HashMap并实现了Map接口,内部对数据进行存储也是数组+链表,只不过链表是双向链表;
  • 2.多了Entry<K,V>,before,after用于双向链表的前驱和后记节点;

  • 3.LiinkedHashmap允许Key和Value都wei空;

  • 4.LinkedHashMap的key是可以被覆盖的;

  • 5.LinkedhashMap是有序的;

  • 6.默认的初始容量是16;

  • 7.提供了AccessOrder参数,用来指定LinkedHashMap的排序方式,accessOrder=false:插入顺序进行排序;accessOrder=true:访问顺序进行排序;

集合中接口与类的特点

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在炮火中前进

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值