Collection:List集合

上一篇总结了一下Collection中的常用方法,它是所有集合的超类,那实现它的子类,肯定会有它特殊的方法,现在再了解一下它的其中一个集合List。

List也是一个接口,它又被好多子类实现,以他的常用的子类ArrayList来学习他的基本方法。

ArrayList底层是以数组来实现的。它的add方法可以看一看。下面是源码:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!   初始化一个数组
        elementData[size++] = e;<span style="white-space:pre">		</span>//将对象e添加到第size+1个位置
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {<span style="white-space:pre">		</span>//如果集合是空的
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);      //创建一个数组,长度为10,默认ArrayList创建大小为10
        }

        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);执行到这儿,就是说添加的数据长度超过了默认长度10,然后新的大小为10+10/2=15.下一次是15+15/2取整
        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);     //重新初始化一个数组
    }
在搜狐面试,问过的一个问题,现在还记忆犹新:ArrayList底层是怎么实现的,默认长度是多少?如果超出了范围,会如何?所以,在复习的时候,我特别认真的看了一下他的源码,所以,大概总结一下:ArrayList底层是以数组形式实现的,当添加一个元素时,相应的在数组中插入一条记录,数组的特点是查找速度快,但不方便删除和插入。如果要删除或插入一个元素,相应的后面都要向前移动一位,效率较低。每次初始化一个ArrayList时,默认创建一个大小为10的数组,当添加数量超过10条之后,继续初始化当前数组,将数组长度增加到[10/2](取整)即15,当长度再次超过时,将数组增加到15+[15/2]=22,以此类推。

上面是ArrayList的add方法。ArrayList在Collection的基础上,又添加了新的方法,常用的方法有:

E get(int index);       得到一个元素

E set(int index, E element); 更改指定位置的元素

void add(int index, E element); 向指定位置添加指定元素

E remove(int index); 移除指定位置的元素
int indexOf(Object o); 查找是否有该元素,返回出现的位置,如果不存在则返回-1

int lastIndexOf(Object o); 最后出现的位置

ListIterator<E> listIterator(); List的迭代器
List<E> subList(int fromIndex, int toIndex); 截取部分集合,返回截取后的集合

大部分都很好理解,其中indexOf(Object o)()和contains()很相似,前者返回查找后的位置,后者返回Boolean型。其中ListIterator<E> listIterator()比较特殊,它是一个List的迭代器,父类是Iterator,主要是在父类的基础上增加了一部分功能。下面先用一个具体的例子来看看他所加的功能。

public class ListTest {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		
		ListIterator<Integer> listIterator = list.listIterator();
		//Iterator方法
		while(listIterator.hasNext()){
			Integer a = listIterator.next();
			System.out.println(a);
			if (a==2) {
				listIterator.add(4);<span style="white-space:pre">		</span>//可以往集合中添加数据,添加位置在当前位置之后
			}
		}
		System.out.println("----------------------------");
		//ListIterator新增方法
		while(listIterator.hasPrevious()){		//逆向遍历,返回是否有前一个元素
			System.out.println(listIterator.previous());	//前一个元素
		}
	}
}
输出结果:

1
2
3
----------------------------
3
4
2
1
其实ListIterator的逆向遍历是没有太大意义的,如果不做正向遍历,逆向遍历不会输出任何数据。

最后的一个比较重要的方法是List<E> subList(int fromIndex, int toIndex)()。我个人觉得这个方法挺重要的,因为我前不久在研究彩票预测,其中要对集合中的数据进行截取,这个方法正好派上用场。主要是截取指定区间中的数据。用一个简单例子说明一下:

public class ListTest {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		list.add(5);
		List<Integer> subList = list.subList(1, 4);
		for (Integer integer : subList) {
			System.out.println(integer);
		}
	}
}

输出结果 2 3 4.是从第i个位置开始到第j个位置,包含i但不包含j。

上面值得补充一点的是,indexOf(Object o)这个方法,如果我比较的是一个自定义对象,会是什么情况呢?下面做个测试,然后跟进源码进行分析一下:

public class Student {
	private String name;
	private Integer 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;
	}
	public Student(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
}

public class ListTest {
	public static void main(String[] args) {
		List<Student> list = new ArrayList<Student>();
		Student stu1 = new Student("张三", 22);
		Student stu2 = new Student("李四", 27);
		Student stu3 = new Student("王五", 30);
		list.add(stu1);
		list.add(stu2);
		list.add(stu3);
		Student stu4 = new Student("李四", 27);
		System.out.println(list.indexOf(stu4));
	}
}

上面这个结果最后输出的是-1,意思就是没有找到这个Student对象,我跟进一下源码去看看他的底层是如何实现的。

  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]))        /比较两个对象,这儿比较的是数组,如果要比较自定义对象,先重写equals方法
                    return i;
        }
        return -1;
    }

看到源码,主要起作用的是equals,而调用的equals方法是Object的,所以可以在自定义类中重写equals方法去实现这一功能。在Student中添加如下代码:

public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) 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;
	}

现在再次运行,就返回了具体的位置。1.


List集合中出了ArrayList之外,还有LinkedList、Vector等。他们都实现了List接口,里面大部分方法都是相同的,主要了解一下不同之处,再做个对比。

LinkedList:

ArrayList底层是以数组的形式去实现的,它的好处是方便查找,但是在删除和插入时,效率会很低。所以,为了更高效的去插入和删除时,就出现了LinkedList。LinkedList的底层是以链表的形式去实现的。链表,当前指针指向下一个节点,如果要插入一个数据,首先获取当前节点,将指针指向插入的新节点,再将插入的数据指向当前节点的下一个节点。举个例子:A-->B-->C-->D,现在要在B后面C的前面插入一个E,那么首先是得到B的指针,将B的指针指向E,然后再将E的指针指向C即可。这样插入,后面的元素不需要移动,因此效率是比较高的。LinkedList底层的插入就是以这种方式去实现的。

LinkedList特有的方法:

 public void addFirst(E e) 

public void addLast(E e)

因为LinkedList底层是链表形式去实现的,因此这两个方法就很容易理解了。

Vector:

Vector和ArrayList底层实现是一样的,他们的基本方法也是一模一样的,但是,ArrayList是线程不同步的,效率比较高,而Vector是线程同步的,所以效率比较低,一般现在都很少去使用Vector。

刚刚说到LinkedList适合插入和删除,而ArrayList适合查询,LinkedList插入和删除是线性的,因为它只是在指定位置加入或删除一个数据元素,其他元素不做相应的移动,而就插入和删除来说,ArrayList是二次增长的。因为它每次插入或者删除一个元素后,后面的元素都需要相应的向前面移动一个位置。同样查询也是如此。下面我用代码来说明一下它们之间的差距。

插入或删除:

public class ArrayListDemo {
	public static void main(String[] args) {
		List<Integer> list1 = new ArrayList<Integer>();
		//往list1中添加200000的值
		for (int i = 0; i < 200000; i++) {
			list1.add(i);
		}
		
		long startTime = System.currentTimeMillis();		//获取开始时间
		Iterator<Integer> iterator = list1.iterator();
		while(iterator.hasNext()){
			//删除偶数项
			Integer next = iterator.next();
			if (next%2==0) {
				iterator.remove();
			}
		}
		long endTime = System.currentTimeMillis();			//获取结束时间
		System.out.println(endTime-startTime);				//运行时间
	}
}
运行结果:1909毫秒

上面是ArrayList的删除时间,再看看LinkedList的删除时间

public class LinkedListDemo {
	public static void main(String[] args) {
		List<Integer> list1 = new LinkedList<Integer>();
		//往list1中添加200000的值
		for (int i = 0; i < 200000; i++) {
			list1.add(i);
		}
		
		long startTime = System.currentTimeMillis();		//获取开始时间
		Iterator<Integer> iterator = list1.iterator();
		while(iterator.hasNext()){
			//删除偶数项
			Integer next = iterator.next();
			if (next%2==0) {
				iterator.remove();
			}
		}
		long endTime = System.currentTimeMillis();			//获取结束时间
		System.out.println(endTime-startTime);				//运行时间
	}
}
运行结果:6毫秒。

由此可以看出效率之高。我把List值提高一倍,看看两个集合的时间,是不是一个是指数增长,一个是线性增长

ArrayList:8025毫秒,由此可知,它不是线性增长,当他的值增加到2倍后,所需要的时间,增加4倍。(由于计算机原因,时间有误差)

LinkedList:12毫秒,由此可知,它是线性增长。

同样,查询也是如此。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章