@生活冷战士 #和我一起去战斗吧!!!学习Java进阶的朋友一定要锁定噢,@我噢,我为你解答。
一、认识集合
为解决数组的缺陷:
- 数组长度一旦确定无法更改
- 数组长度无法与size相等
二、List接口
- 可以储存有序,可重复,有索引,可为null的对象。
- ArrayList类可动态储存数据
- ArrayList类 方法
4. ArrayList类代码操作
public class ArrayListDemo {
public static void main(String[] args) {
System.out.println("crate a list--");
ArrayList list=new ArrayList();
list.add("黄信");
list.add("青信");
list.add("黄成");
for (int i = 0; i < list.size(); i++) {
String name=(String) list.get(i);
System.out.println(name);
}
System.out.println(list.contains("xiaoming"));
list.remove(0);
list.set(0, "宋江");
for (Object obj : list) {
String name=(String) obj;
System.out.println(name);
}
int i=list.indexOf("黄河");
System.out.println(i);
list.clear();
System.out.println("是否有元素");
System.out.println(list.isEmpty());
}
}
public class NewTitle {
private int id;
private String titleName;
private String author;
public NewTitle(int id, String titleName, String author) {
super();
this.id = id;
this.titleName = titleName;
this.author = author;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitleName() {
return titleName;
}
public void setTitleName(String titleName) {
this.titleName = titleName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public class Student {
//field
private int age;
private String name;
private String gender;
//method
public Student(int age,String name,String gender) {
super();
this.name=name;
this.age=age;
this.gender=gender;
}
//重写equals
@Override
public boolean equals(Object obj) {
//是否是同一引用
if (this == obj)
return true;
//判断obj是否为空
if (obj == null)
return false;
//obj是否属于当前类的对象
if (!(obj instanceof Student)) {
return false;
}
//转换为当前对象
Student s = (Student) obj;
//判断每一属性,基本类型用==,有引用类型用equals
if (this.name.equals(s.name)&&this.age==s.age&&this.gender.equals(s.gender)) {
return true;
}
//前面都没有进去
return false;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + ", gender=" + gender + "]";
}
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TestStudent {
public static void main(String[] args) {
Student s1=new Student(25, "lrving", "man");
Student s2=new Student(34, "james", "man");
Student s3=new Student(31, "curry", "man");
ArrayList <Student> list=new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
Iterator <Student>it=list.iterator();
while (it.hasNext()) {
Student student=it.next();
System.out.println(student);
}
boolean a=s1.equals(new Student(25, "lrving", "man"));
System.out.println(a);
int as=list.lastIndexOf(s3);
System.out.println(as);
}
}
5、ArrayList的底层实现
package cn.itlaobing.list;
import java.util.Arrays;
import java.util.Objects;
public class MyArray {
/**
* 定义数组,保存数据
*/
private Object[] objects = null;
/**
* 默认数组长度
*/
private final int DEFAULT_LENGTH = 10;
/**
* 数组长度
*/
private int length;
/**
* 数组元素个数
*/
private int size = 0;
/**
* 长度的倍数
*/
private final int LOAD = 2;
/**
* 默认构造函数,创建数组
*/
/*
* trimsize中计数
*/
private int modCount;
public MyArray() {
length = DEFAULT_LENGTH;
objects = new Object[length];
}
/**
* 构造函数,根据用户传入长度创建数组
*
* @param length
*/
public MyArray(int length) {
this.length = length;
objects = new Object[length];
}
/**
* 如果长度等于大小时,扩容。
*/
private void addLength() {
if (size == length) {
// 元素个数等于数组长度,需要扩容
length = length * LOAD;
objects = Arrays.copyOf(objects, length);
}
}
/*
* 向数组中添加元素
*/
public boolean add(Object o) {
// 判断是否扩容
addLength();
objects[size] = o;
size++;
return true;
}
/**
* 添加数据
*/
public boolean add(int index, Object o) {
if (index < 0 || index > size - 1) {
throw new ArrayIndexOutOfBoundsException("越界");
} else {
// 判断是否需要扩容
addLength();
System.arraycopy(objects, index, objects, index + 1, size - index - 1);
objects[index] = o;
size++;
}
return true;
}
/**
* 在索引处移除
* @param index
* @return
*/
public Object remove(int index) {
Object o = null;
if (index < 0 || index > size - 1) {
throw new ArrayIndexOutOfBoundsException("越界");
} else {
o = objects[index];
System.arraycopy(objects, index + 1, objects, index, size - index - 1);
size--;
objects[size] = null;
}
return o;
}
/**
* 让长度length与个数size相等
*/
public void trimToSize() {
modCount++;
if (size < objects.length) {
objects = (size == 0) ? objects : Arrays.copyOf(objects, size);
}
}
/**
* 重写tostring
*/
/**
*
* @param o
* @return
*/
//打印数组方法一
public String toString(Object o) {
StringBuffer sb = new StringBuffer("[");
for (int i = 0; i < size; i++) {
sb.append(objects[i]);
if(i!=size-1)
sb.append(",");
}
sb.append("]");
return sb.toString();
}
/**
* 获得元素的个数
* @return
*/
public int size() {
return size;
}
/**
* 获得索引处的元素
* @param index
* @return
*/
public Object get(int index) {
if (index<0&&index>size) {
throw new ArrayIndexOutOfBoundsException("越界");
}
return objects[index];
}
/**
* 索引元素所在的位置
* @param o
* @return
*/
public int indexOf(Object o) {
if (o==null) {
return -1;
}
for (int i = 0; i < size; i++) {
if (o.equals(objects[i])) {
return i;
}
}
return -1;
}
/**
* 判断是否包含某个元素
* @param o
* @return
*/
public boolean contains(Objects o) {
int i=indexOf(o);
return i>=0?true:false;
}
/**
* 移除所有元素
* @return
*/
public boolean removeAll() {
Arrays.fill(objects, null);
size=0;
return true;
}
/**
* 更新数组
* @param index
* @param o
* @return
*/
public boolean update(int index,Object o) {
if (index<0&&index>size) {
throw new ArrayIndexOutOfBoundsException("越界");
}
objects[index]=o;
return true;
}
/**
* 打印数组方法二
*/
public String toString() {
StringBuffer sb=new StringBuffer("[");
for (int i = 0; i < size; i++) {
sb.append(objects[i]);
sb.append(",");
}
sb.append("]");
return sb.toString().replace(",]", "]");
}
}
5、使用lLinkedList类动态储存数据
- LinkedList类采用链表储存数据
- 新增加的方法
- 代码操作
package cn.itlaobing.list;
import java.util.LinkedList;
public class DemoLinkedList {
public static void main(String[] args) {
NewTitle car1 = new NewTitle(1, "沃尔沃", "姚盖");
NewTitle car2 = new NewTitle(2, "凯迪拉克", "姚明");
NewTitle car3 = new NewTitle(3, "甲壳虫", "小佳");
NewTitle car4 = new NewTitle(4, "MINI", "宋二娘");
LinkedList<NewTitle> newsLinkedList = new LinkedList<NewTitle>();
newsLinkedList.add(car2);
newsLinkedList.add(car3);
newsLinkedList.add(car4);
newsLinkedList.add(car1);
NewTitle first = (NewTitle) newsLinkedList.getFirst();
System.out.println(first.getTitleName());
NewTitle last = (NewTitle) newsLinkedList.getLast();
System.out.println(last.getTitleName());
newsLinkedList.removeFirst();
newsLinkedList.removeLast();
System.out.println("first and last remove");
System.out.println("遍历所有新闻标题");
for (Object object : newsLinkedList) {
NewTitle newTitle = (NewTitle) object;
System.out.println(newTitle.getTitleName());
}
}
}
4、底层实现
package cn.itlaobing.list;
import java.security.PublicKey;
import java.util.Iterator;
import java.util.LinkedList;
import javax.xml.soap.Node;
public class MyLinklist<E> {
/**
* 链表元素个数
*/
private int size;
/**
* 链表首节点
*/
private Node first;
/**
* 链表尾节点
*/
private Node last;
/**
* 默认构造创建一个空链表
*/
public MyLinklist() {
size = 0;
first = null;
last = null;
}
private void checkLinkError(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("访问越界");
}
}
/**
* 创建一个节点
*
* @author 神州战神
*
* @param <E>
*/
class Node<E> {
private Node<E> pre;
private E data;
private Node<E> next;
public Node(Node<E> pre, E data, Node<E> next) {
super();
this.pre = pre;
this.data = data;
this.next = next;
}
}
/**
* 在链表末尾添加元素
*/
private void linkLast(E e) {
// 1、创建新节点
// 2、将新节点指向原来的尾节点
Node<E> newNode = new Node<E>(last, e, null);
if (last == null) {
last = newNode;
first = last;
} else {
// 3、将原来的尾节点指向新节点
last.next = newNode;
// 4、将新节点变成尾节点
last = newNode;
}
size++;
}
/**
* addLst方法
*/
public void addLast(E e) {
linkLast(e);
}
/**
* 获取链表大小
*
* @return
*/
public int size() {
return size;
}
/**
* 在链表表头添加元素
*/
private void linkfirst(E e) {
// 1、创建首节点
// 2、将新节点指向首节点
// 3、原先的首节点pre指向新节点
// 4、将新节点变为首节点
Node<E> newNode = new Node<E>(null, e, first);
if (first == null) {
last = newNode;
first = newNode;
} else {
first.pre = newNode;
first = newNode;
}
size++;
}
/**
* addFirst方法
*
* @param e
*/
public void addFirst(E e) {
linkfirst(e);
}
/**
* 链表中间添加
*
* @param index
* @param e
*/
private void linkafter(int index, E e) {
checkLinkError(index);
// 1、新建节点Node;先找到索引处节点node,
Node<E> node = new Node(null, e, last);
if (size == 0 && index == 0) {
first = node;
}
// 当来链表只有一个元素或者索引为最后一个元素时
else if (size == 1 || index == size - 1) {
last.next = node;
} else {
// 获得索引处节点
Node<E> indexnode = getNodeByIndex(index);
// 2、下个节点为Next
Node<E> Next = indexnode.next;
// 新建节点,pre指向索引处节点,next指向下节点Next
Node<E> newNode = new Node(indexnode, e, Next);
// 索引处节点指向新建节点
indexnode.next = newNode;
// 下节点指向新建节点
Next.pre = newNode;
}
size++;
}
public void add(int index, E e) {
linkafter(index, e);
}
/**
* 删除索引处元素
*
* @param index
* @return
*/
private boolean remove(int index) {
checkLinkError(index);
if (size == 1 || size == 0) {
first = null;
last = null;
} else if (index == 0) {
Node<E> removeNode = first;
first = first.next;
first.pre = null;
removeNode.next = null;
} else if (index == size - 1) {
Node<E> removeNode = last;
last = last.pre;
last.next = null;
removeNode.pre = null;
} else {
// 1、找到索引处的节点
Node<E> Node = getNodeByIndex(index);
// 2、找到索引处下一个节点
Node<E> Next = Node.next;
// 3、找到索引处前置节点
Node<E> Pre = Node.pre;
// 4、将索引处前个节点指向Next节点
Pre.next = Next;
// 4、将Next节点指向索引处前个节点
Next.pre = Pre;
}
size--;
return true;
}
/**
* remove方法
*
* @param index
*/
public void Remove(int index) {
remove(index);
}
/**
* 修改索引处的节点
*
* @param index
* @param e
* @return
*/
private void set(int index, E e) {
Node<E> node = first;
for (int i = 0; i < size && node != null; i++, node = node.next) {
if (i == index) {
node.data = e;
break;
}
}
}
/**
* Set方法
*
* @param index
* @param e
* @return
*/
public void Set(int index, E e) {
set(index, e);
}
/**
* 根据索引找到节点
*/
private Node<E> getNodeByIndex(int index) {
checkLinkError(index);
if (index == 0) {
return first;
} else if (index == size - 1) {
return last;
} else {
Node<E> current = first;
for (int i = 0; i < size && current != null; i++) {
if (i == index) {
return current;
}
current = current.next;
}
}
return first;
}
/**
* 根据索引找到节点
*/
public E get(int index) {
Node current = getNodeByIndex(index);
return (E) current.data;
}
/**
* 展示链表
*
* @return
*/
@Override
public String toString() {
Node<E> p = first;
StringBuffer sb = new StringBuffer("{");
for (int i = 0; i < size && p != null; i++, p = p.next) {
sb.append(p.data);
if (i != size - 1) {
sb.append(",");
}
}
sb.append("}");
return sb.toString();
}
}
三、Set接口
- 储存一组唯一、无序的对象。
- HashSet类动态储存数据
- HashSet类方法
- 代码操作
1、
import java.util.LinkedList;
public class DemoHashSet {
public static void main(String[] args) {
NewTitle car1 = new NewTitle(1, "沃尔沃", "姚盖");
NewTitle car2 = new NewTitle(2, "凯迪拉克", "姚明");
NewTitle car3 = new NewTitle(3, "甲壳虫", "小佳");
NewTitle car4 = new NewTitle(4, "MINI", "宋二娘");
LinkedList<NewTitle> newsLinkedList = new LinkedList<NewTitle>();
newsLinkedList.add(car1);
newsLinkedList.add(car2);
newsLinkedList.add(car3);
newsLinkedList.add(car4);
System.out.println(newsLinkedList.size());
System.out.println(newsLinkedList.contains(car3));
newsLinkedList.remove();
System.out.println();
System.out.println(newsLinkedList.isEmpty());
for (Object obj : newsLinkedList) {
NewTitle newTitle=(NewTitle) obj;
System.out.println(newTitle.getTitleName());
}
}
}
2、Set练习
public class HashSetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
//添加元素
set.add("陈忠实");
set.add("贾平凹");
set.add("路遥");
set.add("张爱玲");
set.add("林徽因");
boolean a = set.add("徐志摩");
System.out.println(a);
boolean b = set.add("徐志摩");
System.out.println(b);
//set遍历 显示set的无序性
//foreach
for (String s : set) {
System.out.println(s);
}
//iterator
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
package cn.itlaobing.java;
public class Student {
private int age;
private String name;
private String gender;
public Student(int age, String name, String gender) {
super();
this.age = age;
this.name = name;
this.gender = gender;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + ", gender=" + gender + "]";
}
@Override
public int hashCode() {
return age*17+name.hashCode()*11+gender.hashCode()*31;
}
@Override
public boolean equals(Object obj) {
//1.判断是否是同一引用
if(this==obj) {
return true;
}
//2.判断是否为null
if(obj==null) {
return false;
}
//3.判断是否为同一类型
if(!(obj instanceof Student)) {
return false;
}
//4.向下转型
Student s = (Student) obj;
//5.比较实例变量
if(this.age==s.age&&this.name.equals(s.name)&&this.gender.equals(s.gender)) {
return true;
}
return false;
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student(18, "翠花", "男");
Student s2 = new Student(18, "翠花", "男");
Student s3 = new Student(20, "达叔", "男");
HashSet<Student> set = new HashSet<>();
String a = new String("佟年");
Set<String> set2 = new HashSet<>();
set2.add("佟年");
// boolean d = set2.remove("佟年");
boolean k = set2.remove("年");
System.out.println(k);
// boolean s = set2.contains(a);
// System.out.println(s);
//
// set.add(s1);
// set.add(s2);
// set.add(s3);
// set.clear();
// int i = set.size();
// set.isEmpty();
// System.out.println(i);
}
5.底层实现
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set;
import javax.swing.text.html.HTMLDocument.Iterator;
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
// 底层使用HashMap来保存HashSet中所有元素。
private transient HashMap<E,Object> map;
// 定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final。
private static final Object PRESENT = new Object();
// 默认的无参构造器,构造一个空的HashSet。
//
// 实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
public HashSet() {
map = new HashMap<E,Object>();
}
// 构造一个包含指定collection中的元素的新set。
//
// 实际底层使用默认的加载因子0.75和足以包含指定
// collection中所有元素的初始容量来创建一个HashMap。
// @param c 其中的元素将存放在此set中的collection。
public HashSet(Collection<? extends E> c) {
map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
// 以指定的initialCapacity和loadFactor构造一个空的HashSet。
//
// 实际底层以相应的参数构造一个空的HashMap。
// @param initialCapacity 初始容量。
// @param loadFactor 加载因子。
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
// 以指定的initialCapacity构造一个空的HashSet。
//
// 实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。
// @param initialCapacity 初始容量。
public HashSet(int initialCapacity) {
map = new HashMap<E,Object>(initialCapacity);
}
// 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。
// 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。
//
// 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。
// @param initialCapacity 初始容量。
// @param loadFactor 加载因子。
// @param dummy 标记。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}
// 返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。
//
// 底层实际调用底层HashMap的keySet来返回所有的key。
// 可见HashSet中的元素,只是存放在了底层HashMap的key上,
// value使用一个static final的Object对象标识。
// @return 对此set中元素进行迭代的Iterator。
public Iterator<E> iterator() {
return map.keySet().iterator();
}
// 返回此set中的元素的数量(set的容量)。
//
// 底层实际调用HashMap的size()方法返回Entry的数量,就得到该Set中元素的个数。
// @return 此set中的元素的数量(set的容量)。
public int size() {
return map.size();
}
// 如果此set不包含任何元素,则返回true。
//
// 底层实际调用HashMap的isEmpty()判断该HashSet是否为空。
// @return 如果此set不包含任何元素,则返回true。
public boolean isEmpty() {
return map.isEmpty();
}
// 如果此set包含指定元素,则返回true。
// 更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e))
// 的e元素时,返回true。
//
// 底层实际调用HashMap的containsKey判断是否包含指定key。
// @param o 在此set中的存在已得到测试的元素。
// @return 如果此set包含指定元素,则返回true。
public boolean contains(Object o) {
return map.containsKey(o);
}
// 如果此set中尚未包含指定元素,则添加指定元素。
// 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2))
// 的元素e2,则向此set 添加指定的元素e。
// 如果此set已包含该元素,则该调用不更改set并返回false。
//
// 底层实际将将该元素作为key放入HashMap。
// 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key
//与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true),
//新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变,
// 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中,
// 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。
// @param e 将添加到此set中的元素。
// @return 如果此set尚未包含指定元素,则返回true。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// 如果指定元素存在于此set中,则将其移除。
// 更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e,
// 则将其移除。如果此set已包含该元素,则返回true
// (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。
//
// 底层实际调用HashMap的remove方法删除指定Entry。
// @param o 如果存在于此set中则需要将其移除的对象。
// @return 如果set包含指定元素,则返回true。
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
// 从此set中移除所有元素。此调用返回后,该set将为空。
//
// 底层实际调用HashMap的clear方法清空Entry中所有元素。
public void clear() {
map.clear();
}
// 返回此HashSet实例的浅表副本:并没有复制这些元素本身。
// 底层实际调用HashMap的clone()方法,获取HashMap的浅表副本,并设置到HashSet中。
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
}
对实现原理进行一个总结:
(1)基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
(2)当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
(3)HashSet的其他操作都是基于HashMap的。
补充:
*重写hashcode规则
1、同一对象多次调用hashcode返回值应该相同。
2、equals返回true,hashcode返回值也应该相同。
3、所参与equals的实例变量,也应该参与重写hashcode
*重写equals的步骤
1、判断是否为同一引用
2、判断是否为同一类型
3、向下转型
4、比较成员变量(引用equals基本为==)
*重写equals的特性
1、自反性:x.equals(x)
2、对称性:x->y,y->x
3、传递性:x->y,y->z,x->z
4、一致性:多次调用,值一致。
5、任何非空x,x.equals(null)返回false
四、Iterator接口
- hasNext():判断是否存在下一个可以访问的元素,如果仍有元素可迭代,则返回true。
- next():返回要访问的下一个元素。
Iterator <Student>it=list.iterator();
while (it.hasNext()) {
Student student=it.next();
System.out.println(student);
}
Map接口
- HashMap:
HashMap基于Map接口实现,元素以键值对的方式存储,并且允许使用null 建和null值,因为key不允许重复,因此只能有一个键为null,另外HashMap不能保证放入元素的顺序,它是无序的,和放入的顺序并不能相同。HashMap是线程不安全的。 - 继承关系:
public class HashMap<K,V>extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
基本属性:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始化大小 16
static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子0.75
static final Entry<?,?>[] EMPTY_TABLE = {}; //初始化的默认数组
transient int size; //HashMap中元素的数量
int threshold; //判断是否需要调整HashMap的容量
注:HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容。HashMap的线程是不安全的,多线程环境中推荐是ConcurrentHashMap。
3. 方法
3. 遍历
//利用keySet()方法获取keySet
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("乔峰",30);
map.put("虚竹", 20);
map.put("李秋水", 20);
map.put("天山童姥", 60);
map.put("梦姑",18);
//先获取keySet
Set<String> set = map.keySet();
//获取迭代器
Iterator<String> it = set.iterator();
//利用迭代器遍历
while (it.hasNext()) {
String key = it.next();
int i = map.get(key);
System.out.println("key==========="+key);
System.out.println("value========="+i);
}
}
//利用entrySet()获取entrySet
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("乔峰",30);
map.put("虚竹", 20);
map.put("李秋水", 20);
map.put("天山童姥", 60);
map.put("梦姑",18);
//获取键值对集合
Set<Entry<String, Integer>> set = map.entrySet();
//遍历set
for (Entry<String, Integer> entry : set) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("key=========="+key);
System.out.pintln("key=========="+value);
}
}
HashMap和Hashtable的区别
1、线程安全
两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全。
Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。
注:
Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理。
2、针对null的不同
HashMap可以使用null作为key,而Hashtable则不允许null作为key
虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事。
注:HashMap以null作为key时,总是存储在table数组的第一个节点上。
3、继承结构
HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类。
4、初始容量与扩容
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
HashMap扩容时是当前容量翻倍即:capacity2,Hashtable扩容时是容量翻倍+1即:capacity2+1。
5、两者计算hash的方法不同
Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模。
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
HashMap的数据存储结构
1、HashMap由数组和链表来实现对数据的存储
HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题。
数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;
链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。
数据结构由数组+链表组成,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key.hashCode())%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。
HashMap里面实现一个静态内部类Entry,其重要的属性有 hash,key,value,next。
HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); //null总是放在数组的第一个链表中
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//遍历链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key在链表中已存在,则替换为新value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next
//如果size超过threshold,则扩充table大小。再散列
if (size++ >= threshold)
resize(2 * table.length);
}
重要方法深度解析
1、构造方法
HashMap() //无参构造方法
HashMap(int initialCapacity) //指定初始容量的构造方法
HashMap(int initialCapacity, float loadFactor) //指定初始容量和负载因子
HashMap(Map<? extends K,? extends V> m) //指定集合,转化为HashMap
HashMap提供了四个构造方法,构造方法中 ,依靠第三个方法来执行的,但是前三个方法都没有进行数组的初始化操作,即使调用了构造方法此时存放HaspMap中数组元素的table表长度依旧为0 。在第四个构造方法中调用了inflateTable()方法完成了table的初始化操作,并将m中的元素添加到HashMap中。
2、添加方法
public V put(K key, V value) {
if (table == EMPTY_TABLE) { //是否初始化
inflateTable(threshold);
}
if (key == null) //放置在0号位置
return putForNullKey(value);
int hash = hash(key); //计算hash值
int i = indexFor(hash, table.length); //计算在Entry[]中的存储位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i); //添加到Map中
return null;
}
在该方法中,添加键值对时,首先进行table是否初始化的判断,如果没有进行初始化(分配空间,Entry[]数组的长度)。然后进行key是否为null的判断,如果key==null ,放置在Entry[]的0号位置。计算在Entry[]数组的存储位置,判断该位置上是否已有元素,如果已经有元素存在,则遍历该Entry[]数组位置上的单链表。判断key是否存在,如果key已经存在,则用新的value值,替换点旧的value值,并将旧的value值返回。如果key不存在于HashMap中,程序继续向下执行。将key-vlaue, 生成Entry实体,添加到HashMap中的Entry[]数组中。
3、addEntry()
/*
* hash hash值
* key 键值
* value value值
* bucketIndex Entry[]数组中的存储索引
* /
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length); //扩容操作,将数据元素重新计算位置后放入newTable中,链表的顺序与之前的顺序相反
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
添加到方法的具体操作,在添加之前先进行容量的判断,如果当前容量达到了阈值,并且需要存储到Entry[]数组中,先进性扩容操作,空充的容量为table长度的2倍。重新计算hash值,和数组存储的位置,扩容后的链表顺序与扩容前的链表顺序相反。然后将新添加的Entry实体存放到当前Entry[]位置链表的头部。在1.8之前,新插入的元素都是放在了链表的头部位置,但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的元素都放在了链表的尾部。
4、获取方法:get
public V get(Object key) {
if (key == null)
//返回table[0] 的value值
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
在get方法中,首先计算hash值,然后调用indexFor()方法得到该key在table中的存储位置,得到该位置的单链表,遍历列表找到key和指定key内容相等的Entry,返回entry.value值。
5、删除方法
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
删除操作,先计算指定key的hash值,然后计算出table中的存储位置,判断当前位置是否Entry实体存在,如果没有直接返回,若当前位置有Entry实体存在,则开始遍历列表。定义了三个Entry引用,分别为pre, e ,next。 在循环遍历的过程中,首先判断pre 和 e 是否相等,若相等表明,table的当前位置只有一个元素,直接将table[i] = next = null 。若形成了pre -> e -> next 的连接关系,判断e的key是否和指定的key 相等,若相等则让pre -> next ,e 失去引用。
6、containsKey
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
containsKey方法是先计算hash然后使用hash和table.length取摸得到index值,遍历table[index]元素查找是否包含key相同的值。
7、containsValue
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
JDK 1.8的 改变
1、HashMap采用数组+链表+红黑树实现。
在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,下面来看一下这些改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。
2、put方法简单解析:
public V put(K key, V value) {
//调用putVal()方法完成
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断table是否初始化,否则初始化操作
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//计算存储的索引位置,如果没有元素,直接赋值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//节点若已经存在,执行赋值操作
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断链表是否是红黑树
else if (p instanceof TreeNode)
//红黑树对象操作
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//为链表,
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//链表长度8,将链表转化为红黑树存储
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//key存在,直接覆盖
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//记录修改次数
++modCount;
//判断是否需要扩容
if (++size > threshold)
resize();
//空操作
afterNodeInsertion(evict);
return null;
}
如果存在key节点,返回旧值,如果不存在则返回Null。
Collections类常用方法总结
- sort
对集合进行排序
1 public static <T extends Comparable<? super T>> void sort(List<T> list)
2
3 public static <T> void sort(List<T> list,Comparator<? super T> c)
在使用List时想根据List中存储对象的某一字段进行排序,那么我们要用到Collections.sort方法对list排序,用Collections.sort方法对list排序有两种方法:
- 第一种是list中的对象实现Comparable接口;
- 第二种方法是根据Collections.sort重载方法来实现。
示例如下:
public class SortTest {
public static void main(String[] args) {
List<String> listS = new ArrayList<String>();
List<Employer1> list1 = new ArrayList<Employer1>();
List<Employer2> list2 = new ArrayList<Employer2>();
List<Employer3> list3 = new ArrayList<Employer3>();
//一.将String类型的变量插入到listS中并排序
//listS中的对象String 本身含有compareTo方法,所以可以直接调用sort方法,按自然顺序排序,即升序排序
listS.add("5");
listS.add("2");
listS.add("9");
Collections.sort(listS);
//二.将Employer1类的对象插入到list1中并排序
//将已创建的实现了Comparator接口的比较类MyCompare传入Collections的sort方法中即可实现依照MyCompare类中的比较规则。
Employer1 a1 = new Employer1();
Employer1 b1 = new Employer1();
Employer1 c1 = new Employer1();
a1.setName("a1"); a1.setAge(44);
b1.setName("b1"); b1.setAge(55);
c1.setName("b1"); c1.setAge(33);
list1.add(a1);
list1.add(b1);
list1.add(c1);//Collections类的sort方法要求传入的第二个参数是一个已实现Comparator接口的比较器
Collections.sort(list1, new MyCompare());
//三.将Employer2类的对象插入到list2中并排序
//其实原理和上面的二类似,只是没有单独创建MyCompare类,而是用匿名内部类来实现Comparator接口里面的具体比较。
Employer2 a2 = new Employer2();
Employer2 b2 = new Employer2();
Employer2 c2 = new Employer2();
a2.setName("a2"); a2.setAge(66);
b2.setName("b2"); b2.setAge(33);
c2.setName("b2"); c2.setAge(22);
list2.add(a2);
list2.add(b2);
list2.add(c2); //Collections类的sort方法要求传入的第二个参数是一个已实现Comparator接口的比较器
Collections.sort(list2,new Comparator<Employer2>(){
@Override
public int compare(Employer2 a2, Employer2 b2) {
return a2.getOrder().compareTo(b2.getOrder());
}
});
//四.将Employer3类的对象插入到list3中并排序
//被排序的类Employer3实现了Comparable接口,在类Employer3中通过重载compareTo方法来实现具体的比较。
Employer3 a3 = new Employer3();
Employer3 b3 = new Employer3();
Employer3 c3 = new Employer3();
a3.setName("a3"); a3.setAge(77);
b3.setName("b3"); b3.setAge(55);
c3.setName("b3"); c3.setAge(99);
list3.add(a3);
list3.add(b3);
list3.add(c3);
Collections.sort(list3);//Collections类的sort方法要求传入的List中的对象是已实现Comparable接口的对象
System.out.println(listS);
System.out.println(list1);
System.out.println(list3);
System.out.println(list2);
}
}
class Employer1{
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override//重载了Object类里的toString方法,使之可以按照我们要求的格式打印
public String toString() {
return "name is "+name+" age is "+ age;
}
}
class MyCompare implements Comparator<Employer1> {
@Override//重载了Comparator接口里面的compare方法实现具体的比较
public int compare(Employer1 o1, Employer1 o2) {
return o1.getAge().compareTo(o2.getAge());
}
}
class Employer2{
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public Integer getOrder() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override//重载了Object类里的toString方法,使之可以按照我们要求的格式打印
public String toString() {
return "name is "+name+" age is "+age;
}
}
class Employer3 implements Comparable<Employer3>{
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override//重载了Object类里的toString方法,使之可以按照我们要求的格式打印
public String toString() {
return "name is "+name+" age is "+age;
}
@Override//重载了Comparable接口里的compareTo方法来实现具体的比较
public int compareTo(Employer3 a) {
return this.age.compareTo(a.getAge());
}
}