简单的说,容器是一种能够存放对象的一种工具,就比如我们学过的数组一样,它能存放相同类型的数据。下面我讲结合下面这张图,对java自带的常见容器做分析和实现。
java常见容器
Colection和 Map两大类, Colection是序列化的容器,而Map则是基于键值对的容器,这里如果不熟悉的话,可以先只暂时了解一下,然后对于Colection接口,下面又有两个分类,是List和Set,二者很好区别—— List元素可以重复有顺序,Set元素不可重复无顺序,基于他们的实现类有ArrayList、LinkedList和HashSet,而Map的实现类就是HashMap了。
下面我们来分析最简单的ArrayList,首先,它的底层是由数组实现的,因此它在查询方面比较容易,同时,为了解决数组长度有限的问题,ArrayList对它进行了优化,也就是ArrayList的扩容机制,这个我之前已经分析过它的源代码了,基本思想就是:需要扩容时,就给它创建一个新数组,这个数组大小约为原数组的1.5倍,之后拷贝旧数组元素到新数组里面,然后将原来的数组更换为新的数组。详情请查看下面链接哦~
现世安稳:java关于ArrayList扩容机制zhuanlan.zhihu.com
由于ArrayList父类继承了List接口,而List又继承了Colection接口,因此我们可以先从Colection这个接口开始看起,看定义哪些共同的方法。
Colection接口
根据方法名,大家都能推出每个方法的功能是什么,其中可能不是很清楚的就是removeAll和retianAll方法,结合下面这个例子,就会知道它们也很容易区分,removeAll是将list1中的包含list2中的元素全部清除,而retainAll则是只保留list1中与list2中相同的部分元素。
ArrayList<Integer> list1=new ArrayList<>();
list1.add(0);
list1.add(1);
list1.add(2);
System.out.println("list1:"+list1);
ArrayList<Integer> list2=new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
System.out.println("list2:"+list2);
list1.removeAll(list2);
System.out.println(list1);
list1中去掉在list2中出现的元素1,2
ArrayList<Integer> list1=new ArrayList<>();
list1.add(0);
list1.add(1);
list1.add(2);
System.out.println("list1:"+list1);
ArrayList<Integer> list2=new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
System.out.println("list2:"+list2);
list1.retainAll(list2);
System.out.println(list1);
list1中保留在list2中也存在的元素1,2
好了,闲话不多说了,我们现在开始一步一步实现我们自定义的ArrayList:
private static final int DEFAULT_CAPACITY = 10;// 默认容量为10; private int size;// 表示实际大小 private Object elementData[];// 代表底层的数组
1、实现MyArrayList的两种构造方式
无参构造:
public MyArrayList01() {//无参时创建默认大小为10的数组
elementData = new Object[DEFAULT_CAPACITY];
}
有参构造:
public MyArrayList01(int capacity) {
//需判断传入容量是否有意义
if (capacity < 0) {
throw new RuntimeException("容器的容量不能为负数!");
} else if (capacity == 0) {
elementData = new Object[DEFAULT_CAPACITY];
} else {
elementData = new Object[capacity];
}
}
2、add方法实现,并实现扩容机制和泛型的使用
数组实际大小size不停++,会达到默认的容量大小或者自定的capacity大小,此时假如需要再添加元素,就需要对“数组"进行扩容
public void add(E obj) {
// 什么时候需要扩容?
if (size == elementData.length) {
// 怎么样扩容?10->10+10/2
Object[] newArray = new Object[elementData.length + (elementData.length >> 1)];
System.arraycopy(elementData, 0, newArray, 0, elementData.length);
elementData = newArray;
}
elementData[size++] = obj;
}
3、set和get方法实现,加上越界检查处理
public void rangeCheck(int index) {
// 索引判断异常
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("Index:" + index);
}
}
public void set(E element, int index) {
rangeCheck(index);
elementData[index] = element;
}
public E get(int index) {
rangeCheck(index);
return (E) elementData[index];
}
4、remove两种方式,根据索引和根据对象来移除
根据索引来移除:利用arraycopy函数将数组elementData从Index+1的位置开始,将其后面的所有元素,长度为elementData.length-Index-1,全部拷贝到elementData数组Index开始到后面的地方。
remove(int Index)示意图
public void remove(int Index) {
rangeCheck(Index);
int numMoved = elementData.length - Index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, Index + 1, elementData, Index, numMoved);
}
elementData[--size] = null;
}
根据对象来移除:只要用equals方法比较元素即可,然后再利用根据索引删除的方法实现。
public void remove(E element) {
// element,将它与所有元素接个比较,获得第一个比较为true的删掉
for (int i = 0; i < size; i++) {
if (element.equals(get(i))) {
// 容器中比较都使用equals方法
// 将该元素从此处移除
remove(i);
}
}
}
完整测试代码如下:
package myArrayList;
/**
* 了解底层ArrayList操作后,写出自己的ArrayList
* 1、实现add,remove,get,set ;
* 2、根据扩容机制实现add;
* 3、利用泛型实现数据对象参数化;
*
* @author Dell
*
*/
public class MyArrayList01<E> {
private static final int DEFAULT_CAPACITY = 10;// 默认容量为10;
private int size;// 表示实际大小
private Object elementData[];// 代表底层的数组
public MyArrayList01() {
elementData = new Object[DEFAULT_CAPACITY];
}
public MyArrayList01(int capacity) {
if (capacity < 0) {
throw new RuntimeException("容器的容量不能为负数!");
} else if (capacity == 0) {
elementData = new Object[DEFAULT_CAPACITY];
} else {
elementData = new Object[capacity];
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(elementData[i] + ",");
}
sb.setCharAt(sb.length() - 1, ']');
return sb.toString();
}
public static void main(String[] args) {
MyArrayList01<String> myAL = new MyArrayList01<>();
for (int i = 0; i < 11; i++) {
myAL.add("tyl_" + i);
}
System.out.println(myAL);
System.out.println(myAL.get(2));
myAL.set("Hi!", 1);
System.out.println(myAL);
myAL.remove(2);
System.out.println(myAL);
myAL.remove("Hi!");
System.out.println(myAL);
}
public void add(E obj) {
// 什么时候需要扩容?
if (size == elementData.length) {
// 怎么样扩容?10->10+10/2
Object[] newArray = new Object[elementData.length + (elementData.length >> 1)];
System.arraycopy(elementData, 0, newArray, 0, elementData.length);
elementData = newArray;
}
elementData[size++] = obj;
}
public void rangeCheck(int index) {
// 索引判断异常
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("Index:" + index);
}
}
public void set(E element, int index) {
rangeCheck(index);
elementData[index] = element;
}
public E get(int index) {
rangeCheck(index);
return (E) elementData[index];
}
public void remove(E element) {
// element,将它与所有元素接个比较,获得第一个比较为true的删掉
for (int i = 0; i < size; i++) {
if (element.equals(get(i))) {
// 容器中比较都使用equals方法
// 将该元素从此处移除
remove(i);
}
}
}
public void remove(int Index) {
rangeCheck(Index);
int numMoved = elementData.length - Index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, Index + 1, elementData, Index, numMoved);
}
elementData[--size] = null;
}
}
测试结果