java复习5

#第五部分:集合

##1. 集合的概念和作用
java中的集合类:是一种工具类,类似于容器,储存任意数量的具有共同属性的对象
集合的作用
1.在类内部,对数据进行组织
2.简单而快速的搜索大数量的条目
3.有的集合接口,提供了一系列排列有序的元素,并且可以在序列中间快速的插入或者删除又短元素
4.有的集合接口,提供了映射关系,可以通过关键字(key)去快速查找对应的唯一对象,而这个关键字可以使用任意类型。

##2. 集合和数组的区别
与数组相比,选择集合
1.数组长度对比,选择集合
2.数组只能通过下标访问元素,类型可变,而有的集合可以通过任意类型查找所映射的具体对象

##3. 集合框架体系介绍
图:

##4. 集合框架之Collection接口
两个最核心的接口:Collection和Map接口:
Collection接口有两个重要的方法:
1.add()
public boolean add(E e)向集合内部添加数据
2.iterator()
public Iterator iterator()取得iterator接口对象,用于集合输出
##List
List接口是Collection接口的子接口,除了包含Collection接口的那两个方法 外,还扩充了一下两个方法::
get()
public E get(int index)根据索引去的保存的数据
数据set()
public E set(int index,E element)修改
List是一个窗口,想要去的实例化对象。就必须有子类。在LIst接口下,有三个常用的子类:ArrayList,Vector,LinkedList

##5. 泛型的使用
1、泛型是什么
首先告诉大家ArrayList就是泛型。那ArrayList能完成哪些想不到的功能呢?先看看下面这段代码:
[java] view plain copy
ArrayList strList = new ArrayList();
ArrayList intList = new ArrayList();
ArrayList doubleList = new ArrayList();
大家对ArrayList很熟悉,这里构造了三个List,分别盛装String、Integer和Double;这就是ArrayList的过人之处:即各种类型的变量都可以组装成对应的List,而不必针对每个类型分别实现一个构建ArrayList的类。这里可能看不懂,开篇总是困难的,下面看看如果没有泛型的话,我们要怎么做;
2、没有泛型会怎样
先看下面这段代码:
我们实现两个能够设置点坐标的类,分别设置Integer类型的点坐标和Float类型的点坐标:
[java] view plain copy
//设置Integer类型的点坐标
class IntegerPoint{
private Integer x ; // 表示X坐标
private Integer y ; // 表示Y坐标
public void setX(Integer x){
this.x = x ;
}
public void setY(Integer y){
this.y = y ;
}
public Integer getX(){
return this.x ;
}
public Integer getY(){
return this.y ;
}
}
//设置Float类型的点坐标
class FloatPoint{
private Float x ; // 表示X坐标
private Float y ; // 表示Y坐标
public void setX(Float x){
this.x = x ;
}
public void setY(Float y){
this.y = y ;
}
public Float getX(){
return this.x ;
}
public Float getY(){
return this.y ;
}
}
那现在有个问题:大家有没有发现,他们除了变量类型不一样,一个是Integer一个是Float以外,其它并没有什么区别!那我们能不能合并成一个呢?
答案是可以的,因为Integer和Float都是派生自Object的,我们用下面这段代码代替:
[java] view plain copy
class ObjectPoint{
private Object x ;
private Object y ;
public void setX(Object x){
this.x = x ;
}
public void setY(Object y){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
即全部都用Object来代替所有的子类;
在使用的时候是这样的:
[java] view plain copy
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
在设置的时候,使用new Integer(100)来新建一个Integer
[java] view plain copy
integerPoint.setX(new Integer(100));
然后在取值的时候,进行强制转换:
[java] view plain copy
Integer integerX=(Integer)integerPoint.getX();
由于我们设置的时候,是设置的Integer,所以在取值的时候,强制转换是不会出错的。
同理,FloatPoint的设置和取值也是类似的,代码如下:
[java] view plain copy
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
Float floatX = (Float)floatPoint.getX();
但问题来了:注意,注意,我们这里使用了强制转换,我们这里setX()和getX()写得很近,所以我们明确的知道我们传进去的是Float类型,那如果我们记错了呢?
比如我们改成下面这样,编译时会报错吗:
[java] view plain copy
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
String floatX = (String)floatPoint.getX();
不会!!!我们问题的关键在于这句:
[java] view plain copy
String floatX = (String)floatPoint.getX();
强制转换时,会不会出错。因为编译器也不知道你传进去的是什么,而floatPoint.getX()返回的类型是Object,所以编译时,将Object强转成String是成立的。必然不会报错。
而在运行时,则不然,在运行时,floatPoint实例中明明传进去的是Float类型的变量,非要把它强转成String类型,肯定会报类型转换错误的!
那有没有一种办法在编译阶段,即能合并成同一个,又能在编译时检查出来传进去类型不对呢?当然,这就是泛型。
下面我们将对泛型的写法和用法做一一讲解。
二、各种泛型定义及使用
1、泛型类定义及使用
我们先看看泛型的类是怎么定义的:
[java] view plain copy
//定义
class Point{// 此处可以随便写标识符号
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//IntegerPoint使用
Point p = new Point() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());

//FloatPoint使用  
Point<Float> p = new Point<Float>() ;   
p.setX(new Float(100.12f)) ;   
System.out.println(p.getX());
先看看运行结果:

从结果中可以看到,我们实现了开篇中IntegerPoint类和FloatPoint类的效果。下面来看看泛型是怎么定义及使用的吧。
(1)、定义泛型:Point<T>
首先,大家可以看到Point<T>,即在类名后面加一个尖括号,括号里是一个大写字母。这里写的是T,其实这个字母可以是任何大写字母,大家这里先记着,可以是任何大写字母,意义是相同的。
(2)类中使用泛型
这个T表示派生自Object类的任何类,比如String,Integer,Double等等。这里要注意的是,T一定是派生于Object类的。为方便起见,大家可以在这里把T当成String,即String在类中怎么用,那T在类中就可以怎么用!所以下面的:定义变量,作为返回值,作为参数传入的定义就很容易理解了。
[java] view plain copy
//定义变量  
private T x ;   
//作为返回值  
public T getX(){   
return x ;
}
//作为参数  
public void setX(T x){
this.x = x ;
}   
(3)使用泛型类
下面是泛型类的用法:
[java] view plain copy
//IntegerPoint使用  
Point<Integer> p = new Point<Integer>() ;   
p.setX(new Integer(100)) ;   
System.out.println(p.getX());
  
//FloatPoint使用  
Point<Float> p = new Point<Float>() ;   
p.setX(new Float(100.12f)) ;   
System.out.println(p.getX());
首先,是构造一个实例:
[java] view plain copy
Point<String> p = new Point<String>() ;   
这里与普通构造类实例的不同之点在于,普通类构造函数是这样的:Point p = new Point() ; 
而泛型类的构造则需要在类名后添加上<String>,即一对尖括号,中间写上要传入的类型。
因为我们构造时,是这样的:class Point<T>,所以在使用的时候也要在Point后加上类型来定义T代表的意义。
然后在getVar()和setVar()时就没有什么特殊的了,直接调用即可。
从上面的使用时,明显可以看出泛型的作用,在构造泛型类的实例的时候:
[java] view plain copy
//IntegerPoint使用  
Point<Integer> p = new Point<Integer>() ;   
//FloatPoint使用  
Point<Float> p = new Point<Float>() ;   
尖括号中,你传进去的是什么,T就代表什么类型。这就是泛型的最大作用,我们只需要考虑逻辑实现,就能拿给各种类来用。
前面我们提到ArrayList也是泛型,我们顺便它的实现:
[java] view plain copy
public class ArrayList<E>{  
…………  
}  
看到了吧,跟我们的Point实现是一样的,这也就是为什么ArrayList能够盛装各种类型的主要原因。
(4)使用泛型实现的优势
相比我们开篇时使用Object的方式,有两个优点:
(1)、不用强制转换
[java] view plain copy
//使用Object作为返回值,要强制转换成指定类型  
Float floatX = (Float)floatPoint.getX();  
//使用泛型时,不用强制转换,直接出来就是String  
System.out.println(p.getVar());   
(2)、在settVar()时如果传入类型不对,编译时会报错

可以看到,当我们构造时使用的是String,而在setVar时,传进去Integer类型时,就会报错。而不是像Object实现方式一样,在运行时才会报强制转换错误。
2、多泛型变量定义及字母规范
(1)、多泛型变量定义
上在我们只定义了一个泛型变量T,那如果我们需要传进去多个泛型要怎么办呢?
只需要在类似下面这样就可以了:
[java] view plain copy
class MorePoint<T,U>{  
}  
也就是在原来的T后面用逗号隔开,写上其它的任意大写字母即可。想加几个就加几个,比如我们想加五个泛型变量,那应该是这样的:
[java] view plain copy
class MorePoint<T,U,A,B,C>{  
}  
举个粟子,我们在Point上再另加一个字段name,也用泛型来表示,那要怎么做?代码如下:
[java] view plain copy
class MorePoint<T,U> {  
private T x;  
private T y; 
  
private U name;  
  
public void setX(T x) {  
this.x = x;  
}  
public T getX() {  
return this.x;  
}  
…………  
public void setName(U name){  
this.name = name;  
}  
  
public U getName() {  
return this.name;  
}  
}  
//使用  
MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();  
morePoint.setName("harvic");  
Log.d(TAG, "morPont.getName:" + morePoint.getName());  
从上面的代码中,可以明显看出,就是在新添加的泛型变量U用法与T是一样的。
(2)、字母规范
在定义泛型类时,我们已经提到用于指定泛型的变量是一个大写字母:
[java] view plain copy
class Point<T>{  
 …………  
}  
当然不是的!!!!任意一个大写字母都可以。他们的意义是完全相同的,但为了提高可读性,大家还是用有意义的字母比较好,一般来讲,在不同的情境下使用的字母意义如下:
 E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
 K,V — Key,Value,代表Map的键值对
 N — Number,数字
 T — Type,类型,如String,Integer等等
如果这些还不够用,那就自己随便取吧,反正26个英文字母呢。
再重复一遍,使用哪个字母是没有特定意义的!只是为了提高可读性!!!!
3、泛型接口定义及使用
在接口上定义泛型与在类中定义泛型是一样的,代码如下:

[java] view plain copy
interface Info<T>{// 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T x);  
}
与泛型类的定义一样,也是在接口名后加尖括号;
(1)、使用方法一:非泛型类
但是在使用的时候,就出现问题了,我们先看看下面这个使用方法:
[java] view plain copy
class InfoImpl implements Info<String>{   // 定义泛型接口的子类  
private String var ;// 定义属性  
public InfoImpl(String var){// 通过构造方法设置属性内容  
this.setVar(var) ;  
}  
@Override  
public void setVar(String var){  
this.var = var ;  
}  
@Override  
public String getVar(){  
return this.var ;  
}  
}  
  
public class GenericsDemo24{  
public  void main(String arsg[]){  
InfoImpl i = new InfoImpl("harvic");  
System.out.println(i.getVar()) ;  
}  
};  
首先,先看InfoImpl的定义:
[java] view plain copy
class InfoImpl implements Info<String>{ 
 …………  
}  
要清楚的一点是InfoImpl不是一个泛型类!因为他类名后没有<T>!
然后在在这里我们将Info<String>中的泛型变量T定义填充为了String类型。所以在重写时setVar()和getVar()时,IDE会也我们直接生成String类型的重写函数。
最后在使用时,没什么难度,传进去String类型的字符串来构造InfoImpl实例,然后调用它的函数即可。
[java] view plain copy
public class GenericsDemo24{  
public  void main(String arsg[]){  
InfoImpl i = new InfoImpl("harvic");  
System.out.println(i.getVar()) ;  
}  
};  
(2)、使用方法二:泛型类
在方法一中,我们在类中直接把Info<T>接口给填充好了,但我们的类,是可以构造成泛型类的,那我们利用泛型类来构造填充泛型接口会是怎样呢?

[java] view plain copy
interface Info<T>{// 在接口上定义泛型  
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
public void setVar(T var);  
}  
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
private T var ; // 定义属性  
public InfoImpl(T var){ // 通过构造方法设置属性内容  
this.setVar(var) ;
}  
public void setVar(T var){  
this.var = var ;  
}  
public T getVar(){  
return this.var ;  
}  
}  
public class GenericsDemo24{  
public static void main(String arsg[]){  
InfoImpl<String> i = new InfoImpl<String>("harvic");  
System.out.println(i.getVar()) ;  
}  
};  
最关键的是构造泛型类的过程:
[java] view plain copy
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
private T var ; // 定义属性  
public InfoImpl(T var){ // 通过构造方法设置属性内容  
this.setVar(var) ;
}  
public void setVar(T var){  
this.var = var ;  
}  
public T getVar(){  
return this.var ;  
}  
}  
在这个类中,我们构造了一个泛型类InfoImpl<T>,然后把泛型变量T传给了Info<T>,这说明接口和泛型类使用的都是同一个泛型变量。
然后在使用时,就是构造一个泛型类的实例的过程,使用过程也不变。
[java] view plain copy
public class GenericsDemo24{  
public static void main(String arsg[]){  
Info<String> i = new InfoImpl<String>("harvic");  
System.out.println(i.getVar()) ;  
}  
};  
使用泛型类来继承泛型接口的作用就是让用户来定义接口所使用的变量类型,而不是像方法一那样,在类中写死。
那我们稍微加深点难度,构造一个多个泛型变量的类,并继承自Info接口:
[java] view plain copy
class InfoImpl<T,K,U> implements Info<U>{   // 定义泛型接口的子类  
 private U var ;  
 private T x;  
 private K y;  
 public InfoImpl(U var){// 通过构造方法设置属性内容  
 this.setVar(var) ;  
 }  
 public void setVar(U var){  
 this.var = var ;  
 }  
 public U getVar(){  
 return this.var ;  
 }  
 }  
在这个例子中,我们在泛型类中定义三个泛型变量T,K,U并且把第三个泛型变量U用来填充接口Info。所以在这个例子中Info所使用的类型就是由U来决定的。
使用时是这样的:泛型类的基本用法,不再多讲,代码如下:
[java] view plain copy
public class GenericsDemo24{  
public  void main(String arsg[]){  
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");  
System.out.println(i.getVar()) ;  
}  
}  
4、泛型函数定义及使用
上面我们讲解了类和接口的泛型使用,下面我们再说说,怎么单独在一个函数里使用泛型。比如我们在新建一个普通的类StaticFans,然后在其中定义了两个泛型函数:
[java] view plain copy
public class StaticFans {  
//静态函数  
public static  <T> void StaticMethod(T a){  
Log.d("harvic","StaticMethod: "+a.toString());  
}  
//普通函数  
public  <T> void OtherMethod(T a){  
Log.d("harvic","OtherMethod: "+a.toString());  
}  
}  
上面分别是静态泛型函数和常规泛型函数的定义方法,与以往方法的唯一不同点就是在返回值前加上<T>来表示泛型变量。其它没什么区别。
使用方法如下:
[java] view plain copy
//静态方法  
StaticFans.StaticMethod("adfdsa");//使用方法一  
StaticFans.<String>StaticMethod("adfdsa");//使用方法二  
  
//常规方法  
StaticFans staticFans = new StaticFans();  
staticFans.OtherMethod(new Integer(123));//使用方法一  
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二  
结果如下:

首先,我们看静态泛型函数的使用方法:
[java] view plain copy
StaticFans.StaticMethod("adfdsa");//使用方法一  
StaticFans.<String>StaticMethod("adfdsa");//使用方法二  
从结果中我们可以看到,这两种方法的结果是完全一样的,但他们还有些区别的,区别如下:
方法一,可以像普通方法一样,直接传值,任何值都可以(但必须是派生自Object类的类型,比如String,Integer等),函数会在内部根据传进去的参数来识别当前T的类别。但尽量不要使用这种隐式的传递方式,代码不利于阅读和维护。因为从外观根本看不出来你调用的是一个泛型函数。
方法二,与方法一不同的地方在于,在调用方法前加了一个<String>来指定传给<T>的值,如果加了这个<String>来指定参数的值的话,那StaticMethod()函数里所有用到的T类型也就是强制指定了是String类型。这是我们建议使用的方式。
同样,常规泛型函数的使用也有这两种方式:
[java] view plain copy
StaticFans staticFans = new StaticFans();  
staticFans.OtherMethod(new Integer(123));//使用方法一  
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二  
可以看到,与平常一样,先创建类的实例,然后调用泛型函数。
方法一,隐式传递了T的类型,与上面一样,不建议这么做。
方法二,显示将T赋值为Integer类型,这样OtherMethod(T a)传递过来的参数如果不是Integer那么编译器就会报错。

进阶:返回值中存在泛型
上面我们的函数中,返回值都是void,但现实中不可能都是void,有时,我们需要将泛型变量返回,比如下面这个函数:
[java] view plain copy
public static <T> List<T> parseArray(String response,Class<T> object){  
List<T> modelList = JSON.parseArray(response, object);  
return modelList;  
}  
函数返回值是List<T>类型。至于传入参数Class<T> object的意义,我们下面会讲。这里也就是想通过这个例子来告诉大家,泛型变量其实跟String,Integer,Double等等的类的使用上没有任何区别,T只是一个符号,可以代表String,Integer,Double……这些类的符号,在泛型函数使用时,直接把T看到String,Integer,Double……中的任一个来写代码就可以了。唯一不同的是,要在函数定义的中在返回值前加上<T>标识泛型;
5、其它用法:Class<T>类传递及泛型数组
(1)、使用Class<T>传递泛型类Class对象
有时,我们会遇到一个情况,比如,我们在使用JSON解析字符串的时候,代码一般是这样的
[java] view plain copy
public static List<SuccessModel> parseArray(String response){  
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);  
return modelList;  
}  
其中SuccessModel是自定义的解析类,代码如下,其实大家不用管SuccessModel的定义,只考虑上面的那段代码就行了。写出来SuccessModel的代码,只是不想大家感到迷惑,其实,这里只是fastJson的基本用法而已。
这段代码的意义就是根据SuccessModel解析出List<SuccessModel>的数组。
[java] view plain copy
public class SuccessModel {  
private boolean success;  
  
public boolean isSuccess() {  
return success;  
}  
  
public void setSuccess(boolean success) {  
this.success = success;  
}  
}   
那现在,我们把下面这句组装成一个泛型函数要怎么来做呢?
[java] view plain copy
public static List<SuccessModel> parseArray(String response){  
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);  
return modelList;  
}  
首先,我们应该把SuccessModel单独抽出来做为泛型变量,但parseArray()中用到的SuccessModel.class要怎么弄呢?
先来看代码:
[java] view plain copy
public static <T> List<T> parseArray(String response,Class<T> object){  
List<T> modelList = JSON.parseArray(response, object);  
return modelList;  
}  
注意到,我们用的Class<T> object来传递类的class对象,即我们上面提到的SuccessModel.class。
这是因为Class<T>也是一泛型,它是传来用来装载类的class对象的,它的定义如下:
[java] view plain copy
public final class Class<T> implements Serializable {  
…………  
}  
通过Class<T>来加载泛型的Class对象的问题就讲完了,下面来看看泛型数组的使用方法吧。
(2)、定义泛型数组
在写程序时,大家可能会遇到类似String[] list = new String[8];的需求,这里可以定义String数组,当然我们也可以定义泛型数组,泛型数组的定义方法为 T[],与String[]是一致的,下面看看用法:
[java] view plain copy
//定义  
public static <T> T[] fun1(T...arg){  // 接收可变参数
   return arg ;// 返回泛型数组
}
//使用  
public static void main(String args[]){
   Integer i[] = fun1(1,2,3,4,5,6) ;  
   Integer[] result = fun1(i) ;  
}
我们先看看 定义时的代码:
[java] view plain copy
public static <T> T[] fun1(T...arg){  // 接收可变参数
   return arg ;// 返回泛型数组
}
首先,定义了一个静态函数,然后定义返回值为T[],参数为接收的T类型的可变长参数。如果有同学对T...arg的用法不了解,可以去找下JAVA 可变长参数方面的知识。
由于可变长参数在输入后,会保存在arg这个数组中,所以,我们直接把数组返回即可

##6. ArrayList和LinkedList的方法使用
ArrayList和LinkedList的区别(底层数据结构),什么时候用ArrayList什么时候用Linkedlist
1.ArrayList是实现了基于动态数组的数组结构,LinkedList基于链表的数据结构
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList需要移动指针
3.对于新增和删除操作add和remove,Linked比较占优势,因为ArrayList要移动数据
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
transient Object[] elementData; //动态数组
public boolean add(E e) {
// 检查数组的大小是否足够,如果不够将创建一个尺寸扩大一倍新数组,
//将原数组的数据拷贝到新数组中,原数组丢弃,这里会很
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

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


if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
*尺寸不够扩大创建新数组尺寸扩大一倍,数据拷贝到新数组,原数组丢弃
**/
 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);
}
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;
}
}

##7. ArrayList和LinkedList各自的工作原理分析原理分析
1.扩容
ArrayList里面维护了一个数组,add时:
检查数组的大小是否足够,如果不够将创建一个尺寸大一倍的新数组
将原数组的数据拷贝到新数组中,原数组丢弃,这里会很慢调用数组方法
Arrays.copyof(elementData,newCapacity)
2.多个元素发生移动
remove时:
将指定索引的元素移除是通过数组移动调用
System.arraycopy(elementData,index+1,elementData,Index,numMoved)
一次删除会有多个元素发生移动

同理,add(int ,E)向一个指定的位置加元素也会发生多个元素移动
而get/set则直接从数组中根据索引取出元素
public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
  public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
  E elementData(int index) {
        return (E) elementData[index];
    }
数组在内存中是连续存储的,所以她的索引速度非常快,而且赋值与修改元素也很简单

#LinkedList基于链表的数据结构
每个元素都包含了上一个和下一个元素的引用,所以add/remove只会影响到上一个和下一个元素。
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
{
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node f) {
// assert f == first && f != null;
final E element = f.item;
final Node next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size–;
modCount++;
return element;
}
}
linkedList get/set就慢了
get(int)传入索引与size的1/2比较,大于一半则从最后一个元素遍历挨个查找,小于一半则从第一个元素开始遍历挨个查找:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node node(int index) {
// assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

##8. Vector和Stack使用介绍
Vector作为List接口的实现类,底层也是采用数组来存储数据结构,
和ArrayList一样ArrayList与Vector一样,毕竟是先有Vector后有ArrayList.
但是Vector和ArrayList的不同的是:Vector是线程安全的,对外提供的读写方法,都是synchronized方法,有个别非synchronized方法,其实它内部也是synchronized的:比如:
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
而 ArrayList 是线程不安全的,当不涉及到多线程时,ArrayList 效率会高于 Vector。

##Stack
Stack实现了一个LIFO的栈的功能,它是继承于Vector来实现的,所以内部也是数组。

    CopyOnWriteArrayList
    CopyOnWriteArrayList 作为 List 接口的实现类,在
 jdk 1.5 被引入,其目的是提供一个 线程安全(相对于
 ArrayList 等非线程安全的 List) 并且 高效(相对于同样
线程安全的 Vector) 的 List。
	线程安全

CopyOnWriteArrayList 的内部实现机制正如它的名字一样:

Copy-on-Write,也就是“写时复制”,当有 写类型的操作作用到 CopyOnWriteArrayList 对象的时候,它们都会先获取锁,然后复制一份当前数据作为副本,然后在当前的数据副本上做修改,最后把修改提交,然后释放锁。

Array-List,也就是底层是使用数组(顺序表)来进行数据存储,大体实现机制和 ArrayList 差不多。

代码示例(add() 方法):
public boolean add(E e) {
//获取锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//生成数据副本
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
//在副本上进行数据的写(增、删、改)操作
newElements[len] = e;
//把当前修改过的副本提交,让此次修改对其他线程可见。
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}
高效
        CopyOnWriteArrayList 比 Vector 高效,主要有以下 2 个原因:

1、Vector 中,读写操作都被加锁了,而 CopyOnWriteArrayList 中,只有写操作才被加锁,而读操作没有进行加锁。这样,当 读 线程数量远大于 写 线程数量的时候,CopyOnWriteArrayList 尤为高效。

2、Vector 中,使用的是 内置锁(内置锁参考:Java内置锁的简单认识),而 CopyOnWriteArrayList 中,使用的是 jdk 1.5 引入的 ReentrantLock ,相比于 内置锁,ReentrantLock 的性能还是有所提升的。

##9. 使用多种方式遍历集合
结合类的通用遍历方式,用迭代器迭代
public class HelloWorld {
public static void main(String[] args) {
ArrayList al=new ArrayList();
al.add(“孙子”);
al.add(“儿子”);
al.add(“爸爸”);
al.add(“爷爷”);
//第一种
Iterator it=al.iterator();
while(it.hasNext()){
Object obj=it.next();
System.out.println(obj);
}
}
}
###Map遍历方式
public class HelloWorld {
public static void main(String[] args) {
Map<Integer,String> al=new HashMap<Integer,String>();
al.put(1,“孙子”);
al.put(2,“孙女”);
al.put(3,“儿子”);
al.put(4,“爸爸”);
al.put(5,“爷爷”);
//第一种获取所有的key按照key来遍历
for (Integer in:al.keySet()
) {
String str=al.get(in);
System.out.println(str);
}
//第二种通过Map.entrySet使用iterator遍历key和value
Iterator<Map.Entry<Integer,String>> it=al.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer,String> entry=it.next();
System.out.println(entry.getKey()+"----->"+entry.getValue());
}
//第三种通过Map.entrySet遍历key和value,推荐尤其是大量使用
for (Map.Entry<Integer,String> entry:al.entrySet()
) {
//Map.Entry<Integer,String>映射项(键值对)
System.out.println(entry.getKey()+"----->"+entry.getValue());
}
//4.通过Map.values()遍历所有的value,但不能遍历key
for (String v:al.values()
) {
System.out.println(v);
}
}
}
#List遍历方式
public class HelloWorld {
public static void main(String[] args) {
//List遍历方式
List l=new LinkedList();
l.add(“孙子”);
l.add(“儿子”);
l.add(“爸爸”);
l.add(“爷爷”);
((LinkedList) l).poll();
//第一种
/* for (Iterator iterator=l.iterator();iterator.hasNext()?{
int i=(Integer)iterator.next();
System.out.println(i);
}*/
System.out.println(l);
//第二种
Iterator iterator=l.iterator();
while(iterator.hasNext()){
String i=(String)iterator.next();
System.out.println(i);
}
//第三种
for (Object object:l){
System.out.println(object);
}
//第四种
for (int i=0;i<l.size();i++){
String j=(String)l.get(i);
System.out.println(j);
}
}
}
###数据元素是怎样在内存中存放的?
主要有2中存储方式:

1.顺序存储

这种方式,相邻的数据元素存放于相邻的内存地址中,整个内存地址是连续的。可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1).正常来说,只有基于数组实现的集合,才有这种特性,以ArrayList为代表

2.链式存储

这种方式,每一个数据元素,在内存中不要求处于相邻的位置,每个数据元素包含它下一个元素的内存地址。不可以根据元素的位置直接计算内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n),主要以链表为代表。Java中以LinkedList为代表

##10. 迭代器的使用和工作原理
每个遍历方式的实现原理是什么?
1.传统的for循环遍历,基于计数器的:
遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读
取到最后一个元素后,停止。主要就是按照元素的位置来读取元素
2.迭代器遍历,Iterator
每一个具体实现的数据集合,一般都需要提供相应的Iterator,相比于传统的
for循环,iterator取缔了显示的计算器。所以基于顺序存储集合的Iterator可
以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是
需要保存当前遍历的位置,然后根据当前位置来向前或者向后移动指针
3.foreach循环
根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现
只不过Java编译器来帮助我么生成了这些代码

各遍历方式的适用于什么场合?
1.传统的for循环遍历,基于计数器的:
顺序存储:读取性能比较高,适用于遍历顺出存储的集合。
链式存储:时间复杂度太大,不适用于遍历链式存储的集合
2.迭代器遍历,Iterator
顺序存储:如果不是太在意时间,推荐用这种方式,毕竟代码更加整洁。
链式存储:平均时间复杂度降为O(n)推荐使用
3.foreach循环遍历
foreach只是让代码更加简洁了,但是它有一些缺点。就是遍历的时候不能操作
数据,所以有些场合并不适用

#Java的最佳时间是什么?
Java数据集合框架中,提供了一个RandomAccess接口,该接口没有方法,只是
一个标记。通常被List接口的实现使用,用来标记该List的实现是否支持RandomAccess
一个数据集合实现了该接口,就意味着它支持RandomAccess接口,按位置读取元素的平均时间复杂度为O(1)比如ArrayList.
而没有实现该接口的,就表示不支持RandomAccess比如:LinkedList
所以如果想要遍历一个List,那么先判断是否支持RandomAccess,也就是list nstanceof RandomAccess
if (list instanceof RandomAccess) {
//使用传统的for循环遍历。
} else {
//使用Iterator或者foreach。
}

##11. HashSet和LinkedHashSet各自的工作原理分析
##国际习惯
了解一下HashSet这东西是啥?
public class HashSet extends AbstractSetimplements Set,Cloneable,java.io.Serializable{……}
继承自AbstractSet抽象类,实现了Set接口。该集合内无重复元素且遍历是无序
的。基本操作就是add跟remove那些方法,基本不变
###成员变量跟构造方法
static final long serialVersionUID =-5024744406713321676L;
private transient HashMap<E,Object>map;
private static final Object PRESENT=new Object();

    public HashSet() {
map = new HashMap<>();
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
HashSet的底层由一个HahsMap来实现,默认大小为0,而参数为集合的时候,默
认大小为0,而参数为集合的时候,默认大小会在集合数量与0.75的倍数加1,同
16之间取最大值,且其增删改查都是基于内部维护的HashMap来做对应的操作。
因为其底层基于HashMap,可以确定HashSet的遍历也是无序的。但是这样子就
有疑问了,HashMap允许空键(只允许存在一个),也允许不同键内多个空值,但是HashSet只允许非重复元素。如果这样的话,衍生了四个问题:那如果
HashSet的底层是HashMap的话,究竟我们add方法执行的时候,村的元素是在
key-value的哪一个?怎样存?怎样避免重复保存?还有那HashSet允许空值吗?
怎么实现无重复元素?
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
我笑了笑,因为它据然真的用map来操作的,HashSet调用add方法其实就是将元素
作为key,将HashSet内部维护一个标识对象存入map中,但是这里比较是否等于
null是啥意思?就这样来实现去重判断
这里我们需要回顾一下HashMap的put方法,因为HashSet本身就是对HashMap的
又一层封装
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
 
modCount++;
addEntry(hash, key, value, i);
return null;
}
 
private V putForNullKey(V value) {
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
 
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
 
createEntry(hash, key, value, bucketIndex);
}
 
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry<K,V> e = table[bucketIndex];
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
我们可以看到HashMap在做put操作的时候,其实是已经做对应结果返回了
如果是为存在的key,那么就创建Entry单向链表保存value值(只是在HashSet
的环境下我们只关注key就可以,value的值是什么根本没有意义),创建完成返
回null;如果是已经存在的key,那么就修改该key对应的hash值在哈希table中的
下标Entry,最后返回旧值
第三个问题的答案就是:HashSet只需要根据HashMap返回的结果,就可以知道现在
add所传的对象是否已经存在,并将结果返回。所以即使HashSet调用add方法返回了
false,其实内部交由HashMap去执行时候也是执行过依次put操作了,只是插入的值没有变化而已。

#LinkedHashSet
不错,唯一成员变量就是序列化id
private static final long serialVersionUID = -2851667679971038690L;
剩下就是构造函数
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll©;
}
可以确定的是,对内部维护的HashMap的加载因子是使用默认的0.75,且默认的Entry数组的大小根据不同的情况确定,无参则是16,有参则是在集合数量的2倍,
同11之间取最大值。

##12. 集合框架之Map接口
1.Map接口综述:
1.Java。util.Map<K,v>简介
位于Java.util包下的Map集合,是Java集合框架的重要成员,它是和Collection
接口同一等级的集合根接口
Map集合没有继承Collection接口,其提供的是key到value的映射,Map中不能包
含相同的key值,每个key只能影射一个相同的value.key值还决定了存储对象在映
射中的存储位置,但不是key对象本身决定的,而是通过散列技术进行处理,可产生
一个散列码的整数值,散列码通常用作一个偏移量,该偏移量对应分配给映射的内存
区域的起始位置,从而确定存储对象在映射中的存储位置,Map集合包括Map接口以
及Map接口所实现的类。
Map提供了一个更加通用的元素存储方法,Map集合类用于存储语速对(称作键和
值),其中每个键映射到一个值,从概念上而言,可以将List看作是具有数值的Map
而实际上,除了Lit和Map都在定义java.util中外,两者并没有直接的联系。
Map有以下三个重要的实现类
Hashtable:底层是哈希表数据结构,不可以存入null键null值,该集合是线程同步的
HashMap:底层是哈希表数据结构,允许使用null值null键,该集合是不同步的,将
hashtable代替
TreeMap底层是二叉树数据结构,线程不同步。可以用于给map集合中的键进行排序
#Map数据结构
Map接口中键和值一一映射. 可以通过键来获取值。

给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
当尝试修改一个只读的Map时,会抛出一个UnsupportedOperationException异常。

##两种常规Map实现
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
(1)HashMap(): 构建一个空的哈希映像
(2)HashMap(Map m): 构建一个哈希映像,并且添加映像m的所有映射
(3)HashMap(int initialCapacity): 构建一个拥有特定容量的空的哈希映像
(4)HashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的哈希映像
TreeMap:基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
(1)TreeMap():构建一个空的映像树
(2)TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素
(3)TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序
(4)TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序

两种常规Map性能 
HashMap:适用于在Map中插入、删除和定位元素。 
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。 

##13. TreeMap的方法使用和排序方式
TreeMap 和 HashMap 用法大致相同,但实际需求中,我们需要把一些数据进行排序;
以前在项目中,从数据库查询出来的数据放在List中,顺序都还是对的,但放在HashMap中,顺序就完全乱了。

为了处理排序的问题:
1. 对于一些简单的排序,如:数字,英文字母等
   

复制代码
 TreeMap hm = new TreeMap<String, String>(new Comparator() {
   public int compare(Object o1, Object o2) {
  //如果有空值,直接返回0
  if (o1 == null || o2 == null)
  return 0; 

 return String.valueOf(o1).compareTo(String.valueOf(o2));
   }
  });
复制代码
 


  备注:
compareTo(String str) :是String 提供的一个方法,如果参数字符串等于此字符串,
  则返回 0 值;如果按字典顺序此字符串小于字符串参数,则返回一个小于 0 的值;
  如果按字典顺序此字符串大于字符串参数,则返回一个大于 0 的值。

int compare(T o1,T o2):随第一个参数小于、等于或大于第二个参数而分别返回负整数、
零或正整数。
   

  2.对于处理有中文排序的问题


复制代码
 TreeMap hm = new TreeMap<String, String>(new Comparator() {
  public int compare(Object o1, Object o2) {
   //如果有空值,直接返回0
if (o1 == null || o2 == null)
  return 0; 

  CollationKey ck1 = collator.getCollationKey(String.valueOf(o1));
  CollationKey ck2 = collator.getCollationKey(String.valueOf(o2));
  return ck1.compareTo(ck2);  
}
  });
复制代码
 备注: CollationKey:CollationKey 表示遵守特定 Collator 对象规则的 String。

比较两个CollationKey 将返回它们所表示的 String 的相对顺序。使用 CollationKey
  来比较 String 通常比使用 Collator.compare 更快。因此,当必须多次比较 String 时
   (例如,对一个 String 列表进行排序),使用 CollationKey 会更高效。
public class TestSort {
public static void main(String[] args) {
    CollatorComparator comparator=new CollatorComparator();
    TreeMap map=new TreeMap(comparator);
    for (int i=0;i<10;i++){
        String s=""+(int)(Math.random()*1000);
        map.put(s,s);
    }
    map.put("ad","ad");
    map.put("ABC","ABC");
    map.put("aaa","bbb");
    map.put("爸爸","爸爸");
    map.put("儿子","儿子");
    map.put("弟弟","弟弟");
    Collection col=map.values();
    Iterator it=col.iterator();
    while(it.hasNext()){
        System.out.println(it.next());
    }
}
}

结果为:
291
294
415
443
500
617
7
961
bbb
ABC
ad
爸爸
弟弟
儿子
##14. 分析Set和Map之间的区别与联系
Java集合主要分为三种类型:
1.set(类)
2.List(列表)
3.Map(映射)

Java所有“存储及随机访问一连串对象”的做法,array是最有效率的一种。

1、效率高,但容量固定且无法动态改变。

array还有一个缺点是,无法判断其中实际存有多少元素,length只是告诉我们array的容量。

2、Java中有一个Arrays类,专门用来操作array。

 arrays中拥有一组static函数,

   equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。

   fill():将值填入array中。

   sort():用来对array进行排序。

   binarySearch():在排好序的array中寻找元素。

   System.arraycopy():array的复制。

若撰写程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。所以就要用到集合。
那我们开始讨论java中的集合。

集合分类:

Collection:List、Set

Map:HashMap、HashTable

Collection接口

Collection是最基本的集合接口,声明了适用于JAVA集合(只包括Set和List)的通用方法。 Set 和List 都继承了Conllection,Map。

Collection接口的方法:

 boolean add(Object o)  :向集合中加入一个对象的引用  
 void clear():删除集合中所有的对象,即不再持有这些对象的引用  
 boolean isEmpty():判断集合是否为空  
 boolean contains(Object o) : 判断集合中是否持有特定对象的引用  
 Iterartor iterator()  :返回一个Iterator对象,可以用来遍历集合中的元素  
 boolean remove(Object o) :从集合中删除一个对象的引用  
 int size()   :返回集合中元素的数目  
 Object[] toArray(): 返回一个数组,该数组中包括集合中的所有元素

关于:Iterator() 和toArray() 方法都用于集合的所有的元素,前者返回一个Iterator对象,后者返回一个包含集合中所有元素的数组。

Iterator接口声明了如下方法:

 hasNext():判断集合中元素是否遍历完毕,如果没有,就返回true  
 next() :返回下一个元素  
 remove():从集合中删除上一个有next()方法返回的元素。 

Set(集合)

Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。 Set接口主要实现了两个实现类:

• HashSet: HashSet类按照哈希算法来存取集合中的对象,存取速度比较快 
• TreeSet :TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。

Set 的用法:存放的是对象的引用,没有重复对象


Set set=new HashSet();  
 String s1=new String("hello");  
 String s2=s1;  
 String s3=new String("world");  
 set.add(s1);  
 set.add(s2);  
 set.add(s3);  
 System.out.println(set.size());//打印集合中对象的数目 为 2。
Set 的 add()方法是如何判断对象是否已经存放在集合中?


boolean isExists=false;  
Iterator iterator=set.iterator();  
while(it.hasNext())  {  
String oldStr=it.next();  
if(newStr.equals(oldStr)){  
isExists=true;  
} 
}
Set的功能方法

Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只 是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责)

Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

• HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。 
• TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。

LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

List(列表)

List的特征是其元素以线性方式存储,集合中可以存放重复对象。

• ArrayList() : 代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与删除元素的速度慢。 
• LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。

对于List的随机访问来说,就是只随机来检索位于特定位置的元素。 List 的 get(int index) 方法放回集合中由参数index指定的索引位置的对象,下标从“0” 开始。最基本的两种检索集合中的所有对象的方法:

for循环和get()方法:


for(int i=0; i<list.size();i++){  
System.out.println(list.get(i));  
}
2: 使用 迭代器(Iterator):


Iterator it=list.iterator();  
 while(it.hashNext()){  
 System.out.println(it.next());  
 }
List的功能方法

实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。

• List:次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推 荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元 素。 
• ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历 ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。 
• LinkedList :对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

Map(映射)

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。 
Map 的常用方法:

1 添加,删除操作:


Object put(Object key, Object value): 向集合中加入元素  
  Object remove(Object key): 删除与KEY相关的元素  
  void putAll(Map t): 将来自特定映像的所有元素添加给该映像  
  void clear():从映像中删除所有映射
  2 查询操作:

Object get(Object key):获得与关键字key相关的值 。Map集合中的键对象不允许重复,也就说,任意两个键对象通过equals()方法比较的结果都是false.,但是可以将任意多个键独享映射到同一个值对象上。

Map的功能方法

方法put(Object key, Object value)添加一个“值”(想要得东西)和与“值”相关联的“键”(key)(使用它来查找)。方法get(Object key)返回与给定“键”相关联的“值”。可以用containsKey()和containsValue()测试Map中是否包含某个“键”或“值”。 标准的Java类库中包含了几种不同的Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定“键”等价的策略等各不相同。 
执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索“键”是相当慢的。而这正是HashMap提高速 度的地方。HashMap使用了特殊的值,称为“散列码”(hash code),来取代对键的缓慢搜索。“散列码”是“相对唯一”用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。所有Java对象都 能产生散列码,因为hashCode()是定义在基类Object中的方法。 
HashMap就是使用对象的hashCode()进行快速查询的。此方法能够显着提高性能。

Map : 维护“键值对”的关联性,使你可以通过“键”查找“值”

HashMap:Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。

LinkedHashMap: 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。

TreeMap : 基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在 于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

WeakHashMao :弱键(weak key)Map,Map中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。

IdentifyHashMap: : 使用==代替equals()对“键”作比较的hash map。专为解决特殊问题而设计。

Collection 和 Map 的区别

容器内每个为之所存储的元素个数不同。

Collection类型者,每个位置只有一个元素。

Map类型者,持有 key-value pair,像个小型数据库。

各自旗下的子类关系

Collection
   --List:将以特定次序存储元素。所以取出来的顺序可能和放入顺序不同。
  --ArrayList / LinkedList / Vector
   --Set : 不能含有重复的元素
  --HashSet / TreeSet
   Map
   --HashMap
   --HashTable
   --TreeMap
其他特征

List,Set,Map将持有对象一律视为Object型别。Collection、List、Set、Map都是接口,不能实例化。继承自它们的 ArrayList, Vector, HashTable, HashMap是具象class,这些才可被实例化。vector容器确切知道它所持有的对象隶属什么型别。vector不进行边界检查。

总结

1. 如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。

2. 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。

3. 在除需要排序时使用TreeSet,TreeMap外,都应使用HashSet,HashMap,因为他们 的效率更高。

4. 要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。 
5. 容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至数列某位置。一旦将对象置入容器内,便损失了该对象的型别信息。

6. 尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

注意:

1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。

2、Set和Collection拥有一模一样的接口。

3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)

4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。

5、Map用 put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。

  HashMap会利用对象的hashCode来快速找到key。

6、Map中元素,可以将key序列、value序列单独抽取出来。

使用keySet()抽取key序列,将map中的所有keys生成一个Set。

使用values()抽取value序列,将map中的所有values生成一个Collection。

为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。

##15. Collections工具类的使用
Collections提供以下方法对List进行排序操作

void reverse(List list):反转

void shuffle(List list),随机排序

void sort(List list),按自然排序的升序排序

void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑

void swap(List list, int i , int j),交换两个索引位置的元素

void rotate(List list, int distance),旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。

int binarySearch(List list, Object key), 对List进行二分查找,返回索引,注意List必须是有序的

int max(Collection coll),根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)

int max(Collection coll, Comparator c),根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)

void fill(List list, Object obj),用元素obj填充list中所有元素

int frequency(Collection c, Object o),统计元素出现次数

int indexOfSubList(List list, List target), 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).

boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素。

设置不可变(只读)集合

Collections提供了三类方法返回一个不可变集合,

emptyXXX(),返回一个空的只读集合(这不知用意何在?)

singleXXX(),返回一个只包含指定对象,只有一个元素,只读的集合。

unmodifiablleXXX(),返回指定集合对象的只读视图。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值