集合
一:简介
Java中的集合分为两大家族,三个分支
1、 Collection(接口)家族。该接口下的所有子孙均存储的是单一对象。
2、 Map(接口)家族。该接口下的所有子孙均存储的是key-value(键值对)形式的数据。
另外还有三个分支,均是为上述两大家族服务的。
1、 Iterator(迭代器)。主要用于遍历Colleciton接口的及其子类而设计。
2、 Comparator(比较器), 在集合中存储对象时候,用于对象之间的比较
3、 Collecitons是工具类。注意该类名带个s,一般就表示工具类。里面提供了N多静态方法,来对Colleciton集合进行操作。
二:Collection家族
Collection下常见的有三个接口:
- List:不唯一,有序
- Set:唯一无序
- Queue;
1.Collection子接口(一)List
ArrayList(主要的实现类) LinkdeList(对于频繁的插入,删除,但是查找复杂度高) Vector(古老的,线程安全)
1)ArrayList类
数组存储相似(查找效率高,插入删除效率低)
ArrayList实现了三个标记接口,分别是:
- RandomAccess接口:RandomAccess的字面意思就是随机存取,当某个类有可以随机访问存取对象元素的特性时候就可以实现RandomAccess接口,ArrayList底层是一个动态数组,底层是数组实现的,所以ArrayList可以通过下标随机访问对象元素,时间复杂度为O(1)。
- Cloneable接口:clone()方法是深拷贝;Cloneable其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。ArrayList()重写了clone方法,所以需要继承Cloneable接口。
- java.io.Serializable接口:该接口用来标记该类对象是否可以序列化和反序列化,将对象序列化之后,可以进行持久化的储存以及在网络中进行传输。如把对象变成字节流写入到一个文件中,就是一个序列化的过程,实现了对象的持久化储存,然后你的程序可以从这个文件中读取序列化的对象并且把它还原成原来的对象,进行反序列化。如果进行序列化的类的对象没有实现Serializable接口,则会抛出NotSerializableException。
ArrayList底层原理:
1.ArrayList底层是基于数组实现的,与数组不同的是ArrayList可以动态扩容;
2.ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
// ArrayList带容量大小的构造函数。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 新建一个数组
this.elementData = new Object[initialCapacity];
}
// ArrayList无参构造函数。默认容量是10。
public ArrayList() {
this(10);
}
// 创建一个包含collection元素的ArrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
3.Fail-Fast机制: ArrayList也采用了快速失败的机制,通过记录modCount参数来实现
4.ArrayList初始容量是10,负载因子是1,满了就扩容为原来的1.5倍,增加0.5;当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时 。
5.ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
6.在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。
2)LinkList类
**LinkedList也实现两个个标记接口,相对于ArrayList少了一个RandomAccess接口(因为LinkedList是基于链表实现的,没有随机存取元素)多了一个Deque接口; **
1.Deque:线性集合,支持两端插入和移除元素。 名称deque是“双端队列”的缩写,通常发音为“deck”[dek]。此接口定义了访问双端队列两端元素的方法。 提供了插入,移除和检查元素的方法。 这些方法中的每一种都以两种形式存在:一种在操作失败时抛出异常,另一种返回特殊值(null或false,具体取决于操作)。 后一种形式的插入操作专门设计用于容量限制的Deque实现; 在大多数实现中,插入操作不会失败。
方法类似于ArrayList;但是由于实现了Deque接口,所以有从列表首部或者尾部查找,删除,插入。
public class LinkedListTest {
@Test
public void manger() {
LinkedList link=new LinkedList();
link.add(new Stu1(18, "张三"));
link.add(new Stu1(42, "老四"));
link.add(new Stu1(52,"麻子"));//单个插入
System.out.println(link);
link.add(1, "插入的树");
System.out.println(link);
LinkedList link2=new LinkedList();
link2.add("hahg");
link2.add("哈哈哈");
link.addAll(link2);//插入一个集合
Iterator it=link.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
System.out.println("=======================================");
link2.addFirst("插入首位");
link2.addLast("插入尾部");
System.out.println(link2);
System.out.println("=======================================");
Object ti=link2.clone();//拷贝
System.out.println(ti);
System.out.println(link2.contains("hahg"));
System.out.println("=======================================");
link2.clear();//清空集合
System.out.println(link2);
}
}
class Stu1{
int age;
String name;
public Stu1(int age, String name) {
super();
this.age = age;
this.name = name;
}
public String toString() {
return "[姓名:"+name+",年龄:"+age+"]";
}
}
LinkLIst底层原理:
1.LinkList底层是基于双向链表实现的(jdk1.7以前是双向门=循环链表),每个节点都有一个直接前驱和直接后继;
2.二分查找来看 index
离 size 中间距离来判断是从头结点正序查还是从尾节点倒序查 ,也就是说每次查找不需要全部遍历,如果下标大于1/2 size就从末尾查找,如果小于1/2 size就从头结点开始遍历。
源码解析:
public class LinkedList<E>extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0; //LinkedList中存放的元素个数
transient Node<E> first; //头节点
transient Node<E> last; //尾节点
//构造方法,创建一个空的列表
public LinkedList() {
}
//将一个指定的集合添加到LinkedList中,先完成初始化,在调用添加操作
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//插入头节点
private void linkFirst(E e) {
final Node<E> f = first; //将头节点赋值给f节点
//new 一个新的节点,此节点的data = e , pre = null , next - > f
final Node<E> newNode = new Node<>(null, e, f);
first = newNode; //将新创建的节点地址复制给first
if (f == null) //f == null,表示此时LinkedList为空
last = newNode; //将新创建的节点赋值给last
else
f.prev = newNode; //否则f.前驱指向newNode
size++;
modCount++;
}
//插入尾节点
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//在succ节点前插入e节点,并修改各个节点之间的前驱后继
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//删除头节点
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
//删除尾节点
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//删除指定节点
E unlink(Node<E> x) {
// assert x != null;
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;
}
//获取LinkedList中的第一个节点信息
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//获取LinkedList中的最后一个节点信息
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//删除头节点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除尾节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//将添加的元素设置为LinkedList的头节点
public void addFirst(E e) {
linkFirst(e);
}
//将添加的元素设置为LinkedList的尾节点
public void addLast(E e) {
linkLast(e);
}
//判断LinkedList是否包含指定的元素
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//返回List中元素的数量
public int size() {
return size;
}
//在LinkedList的尾部添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
//删除指定的元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//将集合中的元素添加到List中
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//将集合中的元素全部插入到List中,并从指定的位置开始
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray(); //将集合转化为数组
int numNew = a.length; //获取集合中元素的数量
if (numNew == 0) //集合中没有元素,返回false
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index); //获取位置为index的结点元素,并赋值给succ
pred = succ.prev;
}
for (Object o : a) { //遍历数组进行插入操作。修改节点的前驱后继
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
//删除List中所有的元素
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
//获取指定位置的元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//将节点防止在指定的位置
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//将节点放置在指定的位置
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//删除指定位置的元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//判断索引是否合法
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//判断位置是否合法
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//索引溢出信息
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
//检查节点是否合法
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//检查位置是否合法
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//返回指定位置的节点信息
//LinkedList无法随机访问,只能通过遍历的方式找到相应的节点
//为了提高效率,当前位置首先和元素数量的中间位置开始判断,小于中间位置,
//从头节点开始遍历,大于中间位置从尾节点开始遍历
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//返回第一次出现指定元素的位置
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;
}
//返回最后一次出现元素的位置
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
//弹出第一个元素的值
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取第一个元素
public E element() {
return getFirst();
}
//弹出第一元素,并删除
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除第一个元素
public E remove() {
return removeFirst();
}
//添加到尾部
public boolean offer(E e) {
return add(e);
}
//添加到头部
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
//插入到最后一个元素
public boolean offerLast(E e) {
addLast(e);
return true;
}
//队列操作
//尝试弹出第一个元素,但是不删除元素
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//队列操作
//尝试弹出最后一个元素,不删除
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//弹出第一个元素,并删除
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//弹出最后一个元素,并删除
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//如队列,添加到头部
public void push(E e) {
addFirst(e);
}
//出队列删除第一个节点
public E pop() {
return removeFirst();
}
//删除指定元素第一次出现的位置
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//删除指定元素最后一次出现的位置
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//遍历方法
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
//内部类,实现ListIterator接口
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
//静态内部类,创建节点
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
* @since 1.6
*/
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
/**
* Adapter to provide descending iterators via ListItr.previous
*/
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
@SuppressWarnings("unchecked")
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
/**
* Returns a shallow copy of this {@code LinkedList}. (The elements
* themselves are not cloned.)
*
* @return a shallow copy of this {@code LinkedList} instance
*/
public Object clone() {
LinkedList<E> clone = superClone();
// Put clone into "virgin" state
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// Initialize clone with our elements
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
private static final long serialVersionUID = 876323262645176354L;
//将对象写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
//从输入流中将对象读出
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
}
3)Vector类
实现了可扩展的对象数组和ArrayList,(古老的类jdk1.0, 基于线程安全,性能低,不用)
底层是一种可增长对象数组,查询快,增删慢
线程安全,同步,执行效率低
2.Collection子接口(二)Set
Set接口: 存储无序的,不可重复的元素
- 无序性 指的是底层存储的位置是无序的。
- 不可重复性,当向set集合中添加相同的元素,后面的这个不能添加进去
说明 :向set集合中添加所在的类,一定要重写equals()和hashCode()进而保证set中的元素不重复
提问:set中的元素是怎么存储的?
使用了hash算法,向set集合中添加对象时,首先调用此对象所在类的hashCode方法,此对象的哈希值决定了此对象的存储位置,如果哈希值一样,它要验证equals,此时equals方法返回true两个对象是同一个同象不能重复添加;如果哈希值不一样,不会验equals 直接存储. set集合存储(自定义类型)元素,一定要重写hashCode和equals()
1)HashSet(散列存放)
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变,此类允许使用null元素。 在HashSet中,元素都存到HashMap键值对的Key上面,而Value时有一个统一的值private static final Object PRESENT = new Object();(定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。)
- 不允许出现重复的值;
- 不保证集合中元素的顺序;
- 允许包含值为null的元素,但最多只能有一个null元素。
总结:HashSet底层就是HashMap,是一个个Entry组成的数组,K是元素,V是一个static final的虚拟的Object对象。
2)LinkedHashSet
LinkedHashSet底层就是LinkedHashMap,使用链表维护了散列表。
1.LinkedHashSet是继承自HashSet,所以具有HashSet的所有特征,并且增加了一些自己独有的特征;
2.底层使用链表维护了集合中的顺序,导致当我们遍历LinkedHashSet集合元素时,是按照添加的顺序遍历的。
3.插入性能低于hashSet,但在迭代访问Set里的全部元素有很好性能
3)TreeSet
底层采用二叉树(红黑树)为存储结构,基于TreeMap
特点 :
- 向TreeSet中添加的元素必须是同一个类型的。
- 对于基本数据类型,包装类型数据,TreeSet会自动排序;而对于Object类型的数据必须要指定遍历顺序,否则会有运行错误;
- 可以按照添加进集合元素指定的顺序遍历。像String,包装类型 从小到大顺序
- 自定义类型(Person),不做任何处理会报错
- (自然排序)实现Comparable接口 compareTo(Object o)方法按照属性排序
- (定制排序)实现Comparator接口 compare(Object o1,Object o2)方法按照属性排序、
优点:有序,查询速度比List快(根据内容查)
缺点:查询速度没有HashSet速度快
1.定制排序实现:
匿名类实现:
@Test
public void mannager() {
TreeSet set=new TreeSet(new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// if (o1 instanceof Stu4&&o2 instanceof Stu4) {
// Stu4 e1=((Stu4)o1);
// Stu4 e2=((Stu4)o2);
// return e1.name.compareTo(e2.name);
// }
// return 0;
// }
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Stu4&&o2 instanceof Stu4) {
Stu4 e1=((Stu4)o1);
Stu4 e2=((Stu4)o2);
if (e1.name.compareTo(e2.name)==0) {
return e1.age-e2.age;
}
return e1.name.compareTo(e2.name);
}
return 0;
}
});
}
2.定义类实现Comparator接口,将这个类的对象作为TreeSet的有参构造函数的参数
(TreeSet set=new TreeSet(new comp());
class comp implements Comparator{
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Stu4&&o2 instanceof Stu4) {
Stu4 e1=((Stu4)o1);
Stu4 e2=((Stu4)o2);
if (e1.name.compareTo(e2.name)==0) {
return e1.age-e2.age;
}
return e1.name.compareTo(e2.name);
}
return 0;
}
}
2.自然排序实现
类要实现Comparable接口,并且重写comparaTo()方法
class Stu4 implements Comparable{
Integer age;
String name;
public Stu4(int age,String name) {
this.age=age;
this.name=name;
}
public String toString() {
return "[姓名:"+name+",年龄:"+age+"]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Stu4 other = (Stu4) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public int compareTo(Object o) {
if (o instanceof Stu4) {
Stu4 s=((Stu4)o);
if (this.age.compareTo(s.age)==0) {
return s.name.compareTo(this.name);
}else {
return this.age.compareTo(s.age);
}
}
return 0;
}
}
定制排序和自然排序的区别:
1.定制排序要实现Comparator接口,并且重写compare()方法;自然排序要实现Comparable接口,并且重写compareTo()方法;
2.定制排序和自然排序是适用于Object类型元素的排序,自然排序要写在所排序的类里面,而定制排序不需要,只需要将指定的将实现Comparator接口的类的对象放入TreeSet构造函数中当做参数就可以;
3.对于自然排序,当在TreeSet添加数据时,首先调用重写的compareTo()方法,如果返回0,就算是不同的元素也无法加入集合;然后再调用hashCode()方法,如果的得到的hash值不同,就不再调用equals()方法,如果得到的hash值相同,就再调用equals()方法,如果返回的是false才会添加;总结:对于自然排序要经历三道关卡—>compareTo()->hashCode()->equals();对于定制排序,和自然排序类似,也要经历三道关卡—>compare()->hashCode()->equals();
4.自然排序是是对添加的数据,选择排序是对于TreeSet集合;
**compareTo()是String类的方法,用来比较两个字符串大小
三:Map家族
1.HashMap是Map的主要实现类 以键-值对,允许使用null键null值;查询、插入、和删除比较高效;
2.LinkedHashMap:使用链表维护添加进Map中的顺序。故遍历Map时,是按添加的顺序遍历的;
3.TreeMap在遍历有序的键值时十分有效;按照添加进Map中的元素的key的指定属性进行排序。要求:key必须是同一个类的对象!
针对key:自然排序 vs 定制排序
4.Properties一般用于操作属性文件,键和值都为String类型的
5.Hashtable:古老的实现类,线程安全,不建议使用。
1)HashMap
HashMap原理:
HashMap是一个神奇的东西,它用到了hash表,数组,链表,红黑树的数据结构。
HashMap中,主体的数据结构是一个个entry组成的数组,每一个entry有一个指针,指向链表的下一个节点。数组+链表的数据结构完美的解决了hash冲突的问题,简单理解hash冲突就是hash取模,可能会相同,这就产生了hash冲突,如果加上了链表,hash取模的下标的entry的next如果为空,就直接插入,如果不为空,就插入到链表中。
虽然链表解决的了hash冲突问题,但是如果数据量过大,而桶表也就是数组比较小,那样会造成链表的长度过长,这样一来,进行查找操作的时候时间复杂度过高,于是红黑树来了。
红黑树是一个完美的平衡二叉树,平衡二叉树的遍历比较高效,当链表的长度超过8的时候,链表会转红黑树,元素小6的时候又会转回链表。
HashMap常用方法:
public class hasHmap {
public static void main(String[] args) {
ha h1=new ha(1, 1);
ha h2=new ha(2, 2);
HashMap<Integer,Object> H=new HashMap<Integer,Object>();
H.put(1, h1);
H.put(2, 2);
H.put(3, h2);//添加一个映射
System.out.println(H.entrySet());
System.out.println("=====================================");
HashMap H1=new HashMap();
H1.put(4, "四");
H1.put(5, "五");
H1.put(6, "六");
H.putAll(H1);//添加一个地图
System.out.println(H.entrySet());//返回整个地图
System.out.println(H.clone());//返回整个底图的浅拷贝
System.out.println("=====================================");
Set s=H.entrySet();
Iterator it=s.iterator();
while(it.hasNext()) {
System.out.println(it.next());//迭代器遍历
}
System.out.println("=====================================");
for (Object object : s) {//foreach()遍历
System.out.println(object);
}
System.out.println(H.get(3));//使用get()方法取值
H.replace(1, "替换");//使用replace()方法替换
System.out.println(H.get(1));
H.replace(2, 2, "二");
System.out.println(H.get(2));//只有当键与值相等才替换
System.out.println(H.remove(4, "4"));//键值匹配才删除
H.remove(4, "四");
System.out.println(H.get(4));//成功删除返回null
}
}
class ha{
int a;
int c;
public String toString() {
return a+"and"+c;
}
public ha(int a, int c) {
super();
this.a = a;
this.c = c;
}
注意:
1.HashMap中数据是无序的,但是输出是有序的;这里无序的概念是指不会记录插入和添加的顺序,但是存储位置是根据hashCode()来计算,
2.hashMap的数组长度一定保持2的次幂
3.HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;
4.HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力。
5.HashMap的实例有两个参数影响其性能:初始容量和加载因子,初始化的容量是16,加载因子是3/4(当数组元素数量大于总容量的加载因子的时候,扩充数组)。当默认不是空的数组时,当达到加载因子的比例的时候,每次扩充初始容量的2倍
6.哈希表的大小是存在上限的,就是2的30次幂。当哈希表的大小到达该数值时候,之后就不再进行扩容,只是向链表中插入数据了
2)LinkHashMap
LinkedHashMap是HashMap的直接子类,二者唯一的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry连接起来,这样是为保证元素的迭代顺序跟插入顺序相同。
除了可以保迭代历顺序,这种结构还有一个好处:迭代LinkedHashMap时不需要像HashMap那样遍历整个table,而只需要直接遍历header指向的双向链表即可,也就是说LinkedHashMap的迭代时间就只跟entry的个数相关,而跟table的大小无关。
有两个参数可以影响LinkedHashMap的性能:初始容量(inital capacity)和负载系数(load factor)。初始容量指定了初始table的大小,负载系数用来指定自动扩容的临界值。当entry的数量超过capacity*load_factor时,容器将自动扩容并重新哈希。对于插入元素较多的场景,将初始容量设大可以减少重新哈希的次数。
将对象放入到LinkedHashMap或LinkedHashSet中时,有两个方法需要特别关心:hashCode()和equals()。hashCode()方法决定了对象会被放到哪个bucket里,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是“同一个对象”。所以,如果要将自定义的对象放入到LinkedHashMap或LinkedHashSet中,需要*@Override*hashCode()和equals()方法。
3)Hashtable
- 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。HashTable和HashMap的数据结构是一样的,实际保存数据的还是Entry对象。
- Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
- Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。
- Hashtable的负载因子是0.75,初始容量是11;
- HashTable的每一个方法都加了synchronized锁,synchronized是一个重量级的锁。所以使用HashTable系统开销比较大
4)TreeMap
TreeMap实现了SotredMap接口,它是有序的集合。而且是一个红黑树结构,每个key-value都作为一个红黑树的节点。如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。出于性能原因,TreeMap是非同步的(not synchronized),如果需要在多线程环境使用,需要程序员手动同步;
四:面试题
面试题:hashmap和hashtable的区别?
1.继承的父类不同
HashMap继承自AbstractMap,而HashTable继承自Dictionary类,不过他们都实现了S erializable、Map、Cloneable这三个接口
2.对Null key和Null Value的支持不同
HashTable不支持Null值
HashMap中支持Null值,null作为主键,只有一个;但是null可以作为一个或多个键所对应的值。
3.线程安全性不同
HashTable是线程安全的,每个方法都加入了Synchronized,在多线程并发的环境下,可以直接使用HashTable
HashMap是线程不安全的,在多线程的环境下,可能产生死锁的问题,因此在使用HashMap时需要自己增加同步处理。但是HashMap比HashTable的效率高很多
4.遍历方式的内部实现上不同
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
fail-fast(快速失败)机制是一种错误检测机制,它仅用来检测错误。
5.初始容量与每次扩充容量的大小不同
HashTable默认的初始容量为11,每次扩充变为原来的2n+1.
HashMap默认的初始容量为16,每次扩充为原来的2倍
6.计算hash值的方法不同
HashMap为了得到元素的位置,首先根据元素key计算出一个hash值,然后再用hash中计算最终的位置。
HashTable直接使用对象的hashCode。hashCode是根据对象的地址、或者字符串、或者数字计算出来的int类型的数值。然后再使用除留余数法来获得最终的位置
集合 | 负载因子 | 初始容量 | 扩容 |
---|---|---|---|
HashMap | 0.75 | 16 | 2n |
HashTable | 0.75 | 11 | 2n+1 |
ArrayList | 1 | 0(第一次添加变为10) | 1.5n |
StringBuffer | 1 | 16 | 2n+2 |