day-2-4-2

Collection接口

无序 允许重复

public interface Collection<E> extends Iterable<E>

方法
  • add新增
  • remove删除
  • contains判断集合中是否有指定元素
  • clear清空集合
  • size元素个数
  • iterator获取迭代器,通过迭代器遍历集合中的所有元素
  • toArray转换为等长的数组
如何判断删除的元素相等

定义equals方法可以依赖于IDE工具自动生成

@Override
public boolean equals(Object obj) {
	// 用户自定义的比较规则
	if (this == obj)
		return true;
	if (obj == null)  //当前对象不可能为null,否则空指针异常
		return false;
	if (getClass() != obj.getClass())  // 类型判断。一个类只能加载一次
		return false;
	A1 other = (A1) obj;
	//调用Objects工具类中的方法进行相等判断
	 public static boolean equals(Object a, Object b) {
     return (a == b) || (a != null && a.equals(b));
    }
	return Objects.equals(id, other.id) && Objects.equals(name, other.name);
}
==和equals

==比较的是对象的引用值

equals用户自定义比较规则。如果没有自定义equals方法,则从Object类中继承得到equals方法

public boolean equals(Object obj) {
 	return (this == obj);
}
package com.yan1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

public class Test1 {
	public static void main(String[] args) {
		Collection cc = new ArrayList();
		A1 aa = new A1();
		cc.add(aa);
		cc.add(aa);
		System.out.println(cc.size());

		System.out.println(cc.remove(new A1()));
		System.out.println(cc.size());
	}
}

class A1 {
	private Long id;
	private String name;

	

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

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

	@Override
	public boolean equals(Object obj) {
		// 用户自定义的比较规则
		if (this == obj)
			return true;
		if (obj == null)  //当前对象不可能为null,否则空指针异常
			return false;
		if (getClass() != obj.getClass())  // 类型判断。一个类只能加载一次
			return false;
		A1 other = (A1) obj;
		//调用Objects工具类中的方法进行相等判断
		/* public static boolean equals(Object a, Object b) {
         return (a == b) || (a != null && a.equals(b));
        }
        */
		return Objects.equals(id, other.id) && Objects.equals(name, other.name);
	}

}

List接口

有序 允许重复

public interface List<E> extends Collection<E>

继承自Collection接口的方法
  • 继承自Collection接口的方法
  • boolean add(E e);向集合末尾追加元素e对象
  • boolean remove(Object obj)删除第一个和obj相等的元素,如果没有和obj相等元素,则报异常IndexOutOfBoundsException
List接口中的特殊方法
  • void add(int index, E element); 向指定索引位置index上添加元素element,原始数据自动后移
  • E get(int index); 获取指定索引号对应的元素,index应该在[0,size-1]
  • E set(int index, E element); 用于给指定索引位置上进行赋值,这个位置上必须有对应的数据(已经赋过值),这里实际上是修改操作,否则IndexOutOfBoundsException
  • E remove(int index);删除指定位置的元素,可以返回原始位置上存储的元素
  • int indexOf(Object o); 查找从左向右第一个o元素的下标索引,如果元素不存在返回-1
  • int lastIndexOf(Object o);从右向左查找

对象相等判定使用的是equals方法

  • sort方法按照自定义比较器对集合中的所有元素进行排序,默认自然序
default void sort(Comparator<? super E> c) {
     Object[] a = this.toArray();  //将List集合对象转换为数组
     Arrays.sort(a, (Comparator) c);  //调用Arrays工具类中的排序方法对数组进行排序
     ListIterator<E> i = this.listIterator();  //获取List集合对象中特殊的Iterator迭代器对象
     for (Object e : a) {  //foreach结构遍历数组中的所有元素
        i.next();
        i.set((E) e); //修改集合List中迭代器指定的当前位置上的元素
     }
}
list.sort(new Comparator<A2>() {
		public int compare(A2 o1, A2 o2) {
			// 自定义比较规则,按照id从小到大,如果id相等则按照name从大到小
			int res = o1.getId().compareTo(o2.getId());
			if (res == 0)
				res = (o1.getName().compareTo(o2.getName())) * -1;
			return res;
		}
	});

调用方法时要求传入参数为自定义比较器

对应的实现类
  •  ArrayList底层实现为数组,线程不安全
    
  •  Vector底层实现为数组,线程安全  synchronized
    
  •  LinkedList底层实现为链表,线程不安全
    
Set集合

无序 不允许重复

public interface Set<E> extends Collection<E>

没有新方法

如果进行对象相等比较:
  • 首先调用当前对象所属类中的hashCode方法获取当前对象的hashCode值
  • 按照hashCode值进行比较
    • 如果hashCode值不相等,则不会调用equals方法,直接得出结论两个对象不相等
    • 如果hashCode值相等,才调用equals方法进行进一步判断
    • 如果equals为真则判断两个对象相等
  • 潜规则:
    • java要求当两个对象的equals为true时,要求两个对象的hashCode值相等。
    • hashCode值相等并不一定equals为true
实现类:
  • HashSet
  • TreeSet
  • LinkedHashSet
package com.yan3;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class Test1 {
	public static void main(String[] args) {
		Set set = new HashSet();
		set.add(new A1(99L, "zhangsan"));
		set.add(new A1(88L, "lisi"));
		set.add(new A1(99L, "wangwu"));
		set.forEach(System.out::println);
	}
}

class A1 {
	private Long id;
	private String name;
	@Override
	public int hashCode() {
		System.out.println(this+"::hashcode()");
		return id.hashCode();
	}
	//比较规则为:按照id进行比较,如果id相等则对象相等
	public boolean equals(Object obj) {
		System.out.println(this+"::equals()");
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		A1 other = (A1) obj;
		return Objects.equals(id, other.id);
	}

	public A1(Long id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	@Override
	public String toString() {
		return "A1 [id=" + id + ", name=" + name + "]";
	}

}

常见的List接口的实现类

  • ArrayList:数组实现,查询快,增删慢,轻量级;(线程不安全)
  • LinkedList:双向链表实现,增删快,查询慢 (线程不安全)
  • Vector:数组实现,重量级 (线程安全、使用少)
ArrayList的使用和实现

List list=new ArrayList();

public class ArrayList<E> extends AbstractList<E> 通过继承抽象类可以共享所有公共方法

  • implements List, 实现List接口

  • RandomAccess, 实现随机访问接口

  • Cloneable, 实现克隆接口

  • java.io.Serializable 实现序列化接口

  • transient Object[] elementData; 真是存放数据的数组

  • private int size; 当前集合中存储的元素个数

构造器
 public ArrayList() {//针对存储数据的数组进行初始化操作
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
	//常量定义为DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};空数组
	//如果使用无参构造器,则不会直接创建数组,而是采用空数组,当第一次添加元素时才创建数组
	//使用无参构造器时ArrayList会构建一个空数组用于未来存放数据,这里是一种对内存消耗的优化处理
}
带参构造器

List list=new ArrayList(18) 18就是初始化容积,这里的初始化参数值必须为[0,int的最大值)

public ArrayList(int initialCapacity) {  //参数为初始化容积
      if (initialCapacity > 0) {  如果初始化容积大于0,则按照指定的容积创建数组
          this.elementData = new Object[initialCapacity];
      } else if (initialCapacity == 0) {
          this.elementData = EMPTY_ELEMENTDATA;  空数组,长度为0的数组
      } else {  //初始化容积值小于0则报异常
          throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
      }
}
add方法的定义[重点]

protected transient int modCount = 0;

public boolean add(E e) {
    modCount++; 用于统计当前集合对象的修改次数   
    add(e, elementData, size);  参数1:新增元素;参数2:存储数据的数组;参数3:当前集合中存储的元素个数
    return true;   返回true表示添加成功
}
private void add(E e, Object[] elementData, int s) {
	//如果数组已经存满了,则首先进行扩容处理
    if (s == elementData.length)  判断当前集合中存储的元素个数是否和数组长度相等
        elementData = grow();   如果相等则进行扩容处理
    elementData[s] = e; 在数组的指定位置存储元素
    size = s + 1;  集合中所存储的元素个数
        	private Object[] grow(int minCapacity) {
         //调用Arrays工具类中的方法进行数组的拷贝,同时调用newCapacity方法计算所需要的新容积值
        return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));  
    }
}
private Object[] grow() {  //扩容处理方法
    return grow(size + 1);  //继续调用其它扩容方法,参数为 当前存储个元素个数+1---最小容积
}
private Object[] grow(int minCapacity) {
     //调用Arrays工具类中的方法进行数组的拷贝,同时调用newCapacity方法计算所需要的新容积值
    return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));  
}
private static final int DEFAULT_CAPACITY = 10;
private int newCapacity(int minCapacity) {  计算所需要的新容积值
     int oldCapacity = elementData.length;  计算原来数组的长度
     //计算新容积值
     int newCapacity = oldCapacity + (oldCapacity >> 1); 相当于是老容积增加50%,这就是扩容比
     //计算出的新容积值是否满足所需要的最小容积值
     if (newCapacity - minCapacity <= 0) {
     //如果存储数据的数组为初始化时的空数组,则计算默认初始化容积10和所需要最小容积值则最大值
         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
             return Math.max(DEFAULT_CAPACITY, minCapacity);
         if (minCapacity < 0) // OOM内存溢出
             throw new OutOfMemoryError();
         return minCapacity;
     }
     //如果计算出的新容积值大于所需要的最小容积值
     return(newCapacity - MAX_ARRAY_SIZE <= 0)?newCapacity:hugeCapacity(minCapacity);
     //如果计算出的新容积值小于最大允许的容积值,则返回计算出的新容积
     (minCapacity > MAX_ARRAY_SIZE)? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
     //如果计算出的新容积值大于最大允许的容积值并且minCapacity大于MAX_ARRAY_SIZE,则返回最大整数值,否则返回最大允许的容积值
}
//数组长度的上限值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
结论:
  • 如果直接初始ArrayList,则采用延迟初始化处理【初始化数组长度为0】的方式以节约内存,当添加数据时,才进行数组的初始化,默认初始化容积为10
  • 添加数据时当存储数据的数组长度不足时,数组会自动变长,变长的比例为原容积的50%
删除元素
public E remove(int index) {
    Objects.checkIndex(index, size);   //检查所需要删除位置下标参数是否在合理的范围内[0,size-1],如果超出范围则报异常
    final Object[] es = elementData;  获取所存储的所有元素数组
    E oldValue = (E) es[index];  获取指定位置上的元素
    fastRemove(es, index);   利用System.arrayCopy的方法使用覆盖的方式删除指定位置上的元素
    return oldValue;  返回删除掉的元素
    //没有缩容处理
}
private void fastRemove(Object[] es, int i) {
    modCount++;   修改次数+1
    final int newSize;  
    if ((newSize = size - 1) > i) //size - 1就是可以删除元素的最大下标
        System.arraycopy(es, i + 1, es, i, newSize - i); 通过数组拷贝的方式覆盖掉指定位置的元素
    es[size = newSize] = null; //最后位置赋null值
    {1,2,3,4,5} 执行arrayCopy方法需要覆盖下标为2的元素{1,2,4,5,5}
}
结论:
  • 使用数组元素移动的方式实现元素的删除。注意:这里没有变小容积
  • 修改元素个数时会有modCount的修改–快速失败
get方法的实现
public E get(int index) {
    Objects.checkIndex(index, size);针对索引下标进行合法性验证
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}

迭代器的实现类中定义【内部类】
private void checkForComodification() {
    if (modCount != expectedModCount) { 
        throw new ConcurrentModificationException(); 并发修改异常
    }
}
结论:
  • 首先要求index应该在[0,size-1]的范围内,否则异常
  • 如果index正确则按照下标从数组中获取元素
  • 如果多个线程同时操作一个ArrayList,可以通过ConcurrentModificationException解决修改出现的线程安全问题
LinkedList的使用和实现

双向链表实现,增删快,查询慢 (线程不安全)

属性:
  • transient int size = 0; 存储的元素个数
  • transient Node first; 头节点
  • transient Node last; 尾节点
节点的定义
 private static class Node<E> {  静态内部类
    E item;  节点上存储的具体数据
    Node<E> next;  指向下一个节点
    Node<E> prev;  指向上一个节点
	//创建节点对象的参数
	 * prev前一个节点的引用  element具体存储的数据   next下一个节点引用
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
构造器方法

没有指定容积的构造器,理论上来说,链表实际上没有长度限制,但是int size属性要求元素个数必须在[0,Integer.max-value]范围内

public LinkedList() {
}
add方法的定义:在双向链表的末尾新增元素
  • add(E e)
public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last;  缓存尾节点
    final Node<E> newNode = new Node<>(l, e, null);  创建新的节点,新节点的下一个节点为null
    last = newNode;  将尾指针执行新创建的节点
    if (l == null)   如果原来的尾节点为null,则新增的节点应该是链表的第一个节点
        first = newNode;  将头指针指向新创建的节点
    else  
        l.next = newNode;  如果原来的尾节点不为null,则表示已经有元素了,将原来的尾节点指向新创建的节点,这样就将新节点加入到链表中
    size++; 链表中元素个数加1
    modCount++;  修改次数加1
}
  • add(int,Object) 在指定位置新增元素,原来位置上的元素后移
public void add(int index, E element) {
    checkPositionIndex(index); 检查index的合法性,要求index应该是在[0,size]的范围内
    	index >= 0 && index <= size;如果不合法则抛出异常IndexOutOfBoundsException
    if (index == size)  如果索引值index等于size,则在链表默认添加元素
        linkLast(element);
    else  在指定下标位置之前添加元素
        linkBefore(element, node(index));
}
//获取指定位置上的节点对象
Node<E> node(int index) {
      if (index < (size >> 1)) { 如果序号值小于size/2则从头指针开始遍历链表,查找指定索引值的元素Node
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;  从前向后遍历
        return x;
    } else { 如果序号值大于等于size/2则从尾指针开始遍历链表,查找指定索引值的元素Node
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;  从后向前遍历
        return x;
    }
}
//在指定节点对象之前添加e数据
void linkBefore(E e, Node<E> succ) {
   final Node<E> pred = succ.prev;  获取指定位置的前一个节点
   final Node<E> newNode = new Node<>(pred, e, succ);  创建节点对象,参数1是原始节点的前一个节点,参数2是具体存储的数据,参数3是原始位置上的节点
    succ.prev = newNode;  设置原始位置上的node对象的前一个节点为新创建的节点【双向链表】
    if (pred == null)  如果指定位置的节点的前一个节点为null,则插入的新节点应该就是头节点
        first = newNode;  使头指针指向新创建的节点
    else   设置前一个节点的下一个节点指针指向新创建的节点
        pred.next = newNode;
    size++;  元素个数加1
    modCount++;  修改次数加1
}
删除和参数相等的第一个元素,删除成功返回true,否则返回false
public boolean remove(Object o) {
    if (o == null) {  删除值为null的元素,使用==判断
        for (Node<E> x = first; x != null; x = x.next) {从头指针开始执行遍历,查找第一个值为null的节点
            if (x.item == null) {  //判断当前节点的值是否为null
                unlink(x);  删除指定的Node节点,并且返回Node中存储的数据
                return true;  
            }
        }
    } else {  删除值非null的元素,判断使用equals方法
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) { 判断当前需要删除的数据和节点中的数据是否相等
                unlink(x);  删除指定的Node节点,并且返回Node中存储的数据
                return true;
            }
        }
    }
    return false;
}

   删除指定的Node节点,并且返回Node中存储的数据
   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) { 如果当前节点的前一个节点为null,则表示当前节点为头节点
        first = next;  使头指针指向当前节点的下一个节点
    } else { 如果不是头节点
        prev.next = next;   上一个节点的下一个节点为当前节点的下一个节点,将当前节点从链表中剔除出来
        x.prev = null;  当前节点的上一个节点赋null}
    if (next == null) {如果当前节点的下一个节点为null,则表示当前节点为尾节点
        last = prev; 使尾指针指向当前节点的上一个节点
    } else {  如果不是尾节点
        next.prev = prev; 下一个节点的上一个节点引用值为当前节点的上一个节点,将当前节点从链表中剔除出来
        x.next = null; 当前节点的下一个节点赋null}
    x.item = null; 将当前节点的数据赋null值
    size--; 链表中的元素个数-1
    modCount++; 修改次数+1
    return element; 返回当前节点原来的数据
}
按照指定的索引序号删除对应的元素,同时返回删除元素的具体数据值
public E remove(int index) {
   checkElementIndex(index);  检查序号值是否合法,不合法抛出异常IndexOutOfBoundsException  index >= 0 && index < size
   return unlink(node(index));  首先查找指定索引序号对应的节点对象,然后删除对应的节点,返回原来节点上存储的具体数据
}
修改操作
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;  //获取原始节点存储的数据
    x.item = element;  //修改节点上的数据为新值
    return oldVal;  //返回原始存储的数据
}
查找操作,如果找到则返回索引序号,如果没有找到则返回-1
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}
Vector的使用和实现

类定义

@since 1.0 从JDK1.0开始提供的实现类

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

属性:
  • protected Object[] elementData; 内部的数据存储采用的是Object[]
  • protected int elementCount; 存储的元素个数
  • protected int capacityIncrement;容积增长的步长值
构造器
public Vector() {
    this(10);
}
public Vector(int initialCapacity) { 参数为初始化容积
    this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0) 初始化容积不能小于0
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity]; 初始化数组,不是ArrayList中的延迟初始化
    this.capacityIncrement = capacityIncrement;
}
成员方法:
public synchronized boolean add(E e) {   方法上有synchronized,基本上所有的修改方法上都有synchronized关键字,所以线程安全的
     modCount++;  修改次数+1
     add(e, elementData, elementCount); 参数1为需要添加的元素,参数2为具体存储数据的数组,参数3为元素个数
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)   如果当前存储的元素个数和数据相等则先进行扩容处理
        elementData = grow();
    elementData[s] = e;
    elementCount = s + 1;
}

private Object[] grow() {
    return grow(elementCount + 1);
}

//具体的扩容处理,调用newCapacity方法获取新容积
private Object[] grow(int minCapacity) {
    return elementData = Arrays.copyOf(elementData,
                                       newCapacity(minCapacity));
}

private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;  获取原始容积值,也就是存储数据的数组长度
    //新容积值的算法:
     * 如果设置了容积增长的步长值,则新容积为原始容积+步长值
     * 如果没有设置容积增长的步长值,则扩容增长100%
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity <= 0) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
    return (newCapacity-MAX_ARRAY_SIZE <= 0)? newCapacity: hugeCapacity(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
总结List的三种实现类
ArrayListLinkedListVector
实现方式数组,按照索引下标访问速度快O(1),但是当删除添加元素时会导致元素的移动,速度慢O(n)双向链表,按照索引下标访问速度慢O(n),但是删除添加元素速度快O(1)数组,按照索引下标访问速度快O(1),但是当删除添加元素时会导致元素的移动,速度慢O(n)
是否同步不同步,线程不安全,但是并发高,访问效率高不同步,线程不安全,但是并发高,访问效率高 o同步,所以线程安全,但是并发低,访问效率低
如何选择经常需要快速访问,较少在中间增加删除元素时使用;如果多线程访问,则需要自行编程解决线程安全问题经常需要在内部增删元素,但是很少需要通过索引快速访问时使用;如果多线程访问,则需要自行编程解决线程安全问题一般不使用,如果在多线程访问时可以考虑使用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值