源码分析篇--Java集合操作(1)
一、集合框架
1、集合框架体系图
2、集合的概念
Java集合是使程序能够存储和操纵元素不固定的一组数据。 所有Java集合类都位于java.uti包中。与Java数组不同,Java集合中不能存放基本数据类型,只能存放对象的引用。但是在JDK5.0以后的版本当中,JAVA增加了“自动装箱”和“自动拆箱”的机制,比如如果要存入一个INT类型的数据,JVM会把数据包装成Integer然后再存入集合,看起来集合能够存入基本数据类型,其实是不能的只是多了一个包装数据的过程。我们来看个例子,验证一下集合存放的是对象的引用(内存地址,什么是内存地址呢?内存地址就是计算机中使用16进制或其他进制来映射一个字符,这个进制数就是实际字符在内存中所映射的地址,内存地址是内存当中存储数据的一个标识,并不是数据本身,通过内存地址可以找到内存当中存储的数据。),而不是对象数据本身:
package com.yzh.maven.main;
import java.util.ArrayList;
import java.util.List;
import com.yzh.maven.entity.UserInfo;
public class CollectionTest{
private static Integer[] arr = null;
public static void main(String[] args) {
///////////////////////list元素的添加/////////////////////////
List<UserInfo> list = new ArrayList<UserInfo>();
UserInfo u = null;
for(int i = 0;i<5;i++){
u = new UserInfo();
u.setUserName("yzh"+i);
u.setPassword("123"+i);
list.add(u);
}
for(int i = 0;i<list.size();i++){
//System.out.println(System.identityHashCode(list.get(i)));
System.out.println(list.get(i));
}
List<Integer> list2 = new ArrayList<Integer>();
for(int i = 0;i<5;i++){
list2.add(i);
}
System.out.println(list2.get(0).getClass());
}
}
output:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
class java.lang.Integer
注意:
① System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法。
② Java中打印对象内存地址
Object的hashCode()默认是返回内存地址的,但是hashCode()可以重写,所以hashCode()不能代表内存地址的不同。System.identityHashCode(Object)方法可以返回对象的内存地址,不管该对象的类是否重写了hashCode()方法。
3、集合与数组的区别
数组是最常见的链式存储结构,它是一段连续的内存空间,在内存中我们可以简单表示为下图样式
注意:链表与数组是两种不同的数据结构,数据结构可以分为线性结构和非线性结构,在线性结构中,存储方式又分为连续存储(数组)和离散存储(链表),例如栈和队列都是线性结构常见的应用。我们可以将数组简单地理解为一种线性表数据结构(线性表是动态的),因为数组一旦定义了,其长度就不可以更改了,也就是不可以做增删操作,但可以做修改和查询操作。不存在什么先进先出的含义。
用数组存放一堆相同类型对象也是一个不错的选择,但是有一个很大的缺陷,那就是数组大小只能是固定的,不能从数组里动态添加和删除一个对象,要扩容的时候,就只能新建一个数组然后把原来的对象全部复制到新的数组里,而且只能存放相同类型的对象,使用起来不够灵活。下面我们来使用数组实现扩容,如下所示。
package com.yzh.maven.main;
public class CollectionTest3 {
//初始化元素数组
private static Object[] src = new Object[1];
private static int len = src.length;
//目标数组
private static Object[] to = new Object[len];
//记录添加元素的个数
private static int countTemp = 0;
//中间数组
private static Object[] obj = new Object[len];
public static void main(String[] args) {
for(int i = 0;i<12;i++){
add("AA"+i);
}
for(Object o:to){
System.out.print(o+" ");
}
System.out.println("\n原素个数"+size());
}
public static void add(Object i){
int temp = src.length - 1;
//最后一个元素不为空,说明数组元素已经满了,就扩容
if(src[temp] != null){
//在扩展前需要保存to数组的数据
System.arraycopy(to, 0, obj, 0,len);
//扩容
len = len+1;
//动态扩展数组,增加数组容量
to = new Object[len];
//将obj数组从0开始的元素拷贝to数组到从0-第len-1个元素的位置,从obj中间数组中获取数据到to数组
System.arraycopy(obj, 0, to, 0,len-1);
//添加元素
to[len-1]=i;
//克隆数组是为了每次使长度增1
obj = to.clone();
//如果是第一个元素(数组元素未满),也就是最后一个元素为空
}else{
src[0] = i;
System.arraycopy(src, 0, to, 0, countTemp+1);
}
countTemp++;
return;
}
public static int size(){
return countTemp;
}
}
/**output:
AA0 AA1 AA2 AA3 AA4 AA5 AA6 AA7 AA8 AA9 AA10 AA11
12
*/
实际上,集合的底层是一个动态数组,原理类似于上面的情形。可以判断的是,在List集合的底层方法中,一定使用了arraycopy()方法来实现动态数组扩容。数组扩容的大概思路是,先判断数组的最后一个元素是否有值,如果有值,说明数组溢满,数组需要扩容才能添加元素了;如果没值,说明数组没满,就可以继续添加元素。在扩容时,需要定义一个中间数组来存储未扩容的目标数组的数据,否则会丢失内存中原有的数据,而且需要将中间数组的长度也要进行扩容,例如obj = to.clone();只是为了扩展中间数组容量而已,还可以将其改写成obj = new Object[len];来达到扩展中间数组容量。使用数组进行容量扩展是一件很麻烦的事,但是,我们使用已经封装好的List的实现类就不一样了:
package com.yzh.maven.main;
import java.util.ArrayList;
import java.util.List;
public class CollectionTest5 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for(int i = 0;i<12;i++){
list.add("AA"+i);
}
for(Object o:list){
System.out.print(o+" ");
}
}
}
/**output:
AA0 AA1 AA2 AA3 AA4 AA5 AA6 AA7 AA8 AA9 AA10 AA11
*/
我们看到,实现的效果和上面的一样,但是相比数组的操作就简单多了。