1、为什么使用集合?
1)数组的缺陷:定容[一旦数组定义好,数组的长度就无法改变],如果需要改变数组的长度,很复杂。
2)定义可变长度的容器。(自己手撕一个可变长的容器)
public class MyAry {
//定义一个object类型的数组
private Object[] arr;
//初始值为0,表示数组为空
private int size;
//无参构造,如果没有指定数组长度,默认调用本类的有参构造,指定初始值
public MyAry() {
this(3);
}
//有参构造,传入初始数组的长度
public MyAry(int initSize){
//判断传入参数的合法性
if (initSize < 0){
//手动抛出异常
throw new RuntimeException("数组定义不合法");
}
//给arr赋值(存储的是数组地址)
arr = new Object[initSize];
}
//向数组中添加元素
public void setData(Object obj){
//判断数组是否已满
if (size >= arr.length){
//创建新的数组,新数组长度为旧数组的2倍
Object[] newArr = Arrays.copyOf(arr, size * 2);
//改变arr中存储的内存地址,原来的旧数组会被GC(Garbage Collection)垃圾回收
arr = newArr;
}
//将数据添加到arr中
arr[size] = obj;
size++;
}
//获取指定位置的数据
public Object getData(int index){
//判断传入参数的合法性,是否下标越界
if (index >= arr.length){
throw new ArrayIndexOutOfBoundsException("下标越界");
}
//返回Object类型的数据
Object obj = arr[index];
return obj;
}
}
Java官网基于数组,根据不同的数据结构创建了多个类,这些类统称为集合框架。
集合框架图:
2、List集合
List是一个接口,ArrayList和LinkedList是List接口的实现类
1)ArrayList集合的基本操作
public class TestArrayList {
public static void main(String[] args) {
//创建一个ArrayList对象
List list = new ArrayList();//默认初始值为10
//集合添加元素 element为Object类型
list.add("aaa");
list.add(1);
list.add(new Date());
list.add(2.34);
System.out.println(list);
//在指定下标位置添加元素
list.add(2,"bbb");
System.out.println(list);
List list1 = new ArrayList();
list1.add("a");
list1.add("b");
//在集合尾部添加其他的集合
list.addAll(list1);
System.out.println(list);
//删除操作
list.remove(1);
System.out.println(list);
//清空集合
list1.clear();
System.out.println(list1);
//修改指定位置的数据
list.set(1,"ccc");
System.out.println(list);
//查询操作
//获取指定位置的元素
Object o = list.get(1);
System.out.println(o);
//集合中是否包含具体元素,返回值为Boolean类型
boolean gjx = list.contains("aaa");
System.out.println(gjx);
//获取指定元素在集合中第一次出现的下标位置
int index = list.indexOf("aaa");
System.out.println(index);
//获取指定元素最后一次出现的下标位置
int index1 = list.lastIndexOf("a");
System.out.println(index1);
//获取集合中的元素个数
int size = list.size();
System.out.println(size);
//普通的for循环输出list
for (int i = 0; i < size; i++) {
System.out.println(list.get(i));
}
System.out.println("=========================");
//for each 增强循环输出list
for (Object object : list) {
System.out.println(object);
}
}
}
2)相应源码
创建ArrayList对象
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//无参构造
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//有参构造,有初始长度
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
//有参构造,有初始内容
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
添加操作
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
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;
}
删除操作
//返回当前位置的值,并删除该位置的元素
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;
}
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;
}
修改操作
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
查询操作
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
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++)
if (o.equals(elementData[i]))
return i;
}
return -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;
}
//集合中元素的个数
public int size() {
return size;
}
3)LinkedList(链表结构)相关操作
public class TestLinkedList {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
//添加到尾部
linkedList.add("java1");
//添加到头部
linkedList.addFirst("java0");
//添加到尾部
linkedList.addLast("java2");
linkedList.add("java3");
System.out.println(linkedList);
//移除
linkedList.removeFirst();
System.out.println(linkedList);
linkedList.remove(0);
System.out.println(linkedList);
//修改
linkedList.set(1,"12");
System.out.println(linkedList);
//查询
int size = linkedList.size();
System.out.println(size);
boolean empty = linkedList.isEmpty();
System.out.println(empty);
boolean java1 = linkedList.contains("java1");
System.out.println(java1);
Object obj = linkedList.get(0);
System.out.println(obj);
Object first = linkedList.getFirst();
System.out.println(first);
Object last = linkedList.getLast();
System.out.println(last);
}
}
4)相关源码
linkedList是一个双向链表结构,增加和删除效率比较高,不用像ArrayList那样移动元素。
linkedList的构造方法
//节点个数
transient int size = 0;
//头节点
transient Node<E> first;
//尾节点
transient Node<E> last;
//空构造方法
public LinkedList() {
}
//Node对象的定义
private static class Node<E> {
//节点元素内容
E item;
//存储下一个节点的内存地址
LinkedList.Node<E> next;
//存储上一个节点的内存地址
LinkedList.Node<E> prev;
Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList的添加方法
//添加方法
public boolean add(E e) {
linkLast(e);
return true;
}
//linkLast方法
void linkLast(E e) {
//将最后一个节点赋值给l
final LinkedList.Node<E> l = last;
//创建一个新的节点对象,上一个节点指向l,元素为Object对象(e),下一个节点为空
final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
//让尾节点指向新创建的节点
last = newNode;
//如果当前对象的尾节点为空(即当前集合为空),让头节点也指向新建的节点对象
if (l == null)
first = newNode;
else
//如果尾节点不为空,就让当前元素的尾节点指向新创建的节点对象
l.next = newNode;
//节点个数加1
size++;
modCount++;
}
添加元素的过程如下:
- 新创建的LinkedList对象中有三个属性值,first初始值为null,last初始值为null,size初始值为0;
- 添加第一个元素element1,此时last的值为null,调用linkLast方法,此时l的值为null;
- 在内存中新创建一个Node对象newNode,Node对象有三个属性,依次是prev,item,next;传入的值依次为l(null),element1,null;
- 让当前对象的尾节点指向element1节点;
- 如果l的值为空,即当前集合为空,就让当前对象的first节点指向element1节点,节点数量++;
- 添加第二个元素element2,此时last为element1节点,将last赋值给l,新创建一个Node对象,Node对象有三个属性,依次是prev,item,next;传入的值依次为l(element1节点),element2,null;
- 让当前对象的last节点指向element2节点,判断l的值是否为空,如果不为空,就让l的下一个节点指向element2节点;节点数量++;
- 此时的结构为first节点为element1节点,element1的prev为null,next为element2节点,element2的prev为element1节点,next为null,last节点为element2节点
LinkedList获取元素
public E get(int index) {
//检查index下标是否正确
checkElementIndex(index);
//node对象有三个属性,分别为prev,item,next item存储的是具体内容,其他两项存储的是其他node对象的地址
return node(index).item;
}
//返回的值为node对象
LinkedList.Node<E> node(int index) {
// assert isElementIndex(index);
//“>>”:位运算二进制运算 size>>1的意思是一半也就相当于size/2
//如果下标小于size/2
if (index < (size >> 1)) {
//从第一个节点从前向后查找
LinkedList.Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//如果下标大于size/2
//从最后一个节点向前查找
LinkedList.Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
LinkedList查询效率低,原因是因为需要一个节点一个节点的查找,没有存储下标,不能直接根据下标找到相应的元素。
总结:
ArrayList的底层是数组结构,查询效率高,可以直接根据数组下标查询,但是增加和删除操作效率低,需要进行数据迁移。
LinkedList的底层是双向链表结构,增加和删除效率高,不需要移动数据,只需要改变其中节点的相应属性值即可,查询效率低,不能根据下标获取元素,需要一个一个查找。
3、Set集合
Set是一个接口,其中存储的内容无序,且不可重复。
1)HashSet集合的基本操作
创建HashSet对象(HashSet的底层为HashMap对象,默认容量为16,负载因子为0.75,当空间使用达到0.75时需要扩容)
public class TestHashSet {
public static void main(String[] args) {
//Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
//HashSet的底层为HashMap对象,默认容量为16,负载因子为0.75,当空间使用达到0.75时需要扩容
//也可以在创建对象的时候传入容量和负载因子
HashSet hashSet = new HashSet();
HashSet hashSet1 = new HashSet(16);
//负载因子为float类型
HashSet hashSet2 = new HashSet(16,0.7f);
}
}
添加操作
hashSet.add("java1");
hashSet.add("java2");
hashSet.add(1);
hashSet.add(2);
hashSet.add(5);
hashSet.add(1);
//hashSet中的元素不可重复,且无序
System.out.println(hashSet);// [1, 2, java2, 5, java1]
删除操作
hashSet.remove(5);
System.out.println(hashSet);// [1, 2, java2, java1]
修改操作
hashSet没有set方法
查询操作
//查询hashSet中的元素个数
int size = hashSet.size();
System.out.println(size);// 4
//清空hashSet中的元素
hashSet.clear();
System.out.println(hashSet);// []
//判断hashSet是不是为空
boolean empty = hashSet.isEmpty();
System.out.println(empty);// true/false
hashSet集合的遍历
for each 遍历
//hashSet的遍历
// for each
for (Object o : hashSet ) {
System.out.println(o);// 1 2 java2 java1
}
迭代器遍历
// 迭代器遍历
//1、获取迭代器对象,有序,有下标
Iterator iterator = hashSet.iterator();
//2、判断迭代器对象是否可以移动 hasNext的返回值为布尔类型
while (iterator.hasNext()){
//3、指定移动并获取当前元素
Object obj = iterator.next();
//4、输出当前元素
System.out.println(obj);// 1 2 java2 java1
}
2)TreeSet的基本操作
TreeSet中的方法和HashSet中的方法相同,TreeSet是对HashSet的排序,可以指定比较器,默认自然升序排序,如果存储自定义类型的数据,自定义类型需要实现实现Comparable接口 方可放入TreeSet,或者指定排序的对象。
TreeSet对象的创建
//无参
public TreeSet() {
this(new TreeMap<E,Object>());
}
//传入指定的比较器
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//传入指定的初始集合
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
TreeSet集合的添加操作
String类型和Integer类型的数据添加
TreeSet treeSet = new TreeSet();
// TreeSet是对HashSet的排序,可以指定比较器,默认自然升序排序
//存储String类型
treeSet.add("java1");
treeSet.add("java2");
treeSet.add("java3");
//treeSet.add(1);// java.lang.String cannot be cast to java.lang.Integer
treeSet.add("java2");
System.out.println(treeSet);// [java1, java2, java3]
TreeSet treeSet1 = new TreeSet();
//存储Integer类型
treeSet1.add(1);
treeSet1.add(4);
treeSet1.add(2);
System.out.println(treeSet1);// 1 2 4
自定义类型的数据添加
//自定义Student类型
public class Student {
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestTreeSet {
public static void main(String[] args) {
TreeSet treeSet2 = new TreeSet();
treeSet2.add(new Student("张三",19));
treeSet2.add(new Student("李四",26));
treeSet2.add(new Student("王五",21));
System.out.println(treeSet2);
}
}
此时控制台会报如下错误
查看源码发现,String包实现了Comparable接口,Integer类也实现了Comparable接口
解决方法:
1.TreeSet中的元素必须实现Comparable接口 方可放入TreeSet ,让自定义类型实现Comparable接口,并重写compareTo方法
public class Student implements Comparable{
……
……
//排序:返回值如果大于0,表示当前元素比传入的o大,
// 返回值如果小于0,表示当前元素比传入的o小
// 返回值如果等于0,表示相同元素
@Override
public int compareTo(Object o) {
Student student = (Student) o;
if (this.age > student.age){
return 1;
}
if (this.age < student.age){
return -1;
}
return 0;
}
}
public class TestTreeSet {
public static void main(String[] args) {
TreeSet treeSet2 = new TreeSet();
treeSet2.add(new Student("张三",19));
treeSet2.add(new Student("李四",26));
treeSet2.add(new Student("王五",21));
System.out.println(treeSet2);
// [Student{name='张三', age=19}, Student{name='王五', age=21}, Student{name='李四', age=26}]
}
}
2.在创建TreeSet时指定排序的对象。
新建一个选择器类型的对象,在创建TreeSet对象时传入参数
public class MyComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Student student1 = (Student) o1;
Student student2 = (Student) o2;
if (student1.getAge() > student2.getAge()){
return 1;
}
if (student1.getAge() < student2.getAge()){
return -1;
}
return 0;
}
}
public class TestTreeSet {
public static void main(String[] args) {
TreeSet treeSet2 = new TreeSet(new MyComparator());
treeSet2.add(new Student("张三",19));
treeSet2.add(new Student("李四",26));
treeSet2.add(new Student("王五",21));
System.out.println(treeSet2);// [Student{name='张三', age=19}, Student{name='王五', age=21}, Student{name='李四', age=26}]
}
}
4、Map集合
1)HashMap的基本操作(HashMap时键值对的形式存储,且key唯一不可重复)
创建HashMap对象
public class TestHashMap {
public static void main(String[] args) {
//默认容量为16,负载因子为0.75
Map map = new HashMap();
HashMap map1 = new HashMap(15);
HashMap map2 = new HashMap(10,0.78f);
}
}
HashMap的添加操作
put(key,value):添加一个键值对,当key的值重复时,value的值会被新添加的元素覆盖
putAll(Map map):将另一个map的所有内容添加到当前map对象中
putIfAbsent(key,value):添加一个键值对,如果key的值已经在map中存在,则不添加到map中
public class TestHashMap {
public static void main(String[] args) {
//默认容量为16,负载因子为0.75
Map map = new HashMap();
HashMap map1 = new HashMap(15);
HashMap map2 = new HashMap(10,0.78f);
//添加操作
map.put("name","zjw");
map.put("age",18);
map.put("school","zut");
map.put("age",23);
System.out.println(map);
map1.put("k1","v1");
map1.put("k2","v2");
//putAll:添加一个map对象中的全部内容
map.putAll(map1);
System.out.println(map);
//putIfAbsent:如果key存在,则不放入map中
map.putIfAbsent("age",24);
map.putIfAbsent("phone","183");
System.out.println(map);
}
}
删除操作
remove(key):移除指定key的元素
//删除操作
map.remove("k2");
System.out.println(map);
修改操作
replace(key,value) 和put(key,value)都能达到修改的效果,修改指定key的value值
//修改操作
//replace 和 put都可以达到修改的效果,修改指定key的value值
map.replace("k1","value");
System.out.println(map);
查询操作
get(key):获取key的value值
size():获取map对象中元素的个数
containsKey(key):返回值为布尔类型,是否包含key为指定内容的元素
containsValue(value):返回值为布尔类型,是否包含value为指定内容的元素
keySet():返回值为Set对象,获取map中的所有key值,并存储为Set对象,使用for each 遍历keySet,输出key和value
//keySet获取map中的所有key值,返回值为Set
Set keySet = map.keySet();
System.out.println(keySet);
for (Object o : keySet ) {
System.out.println(o+"------------->"+map.get(o));
}
2)HashMap在内存中的存储结构如下图
使用了三种存储结构(数组,单向链表,红黑树)
- 创建一个初始容量为16的数组;
- 根据key的hash值求出在数组中的位置,key. hash%16得到0~15之间的值,即数组下标,其中存储的是添加元素(node对象)的地址;
- 创建一个Node对象,有key, value, next三个属性,next的值刚开始为null ;
- 如果经过hash运算得到的结果相同,这种情况称为hash碰撞( 冲突),然后在通过equals作比较,如果不同就使用尾插法挂在链表上,next的值为新创建的node对象的地址,这个链表结构为单向链表(添加和查询效率比较低,需要逐个查询)。
- 如果hash冲突的数量达到8个,将转换为红黑树的结构(优点:添加、查询效率高;缺点:创建时非常耗费资源)
jdk1.7和jdk1.8中hashMap的区别
jdk1.7中:数组+单向链表 而链表的插入为头部插入(容易造成死循环)
jdk1.8中:数组+单向链表+红黑树 链表的插入方式为尾部插入,当hash碰撞的个数超过8个时,单向链表转换为红黑二叉树。
3)hashMap集合的put方法的源码
public V put(K key, V value) {
//hash(key):根据key的hash值进行运算
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//返回值为(h = key.hashCode()) ^ (h >>> 16)的值,
//也就是hashcode和无符号右移16位的hashcode后得到的值做异或运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
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;
//比较两个元素的hash值是否相等,如果hash值相等在判断key值是否相等,相等则覆盖
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;
}
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;
}