赞
踩
集合框架:集合就是一个放数据的容器,准确的说是放数据对象引用的容器。
下面画出Java集合间的框架关系,如图
Java各集合类之间的关系
Collection接口为单列集合的顶层接口,含有允许常见操作。
单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List和java.util.Set。其中,List的特点是元素有序、元素可重复。Set的特点是元素无序,而且不可重复。List接口的主要实现类有java.util.ArrayList和java.util.LinkedList,Set接口的主要实现类有java.util.HashSet和java.util.TreeSet。
// 添加方法: add(Object o) // 添加指定元素 addAll(Collection c) // 添加指定集合 // 删除方法: remove(Object o) // 删除指定元素 removeAll(Collection c) // 输出两个集合的交集 retainAll(Collection c) // 保留两个集合的交集 clear() // 清空集合 // 查询方法: size() // 集合中的有效元素个数 toArray() // 将集合中的元素转换成Object类型数组 // 判断方法: isEmpty() // 判断是否为空 equals(Object o) // 判断是否与指定元素相同 contains(Object o) // 判断是否包含指定元素 containsAll(Collection c) // 判断是否包含指定集合
Collection接口的常用子类如下:
List集合:有序(存取顺序一致)、存在索引(可随机取值)、可重复
a. ArrayList:常用的有序集合(数组结构,非线程安全)
b. Vector:并发安全的有序集合,效率较低逐渐淘汰,可使用Collections集合提供的Collections.synchronizedList()方法或者CopyOnWriteArrayList集合来替代
c. LinkedList:LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低,底层使用链表来维护(链表结构,非线程安全)
Set集合:无序(存取顺序不一致)、无索引(内部维护一个Map结构来实现的,hashcode定位)、不可重复
a. HashSet:无序、唯一。HashSet是基于HashMap来实现的,实现了Set接口,同时还实现了序列化和可克隆化。而集合(Set)是不允许重复值的,允许空值,非线程安全
b. LinkedHashSet:哈希表和链表结构,LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
c. TreeSet: 有序、唯一、TreeSet 是 Java 集合框架中的一种有序集合,它实现了 Set 接口,因此具有不允许重复元素的特性。与 HashSet 不同,TreeSet 使用红黑树数据结构来存储元素,这使得元素在集合中保持有序。
Queue
a. PriorityQueue: Object[] 数组来实现二叉堆
b. ArrayQueue: Object[] 数组 + 双指针
List 子接口的特点
List接口扩充的方法
public interface List<E> extends Collection<E> // 常用方法: public void add(int index,E element) // 在指定位 置处增加元素 boolean addAll(int index,Collection<? extends E> c) // 在指定位置处增加- -组元素 public E get(int index) // 根据索引位置取出每一个元素 publice int indexOf(Object o) // 根据对象查找指定的位置,找不到返回-1 . public int lastIndexOf(Object o) // 从后面向前查找位置,找不到返回-1 public ListIterator<E> listIterator( // 返回ListIterator 接口的实例 public ListIterator<E> listIterator(int index) // 返回从指定位置的ListIterator接口的实例 public E remove(int index) // 删除指定位 置的内容 public E set(int index,E element) // 修改指定位置的内容 List<E> subList(int fromIndex,int toIndex)
注:get方法比较常用
List常见的子类
List和Vector的比较
示例:
import java.util.List;
import java.util.Arrays;
class Solution {
public static void main(String[] args) {
// List<Integer> ls1 = Arrays.asList(1, 2, null);
List<String> ls2 = List.of("JieWen","2","King");
Object result[] = ls2.toArray();//将List转为Array储存
// System.out.println(ls1.contains(null));
for(Object temp : result){
System.out.printf(temp + "\");
}
}
}
// 结果:JieWen、2、King
线性结构最方便的实现就是基于数组实现
ArrayList的类定义:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
你了解这三种构造方法?
public ArrayList() { this(10); // 调用ArrayList(10) 默认初始化一个大小为10的object数组。 } public ArrayList(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); // 如果用户初始化大小小于0抛异常,否则新建一个用户初始值大小的object数组。 this.elementData = new Object[initialCapacity]; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // 当c.toArray返回的不是object类型的数组时,进行下面转化。 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
默认情况下使用ArrayList会生成一个大小为10的Object类型的数组。也可以调用ArrayList(int initialCapacity) 来初始化Object数组的大小。并且用户可以往ArrayList中传入一个容器只要这个容器是Collection类型的。调用ArrayList(Collection<? extends E> c)接口的时候会将容器数组化处理并将这个数组值赋给Object数组。
ArrayList的特点:
ArrayList的底层分析:
ArrayList核心源码解读:
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 默认初始容量大小 */ private static final int DEFAULT_CAPACITY = 10; /** * 空数组(用于空实例)。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; // 用于默认大小空实例的共享空数组实例。 // 我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 保存ArrayList数据的数组 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList 所包含的元素个数 */ private int size; /** * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小) */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // 如果传入的参数大于0,创建initialCapacity大小的数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 如果传入的参数等于0,创建空数组 this.elementData = EMPTY_ELEMENTDATA; } else { // 其他情况,抛出异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 默认无参构造函数 * DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 */ public ArrayList(Collection<? extends E> c) { //将指定集合转换为数组 elementData = c.toArray(); //如果elementData数组的长度不为0 if ((size = elementData.length) != 0) { // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断) if (elementData.getClass() != Object[].class) //将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 其他情况,用空数组代替 this.elementData = EMPTY_ELEMENTDATA; } } /** * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。 */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } // 下面是ArrayList的扩容机制 // ArrayList的扩容机制提高了性能,如果每次只扩充一个, // 那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。 /** * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量 * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { // 如果是true,minExpand的值为0,如果是false,minExpand的值为10 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; // 如果最小容量大于已有的最大容量 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } // 1.得到最小扩容量 // 2.通过最小容量扩容 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 获取“默认的容量”和“传入参数”两者之间的最大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } // 判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) // 调用grow方法进行扩容,调用此方法代表已经开始扩容了 grow(minCapacity); } /** * 要分配的最大数组大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * ArrayList扩容的核心方法。 */ private void grow(int minCapacity) { // oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; // 将oldCapacity 右移一位,其效果相当于oldCapacity /2, // 我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1); // 然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 再检查新容量是否超出了ArrayList所定义的最大容量, // 若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, // 如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } // 比较minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** * 返回此列表中的元素数。 */ public int size() { return size; } /** * 如果此列表不包含元素,则返回 true 。 */ public boolean isEmpty() { // 注意=和==的区别 return size == 0; } /** * 如果此列表包含指定的元素,则返回true 。 */ public boolean contains(Object o) { // indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 return indexOf(o) >= 0; } /** * 返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) // equals()方法比较 if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。. */ public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } /** * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) */ public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // 这不应该发生,因为我们是可以克隆的 throw new InternalError(e); } } /** *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。 *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) */ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) // 新建一个运行时类型的数组,但是ArrayList数组的内容 return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //调用System提供的arraycopy()方法实现数组之间的复制 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // Positional Access Operations @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } /** * 返回此列表中指定位置的元素。 */ public E get(int index) { rangeCheck(index); return elementData(index); } /** * 用指定的元素替换此列表中指定位置的元素。 */ public E set(int index, E element) { //对index进行界限检查 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; //返回原来在这个位置的元素 return oldValue; } /** * 将指定的元素追加到此列表的末尾。 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! //这里看到ArrayList添加元素的实质就相当于为数组赋值 elementData[size++] = e; return true; } /** * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } /** * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 */ public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work //从列表中删除的元素 return oldValue; } /** * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 *返回true,如果此列表包含指定的元素 */ 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 true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ 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; // clear to let GC do its work } /** * 从列表中删除所有元素。 */ public void clear() { modCount++; // 把数组中所有的元素的值设为null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } /** * 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。 */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } /** * 将指定集合中的所有元素插入到此列表中,从指定的位置开始。 */ public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; } /** * 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。 *将任何后续元素移动到左侧(减少其索引)。 */ protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; } /** * 检查给定的索引是否在范围内。 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * add和addAll使用的rangeCheck的一个版本 */ private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 返回IndexOutOfBoundsException细节信息 */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } /** * 从此列表中删除指定集合中包含的所有元素。 */ public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); //如果此列表被修改则返回true return batchRemove(c, false); } /** * 仅保留此列表中包含在指定集合中的元素。 *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } /** * 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。 *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 *返回的列表迭代器是fail-fast 。 */ public ListIterator<E> listIterator(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index); return new ListItr(index); } /** *返回列表中的列表迭代器(按适当的顺序)。 *返回的列表迭代器是fail-fast 。 */ public ListIterator<E> listIterator() { return new ListItr(0); } /** *以正确的顺序返回该列表中的元素的迭代器。 *返回的迭代器是fail-fast 。 */ public Iterator<E> iterator() { return new Itr(); }
以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
ArrayList底层是动态数组存在扩容说法,默认的数组大小是10,在检测是否需要扩容后,如果扩容,会扩容为原来的1.5倍大小。原理就是把老数组的元素存储到新数组里面。
使用Collections.synchronizedList(new ArrayList())生成的ArrayList可以是线程安全的。
ArrayList 常见方法:
1.add(E e) 在集合末尾新增一个元素
2.add(int index, E element) 在指定位置添加元素
3.get(int index)获取指定位置的元素
4.remove(int index) 删除指定位置的元素
5.remove(Object o) 删除指定元素
6.indexOf(Object o) 查询指定元素的位置 lastIndexOf也一样,只是从尾部开始遍历
7.set(int index, E element) 设置指定位置的元素值
8.retainAll(Collection<?> c) 求两个集合的交集
ArrayLIst 实现:
import java.util.*; public class Daycare{ public static void main(String[] args){ // ArrayList Collections.addAll(集合,元素,....) // 创建一个集合对象装名字 ArrayList<String> list=new ArrayList<>(); // 一次添加一个元素的方式:添加:Andy Lee Collections.addAll(list,"Andy","Lee"); // 统计集合里面有几个人的姓名 System.out.println("人的个数:"+list.size()); // 打印第一个人的姓名 System.out.println("第一个人的名字:"+list.get(0)); // 判断集合里面是否出现Lee的名字 System.out.println(list.contains("Lee")); // 使用两种不同的方式打印 所有以A开头的名字 for(String name:list){ if(name.charAt(0)=='A'){ System.out.println(name); } } for(String name:list){ if(name.startsWith("A")){ System.out.println(name); } } // 用迭代器 for(Iterator<String> car=list.iterator();car.hasNext();){ String name=car.next(); if(name.startsWith("A")) { System.out.println(name); } } }
ArrayList 保存自定义类对象
必须实现的方法:
方法的实现代码:
import java.util.ArrayList; /** * ArrayList保存自定义类对象 **/ public class ArrayListSaveDefineClassInstance { public static void main(String[] args) { ArrayList<ArrayListPerson> all= new ArrayList<ArrayListPerson> (); all.add(new ArrayListPerson("张三" , 19)); all.add(new ArrayListPerson("李四" , 20)); all.add(new ArrayListPerson("王五" , 12)); // 由于地址不一样,无法找到也无法,删除,所以要在自定义类中复写equals()方法 System.out.println(all.contains(new ArrayListPerson("王五" , 12))); all.remove(new ArrayListPerson("王五" , 12)); System.out.println(all); } } /** 1. 自定义类 **/ class ArrayListPerson{ private String name; private int age; // 需要覆写的equals()方法才能保证能够实现查找 @Override public boolean equals(Object obj) { if (!(obj instanceof ArrayListPerson)) { return false; } if (this == obj) { return false; } if (obj == null) { return false; } ArrayListPerson objP = (ArrayListPerson) obj; return this.name==objP.name && this.age==objP.age; } @Override public String toString() { return "ArrayListPerson{" + "name='" + name + '\'' + ", age=" + age + '}'; } public ArrayListPerson(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; } }
LinkedList 概述:
LinkedList 是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。所以 LinkedList 插入和删除方面要优于 ArrayList,而随机访问上则 ArrayList 性能更好。除了 List 接口。之外,LinkedList 还实现了 Deque,Cloneable,Serializable 三个接口。这说明该数据结构支持队列,克隆和序列化操作的。与 ArrayList 一样,允许 null 元素的存在,且是不支持多线程的。
LinkedList 特点:
构造方法:
public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList 提供了两个构造方法:LinkedList() 和 LinkedList(Collection<? extends E> c)。
LinkedList 实现原理:
LinkedList是基于双向链表的数据结构实现的,链表是可以占用一段不连续的内存,双向链表有前驱节点和后驱节点,里面存储的是上一个元素和后一个元素所在的位置,中间的黄色部分就是业务数据了,当我们需要执行插入的任务,比如第一个元素和第二个元素之间,只需要改变他们的前驱节点和后驱节点的指向就可以了,不要像动态数组那么麻烦,删除也是同样的操作,但是因为是不连续的内存空间,当需要执行查找,需要从第一个元素开始查找,直到找到我们需要的数据。
▶LinkedList 常用方法:
// 增加: add(E e) // 在链表后添加一个元素; 通用方法 addFirst(E e) // 在链表头部插入一个元素; 特有方法 addLast(E e) // 在链表尾部添加一个元素; 特有方法 push(E e) // 与addFirst方法一致 offer(E e) // 在链表尾部插入一个元素 add(int index, E element) // 在指定位置插入一个元素。 offerFirst(E e) // JDK1.6版本之后,在头部添加; 特有方法 offerLast(E e) // JDK1.6版本之后,在尾部添加; 特有方法 // 删除: remove() // 移除链表中第一个元素; 通用方法 remove(E e) // 移除指定元素; 通用方法 removeFirst(E e) // 删除头,获取元素并删除; 特有方法 removeLast(E e) // 删除尾; 特有方法 pollFirst() // 删除头; 特有方法 pollLast() // 删除尾; 特有方法 pop() // 和removeFirst方法一致,删除头。 poll() // 查询并移除第一个元素 特有方法 // 查: get(int index) // 按照下标获取元素; 通用方法 getFirst() // 获取第一个元素; 特有方法 getLast() // 获取最后一个元素; 特有方法 peek() // 获取第一个元素,但是不移除; 特有方法 peekFirst() // 获取第一个元素,但是不移除; peekLast() // 获取最后一个元素,但是不移除; pollFirst() // 查询并删除头; 特有方法 pollLast() // 删除尾; 特有方法 poll() // 查询并移除第一个元素 特有方法
LinkedList 的实现方法:
public class LinkedListMethodsDemo { public static void main(String[] args) { LinkedList<String> linkedList = new LinkedList<>(); linkedList.add("first"); linkedList.add("second"); linkedList.add("third"); System.out.println(linkedList); linkedList.addFirst("addFirst"); System.out.println(linkedList); linkedList.addLast("addLast"); System.out.println(linkedList); linkedList.add(2, "addByIndex"); System.out.println(linkedList); } } /* 输出结果: [first, second, third] [addFirst, first, second, third] [addFirst, first, second, third, addLast] [addFirst, first, addByIndex, second, third, addLast] */
注:Vector类在JDK1.0就已经提供,但是由于以下:
所以vector子类目前已经过时,但是由于其广泛性并没有弃用。
Vector底层分析:
底层数据结构:数组实现,和ArrayList 一样,但是Vector 是同步方法线程安全,但是效率低,所以不推荐使用,只有要求需要线程安全才会去使用该子类;
Vector 所定义结构
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.Vector<E>
示例:
public class VectorTest{
public static void main(String[] args){
List<String> all = new Vector<String>();
all.add("JieWen");
System.out.printf(all);
}
}
提供该接口原因:
为了与List接口的使用有所区分,在进行Set接口设计的时候要求内部不允许保存重复的元素,Set接口定义如下:
public interface Set<E> extends Collection<E>(){}
Set接口的使用
public class SetTest{
public static void main(String[] args){
Set<String> all = new Set.of("JieWen","XiaoFei");
System.out.printf(all);
}
}
Set的特性
基本和Collection接口的方法一种,没有进行功能的扩充。
Set的子类
Set常用的方法
Set 接口继承 Collection 接口,而且它不允许集合中存在重复项。所有原始方法都是现成的,没有引入新方法。具体的 Set 实现类依赖添加的对象的 equals() 方法来检查等同性。
public int size() // 返回set中元素的数目,如果set包含的元素数大于Integer.MAX_VALUE,返回Integer.MAX_VALUE;
public boolean isEmpty() // 如果set中不含元素,返回true ;
public boolean contains(Object o) // 如果set包含指定元素,返回true ;
public Iterator iterator() //返回set中元素的迭代器,元素返回没有特定的顺序,除非set提高该保证的某些类的实例 ;
public boolean add(Object o) // 如果set中不存在指定元素,则向set加入;
public boolean remove(Object o) // 如果set中存在指定元素,则从set中删除 ;
public boolean removeAll(Collection c) // 如果set包含指定集合,则从set中删除指定集合的所有元素 ;
public void clear() // 从set中删除所有元素;
数据结构:
JDK 1.8之前是通过哈希表的单向链表和数组来组成、JDK 1.8之后将单链表该为(单链表+红黑树)目的在于:当链表的长度超过阀值(8)时改为红黑树,红黑树转变为链表的值为(6)、而链表查找元素的时间复杂度为 O(n),远远大于红黑树的 O(logn),尤其是在节点越来越多的情况下,O(logn) 体现出的优势会更加明显;简而言之就是为了提升查询的效率。装载因子(0.75f),
HashSet 的特性:(散列存放)
如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone()、writeObject()、readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
HashSet实现了 Map 接口 实现 Set 接口
HashMap存储键值对 HashSet仅存储对象
HashMap调用 put()向 map 中添加元素 HashMap调用 add()方法向 Set 中添加元素
HashMap 使用键(Key)计算 hashcode HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以equals()方法用来判断对象的相等性
HashSet的构造方法
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {}
方法概览
// 增加元素 add(Object o) addAll(Collection c) // 判断元素是否存在 contains(Object o) containsAll(Collection c) // 判断集合是否为空 isEmpty() // 删除元素 remove(Object o) removeAll(Collection c) // 返回集合的大小 size() // 清空集合 clear() // 迭代器 iterator() // 将内容转到数组中 toArray()
示例:
import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class Test { public static void main(String args[]) { int[] a= {6,5,4,3,2,1,1,5,6,7}; HashSet<Integer> hs=new HashSet<>(); //所有的集合中存储的元素的类型都不会是基本数据类型!!!使用菱形推断创建对象 for(int i:a) { if(!hs.add(i)) { System.out.println(i+"已存在!"); //HashSet中的元素不允许重复! } hs.add(i); } /*for(int i=0;i<a.length;i++) { if(!hs.add(a[i])) System.out.println(a[i]+"已存在!"); hs.add(a[i]); }*/ if(!hs.isEmpty()) { //判断集合是否为空 System.out.println("hs不为空"); } if(hs.contains(7)) { //判断集合是否包含元素 System.out.println("集合hs中包括元素7"); } for(int j : hs) { System.out.print(j+" "); } System.out.println(); HashSet<Integer> h=new HashSet<>(); h.add(102); // 添加元素102 // h.remove(102); 删除元素102 h.add(103); h.add(2); if(hs.containsAll(h)) { // 判断集合是否包含另一集合的所有元素 System.out.println("hs集合中包含h集合中的所有元素"); } else { System.out.println("hs集合中不包含h集合中的所有元素"); } // hs.addAll(h); 将集合h中的所有元素添加到hs中 hs.removeAll(h); // 删除hs和h的交集中的元素 // hs.retainAll(h); 取得hs和h的交集的元素 // hs.clear(); 删除所有hs的元素 Object[] objects=hs.toArray(); //HashSet.toArray()的无参方法 for(Object o:objects) { System.out.print(o+" "); } System.out.println(); Integer[] mid=hs.toArray(new Integer[hs.size()]); // HashSet.toArray()的带参方法 for(Integer integer:mid) { System.out.print(integer+" "); } System.out.println(); hs.removeAll(java.util.Collections.singleton(7)); // Collections.singleton(7):生成只有一个元素7的集合 Iterator<Integer> iterator=hs.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next()+" "); } System.out.println(); } }
HashSet如何检查重复?
当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。
// Returns: true if this set did not already contain the specified element
// 返回值:当 set 中没有包含 add 的元素时返回真
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
数据结构:红黑树
TreeSet特性
底层分析
TreeSet的底层是TreeMap,添加的数据存入了map的key的位置,而value则固定是PRESENT。TreeSet中的元素是有序且不重复的,因为TreeMap中的key是有序且不重复的。
注意: 不允许存放null。
示例:
public class TreeTest{
public static void main(String[] srgs){
Set<String> all = new Set<>();
all.add("JieWen");
System.out.printf(all);
}
}
TreeSet 子类排序分析
即类要实现Comparable接口,并重写compareTo()方法,TreeSet对象调用add()方法时,会将存入的对象提升为Comparable类型,然后调用对象中的compareTo()方法进行比较,判断有没有重复元素,根据比较的返回值进行存储。
因为TreeSet底层是二叉树,当compareTo方法返回0时,不存储;当compareTo方法返回正数时,存入二叉树的右子树;当compareTo方法返回负数时,存入二叉树的左子树。如果一个类没有实现Comparable接口就将该类对象存入TreeSet集合,会发生类型转换异常。
自然排序
import java.util.Set; import java.util.TreeSet; // TreeSet的自然排序 // 1.实体类实现Comparable接口,重写compareTo()方法 public class TestTreeSetSort1 { public static void main(String[] args) { Set<Student> tree=new TreeSet(); Student s1=new Student("JieWen",21,88); Student s2=new Student("Feign",21,34); Student s3=new Student("K",24,99); Student s4=new Student("J",27,13); Student s5=new Student("N",22,65); tree.add(s1); tree.add(s2); tree.add(s3); tree.add(s4); tree.add(s5); // Exception in thread "main" java.lang.ClassCastException: com.qianfeng.kxf.day25.set.Student cannot be cast to java.lang.Comparable // 为什么报错?因为添加的是对象元素,必须实现一个排序,否则就会类型转换错误的问题 System.out.println("元素对象多少个"+tree.size()); for(Student s:tree) { System.out.println(s); } } } class Student implements Comparable{ private String name; private int age; private int score; 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; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", score=" + score + "]"; } public Student(String name, int age, int score) { super(); this.name = name; this.age = age; this.score = score; } public Student() { super(); } /*//重写该方法:只通过名字进行排序 @Override public int compareTo(Object o) { // 先转换为Student类型对象 Student str=(Student)o; // 先对姓名进行排序,直接调用String类型中的compareTo()方法进行比较 return this.name.compareTo(str.name); } // 元素对象多少个3 // Student [name=J, age=27, score=13] // Student [name=K, age=24, score=99] // Student [name=JieWen, age=21, score=88] */ /*//如果名字相同,就按年龄排序 public int compareTo(Object o) { Student stu=(Student)o; //先对名字排序:升序 int n=this.name.compareTo(stu.name); //如果名字相同就比较年龄 :升序 return n==0?this.age-stu.age:n; }*/ //如果名字相同,就按年龄排,如果年龄还相同就按成绩排 public int compareTo(Object o) { Student stu=(Student)o; int n=this.name.compareTo(stu.name); int a=n==0?this.age-stu.age:n; return a==0?this.score-stu.score:a; } }
定制排序
//主要实现方法 class MyComparator implements Comparator<Person>{ // 按姓名排序 /*@Override public int compare(Person o1, Person o2) { int n=o1.getName().compareTo(o2.getName()); return n; }*/ // 按姓名和年龄排序 /*public int compare(Person p1,Person p2) { int n=p1.getName().compareTo(p2.getName()); return n==0?p1.getAge()-p2.getAge():n; }*/ // 按姓名.年龄.成绩排序 public int compare(Person p1,Person p2) { int n = p1.getName().compareTo(p2.getName()); int a = n == 0 ? p1.getAge() - p2.getAge() : n; return a == 0 ? p1.getScore() - p2.getScore() : a; } }
消除重复元素
由于TreeSet有排序要求,所以利用Comparable接口实现重复元素判断,而非排序集合中对重复元素的判断Object类提供的hash码,boolean equals()对象比较。
代码实现
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; People people = (People) o; return Objects.equals(name, people.name) && Objects.equals(age, people.age); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public int compareTo(People o) { if(this.age > o.age){ return 1; } else if (this.age < o.age){ return -1; } else { return this.name.compareTo(o.name); } }
3.3.3 LinkedHashSet
目的:为了解决HashSet实现类的无序性而创建的一个类,实现基于链表的顺序储存
数据结构:和HashSet基本一致,只是将单链表改为双向链表
LinkedHashSet 特性
>查询快、元素有序、元素不可重复、没有索引。
底层分析:
为HashSet的子类,只是比他多添加一条记录元素顺序的链表,所以其为有序;
//从键盘输入字符串,去掉重复字符输出
public static void getSingleChar(){
Scanner sc=new Scanner(System.in);
System.out.println("请输入一串字符:");
String str=sc.nextLine();
char[] ch=str.toCharArray();
LinkedHashSet<Character> linkedHashSet=new LinkedHashSet<>();
for (char c:ch) {
linkedHashSet.add(c);
}
for (Character c:linkedHashSet) {
System.out.println(c);
}
}
概述
Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map内部自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的;
(key,value)即键值对,key不允许重复,value允许重复,一一对应;
Map属于双列集合,保证key唯一,必须重写hashCode 和equals()方法;
Map的子实现:
Map常用方法
// 添加、删除、修改操作: // 将指定key-value添加到(或修改)当前map对象中 Object put(Object key,Object value) // 将m中的所有key-value对存放到当前map中 void putAll(Map m) // 移除指定key的key-value对,并返回value Object remove(Object key) // 清空当前map中的所有数据 void clear() // 元素查询的操作: // 获取指定key对应的value Object get(Object key) // 是否包含指定的key boolean containsKey(Object key) // 是否包含指定的value boolean containsValue(Object value) // 返回map中key-value对的个数 int size() // 判断当前map是否为空 boolean isEmpty() // 判断当前map和参数对象obj是否相等 boolean equals(Object obj) // 元视图操作的方法: // 返回所有key构成的Set集合 Set keySet() // 返回所有value构成的Collection集合 Collection values() // 返回所有key-value对构成的Set集合 Set entrySet()
注:JDK1.9后为了方便进行Map数据的操作,添加了Map.of()的方法接受每一组数据转为Map;
public class MapTest{
public static void main(String[] srgs){
// one =1,two =2;
Map<String,Integer> map = Map.of("one",1,"two",2);//k,v型
System.out.printf(map);
// {two=2},{one=1}
}
}
注意:
Map和Collection之间的区别
概述
HashMap 是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即存储的对象是 Entry (同时包含了 Key 和 Value) 。在HashMap中,其会根据hash算法来计算key-value的存储位置并进行快速存取。特别地,HashMap最多只允许一条Entry的键为Null(多条会覆盖),但允许多条Entry的值为Null。此外,HashMap 是 Map 的一个非同步的实现。不同步导致可能多个线程同时写该方法;
数据结构:
JDK8之后扰动函数逻辑:
hashCode() 00000000 11111111 00000000 00001010 hash值
hashCode() >>> 16 00000000 00000000 00000000 11111111 右移16位
hashCode() ^ (hashCode() >>> 16) 00000000 11111111 00000000 11110101 异或高低位(相同为0,不相同为1)
hashCode() ^ (hashCode() >>> 16) & 15 00000000 00000000 00000000 00001111 与运算下标(都为1则为1,否则为0)
00000000 00000000 00000000 00000101
= 0101
= 5
HashMap的特性
HashMap常用方法
// 在此映射中关联指定的Key-value put(Object key,Object value) // 在此映射中将指定的映射关系添加到被操作的映射中 putAll(Collection c) // 根据key获取指定的value get(Object key) // 检测该映射中是否存在指定key的映射,有则返回true;没有则返回false containsKey(Object key) // 检测该映射中是否存在指定value的映射,有则返回true;没有则返回false containsValue(Object value) // 根据key的值删除指定的映射关系 remove(Object key) // 测试映射是否为空 isEmpty() // 返回值的集合 values()
HashMap方法实现
public class HashMap{
public static void main(){
Map<String,Integer> map = new HashMap<>();
map.put("one",1);
map.put("two",2);
map.put("null",0);
map.put("one",101);// 就近原则覆盖前面的
System.out.printf(map.get("ten")); // 不存在则返回null;
}
}
迭代器输出
Map<String, String> map = new HashMap<>();
Iterator iter = map.entrySet().iterator();// 比Set多一步
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
System.out.println("[Key] : " + entry.getKey() + " [Value] : " + entry.getValue());
}
数据结构:和HashMap基本一致,只是将单链表改为双向链表
LinkedHashMap 特性:
迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。而LinkedHashMap,它虽然增加了时间和空间上的开销,通过一个双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序有两种,可以是插入顺序或者是访问顺序。LinkedHashMap继承了HashMap类,有着HashMap的所有功能,还提供了记住元素添加顺序的方法,通过链表实现;
key不可重复但是可以为null;value可以重复;
LinkedHashMap 定义:
public class LinkedHashMap<K,V> extends HashMap<K,V>
implements Map<K,V>{}
LinkedHashMap方法实现和上面 HashMap 基本一致。
概述
早期的字典实现类(通过哈希值查找),可以更方便实现数据查询;但是这个子类比较古老,目前已经不推荐使用该类,因为该方法是同步方法,线程安全但是效率比较低;同时Hashtable不允许储存null数据,无序;
Hashtable 的定义
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
该子类的保存数据和Map类一致 ;
注:目前将方法contains()改为以下两种:containsValue() 值判断以及containsKey() 键判断;
还有一种实现线程安全: 使用ConcurrentHashMap
其实现原理是Hashtable是对整个表进行加锁,而ConcurrentHashMap是把表进行分段,初始情况下分成16段,每一段都有一把锁,当多个线程访问不同的段时,因为获取到的锁是不同的,所以可以并行的访问。效率比Hashtable高多了,推荐使用。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
概述
TreeSet是一个有序集合,可以以任意顺序将元素插入到集合中,在对集合进行遍历的时候,每个元素将自动按照排序后的顺序呈现。底层使用的是二叉树(更具体点是红黑树)实现,对于元素之间排序,如果不指定自定义的比较器Comparator,那么插入的对象 必须实现Comparable 接口,元素按照实现此接口的compareTo()方法去排序。如果指定了自定义的比较器Comparator,优先使用Comparator去对元素进行排序。比较规则决定了元素是否可以重复,以及元素的排序结果。
数据结构:红黑树
TreeMap 特性
底层分析:和TreeSet的相类似;
TreeMap 排序方法
public class MapTest{
public static void main(String[] srgs){
//one =1,two =2;
Map<String,Integer> map = new TreeMap<>();//k,v型
map.put("A",1);
map.put("C",3);
map.put("B",2);
System.out.printf(map);
// 结果:
// {A=1,B =2,C= 3}//自然排序默认
}
}
自然排序和定制排序和TreeSet一致(因为TreeSet保存的值为Map的键Key储存的值)利用Key值来进行排序;
hashCode()与equals()的相关规定:
概述
在Map类设计是,提供了一个嵌套接口(static修饰的接口):Entry。Entry将键值对的对应关系封装成了对象,即键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。可以获取相应的值;
示例:
public class MapEntryTest{
public static void main(String[] args){
Map.Entry<String,Integer> entry= Map.entry("one",1);
System.out.printf(entry.getKey());// 获取Key
System.out.printf(entry.getValue());// 获取Value
}
}
概述
Java集合框架的集合类,我们有时候称之为容器。容器的种类有很多种,比如ArrayList、LinkedList、HashSet…,每种容器都有自己的特点,ArrayList底层维护的是一个数组;LinkedList是链表结构的;HashSet依赖的是哈希表,每种容器都有自己特有的数据结构。
因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java引入了迭代器模式!
Iterator中常用方法
public boolean hasNext();// 判断下一个是否有值
public E next();// 先移动到下一位置取出元素;
public default void remove();// 移除当前元素
示例:
public class Demo { public static void main(String[] args) { Collection coll = new ArrayList(); // List<String> list = new ArrayList();//List和Set都需要先获取iterator方法 /* Set<String> all = Set.of("1","2","3"); Iterator<String> iter = all.iterator(); */ coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); System.out.println(coll); Iterator it = coll.iterator(); while (it.hasNext()) { it.next(); it.remove(); } System.out.println(coll); } }
注意:
怎么使用迭代器?
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。