工作2年了,最近在面试的过程中经常有人问到,ArrayList集合与LinkedList的区别。再加上自从上次在余胜军老师的视频上看到纯手写实现ArrayList集合框架,随后看了几分钟的视频,只是听到了ArrayList实现的原理是数组,后来因为忙着面试就没有去细看代码了。今天下午忙完了后我从余胜军老师的网站上下载下来了他的课上代码,看了一下,带着一些问题百度了一番,终于自己手写出来了,而且还在余胜军老师的代码基础上自己做了一个update更新方法的扩展。具体上代码吧。
项目结构:
下面我就来说说具体实现过程,以及我在写的过程中遇到的问题:
第一步:定义EasyList集合接口。
package com.liuz.list;
public interface EasyList<E> {
public void add(E object);
public void add(int index,E object);
public boolean remove(E object);
public Object remove(int index);
public int getSize();
public Object get(int index);
/**
* 自己的扩展
* @param index
* @param obj
*/
public void update(int index,Object obj);
}
第二步:自己定义实现类,实现EasyArrayList接口。
package com.liuz.list;
import java.util.Arrays;
public class EasyArrayList<E> implements EasyList<E>{
// 定义属性
private transient Object[] elementData;
// ArrayList实际数量
private int size;
//构造方法
public EasyArrayList() {
this(10);
}
public EasyArrayList(int initialCapacity) {
if(initialCapacity<0)
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
elementData = new Object[initialCapacity];
}
public void add(E object) {
// TODO Auto-generated method stub
ensureExplicitVapacity(size+1);
elementData[size++]=object;
}
public void add(int index, E object) {
// TODO Auto-generated method stub
rangeCheck(index);
ensureExplicitVapacity(size+1);
System.arraycopy(elementData, index, elementData, index+1, size);
elementData[index]=object;
size++;
}
public boolean remove(E object) {
// TODO Auto-generated method stub
for(int i=0;i<elementData.length;i++){
Object element=elementData[i];
if(element.equals(object)){
remove(i);
return true;
}
}
return false;
}
public Object remove(int index) {
// TODO Auto-generated method stub
Object object=get(index);
int numMoved = elementData.length-index-1;
if(numMoved>0){
System.arraycopy(elementData, index+1, elementData, index, numMoved);
}
elementData[--size]=null;
return object;
}
public int getSize() {
// TODO Auto-generated method stub
return size;
}
public Object get(int index) {
// TODO Auto-generated method stub
rangeCheck(index);
return elementData[index];
}
/**
* 更新
*/
public void update(int index, Object obj) {
// TODO Auto-generated method stub
rangeCheck(index);
remove(index);
E e =(E)obj;
add(e);
}
/**
* 扩容
* @param minCapacity
*/
public void ensureExplicitVapacity(int minCapacity){
if(size==minCapacity){
int oldCapacity=elementData.length;
int newCapacity=oldCapacity+(oldCapacity >>1);
if(newCapacity<minCapacity){
newCapacity=minCapacity;
}
elementData=Arrays.copyOf(elementData,newCapacity);
}
}
/**
* 检测数组是否下标越界
* @param index
*/
private void rangeCheck(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException("length--->"+index);
}
}
}
第三步:编写测试类
package com.liuz.test;
import com.liuz.list.EasyArrayList;
public class TestEasyArrayList {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
EasyArrayList<String> list = new EasyArrayList<String>();
list.add("张三");
list.add("李四");
System.out.println("--------新增------------");
for(int i=0;i<list.getSize();i++){
System.out.println(list.get(i));
}
list.update(1, "王五");
System.out.println("--------修改------------");
for(int i=0;i<list.getSize();i++){
System.out.println(list.get(i));
}
list.add("赵六");
list.add("何七");
System.out.println("--------删除------------");
System.out.println("删除前:");
for(int i=0;i<list.getSize();i++){
System.out.println(list.get(i));
}
list.remove(3);
System.out.println("删除后:");
for(int i=0;i<list.getSize();i++){
System.out.println(list.get(i));
}
}
}
运行结果:
遇到的问题:
在看jdk源代码的时候,
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
这是jdk1.5使用扩容的代码,新的数组=(原来的数组*3)/2+1。而到了jdk1.8的实现方式有所改变
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
于是,这才发现,原来jdk扩容的方法是 新数组=老数组+(老数组 >>1);老数组 >> 1 也就是老数组长度的二进制后移2位。也就是1/2;就这样新数组的长度是老数组的1.5倍。
在这里解释一下java基础里面的位运算,估计很多人都已经忘了。
java中有三种移位运算符
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
具体的也可以看这篇博客:https://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html
我是看这篇博客,然后自己手动的在稿纸把10进制转换成2进制,然后去做乘除和补0计算的。自己去手动的体会一遍计算过程,你的收获会又不一样。
在这里我还遇到了第二个问题
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
就是这一行代码,为何数组最大的长度是Integer.MAX_VALUE-8呢?Integer.MALUE等于多少?为什么是-8而不是-其他的数字呢?
于是带着这2个问题有翻阅了很多博客。才明白
Integer.MIN_VALUE,即-2147483648,二进制位如下:
1000 0000 0000 0000 0000 0000 0000 0000 第一位是表示正负符号 0表示正数 1表示负数
那么最大数也就是和这个相反的数了。于是就成了
1000 0000 0000 0000 0000 0000 0000 0000 Integer.MIN_VALUE
取反 0111 1111 1111 1111 1111 1111 1111 1111 (取反之后变成了Integer.MAX_VALUE)
在计算机的运算中,“-”(前缀)运算表示各二制位取反再加1,也就是说 b = -a 在计算机内部是 b = ~a + 1 这样处理的,所以上面的位就变成了:
Integer.MAX_VALUE+1=1000 0000 0000 0000 0000 0000 0000 0000 -Integer.MIN_VALUE(与原来的结果一样)
即Integer.MAX_VALUE=2147483647 反过来Integer.MIN_VALUE=-2147483647。
再说说为什么是-8呢?
找了一下网上的博客,有的说跟电脑操作系统的位数有关系,唯一找到一个觉得可以解释的这个
原来前面的8位被数组对象的元数据占用了。于是就要-8了。
好了,就这么多了。