💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## Java编程那些事儿80——集合框架简述 陈跃峰 出自:[http://blog.csdn.net/mailbomb](http://blog.csdn.net/mailbomb) ### 9.6.3 集合框架简述 在JDK API中专门设计了一组类,这组类的功能就是实现各种各样方式的数据存储,这样一组专门用来存储其它对象的类,一般被称为对象容器类,简称容器类,这组类和接口的设计结构也被统称为集合框架(Collection Framework)。 这组类和接口都包含在java.util包中。 为了使整个集合框架中的类便于使用,在设计集合框架时大量的使用接口,实际实现的功能类实现对应的接口,这样可以保证各个集合类的使用方式保持统一。 在集合框架中,提供的存储方式共有两种: 1、按照索引值操作数据 在这种存储方式中,为每个存储的数据设定一个索引值,存储在容器中的第一个元素索引值是0,第二个索引值是1,依次类推。在操作数据时按照索引值操作对应的数据,实现这种方式的集合类都实现java.util.Collection接口。 2、按照名称操作数据 在这种存储方式中,为每个存储的数据设定一个名称(任意非null的对象都可以作为名称),以后按照该名称操作该数据,要求名称不能重复,每个名称对应唯一的一个值。这种存储数据的方式也称作名称-数值对,也就是名值对存储。实现这种方式的几个类都实现java.util.Map接口。 这里“按照索引值操作数据”的存储方式,又按照容器内部是否能够存储重复的元素,划分成两类: 1、允许存储重复元素。 这种存储方式中,所有的类都实现了java.util.List接口。 2、不允许存储重复元素。 这种存储方式中,所有的类都实现了java.util.Set接口。 这样,集合框架中的类就分成了三大类: 1、List系列 该系列中的类按照索引值来操作数据,允许存放重复的元素。 2、Set系列 该系列中的类按照索引值来操作数据,不允许存放重复的元素。 3、Map系列 该系列中的类按照名称来操作数据,名称不允许重复,值可以重复,一个名称对应一个唯一的值。 而在数据结构中,实现数据的存储又可以使用不同的数据结构类型进行存储,例如数组、链表、栈、队列和树等,则以上三类集合框架可以使用不同的数据结构类进行实现,使用每种数据结构则具备该中数据结构的特点。例如使用数组则访问速度快,使用链表则便于动态插入和删除等,这样就造成了集合框架的复杂性。 另外,在将对象存储到集合类中,为了加快存储的速度,要求被存储对象的类中必须覆盖equals方法和hashCode方法。 对于这些集合类,下面按照以上三个系列的顺序一一进行说明。 #### 9.6.3.1 List系列 List系列的类均实现List接口,大部分的类都以List作为类名的后缀,也有部分该体系中的类命名比较特殊。 该系列中的类,比较常见的有ArrayList和LinkedList两个。其中ArrayList是以数组为基础实现的List,而LinkedList则是以链表为基础实现的List,ArrayList拥有数组的优点,而LinkedList拥有链表的优点。 由于该体系中的类均实现List接口,所以在这些类的内部,相同的功能方法声明是保持一致的,下面进行一一介绍: a、add方法 boolean add(Object o) 该方法的作用是追加对象o到已有容器的末尾。 另外一个add方法: void add(int index, Object element) 该方法的作用是将对象element插入到容器中索引值为index的位置,原来位于该位置的对象以及后续的内容将依次向后移动。 b、addAll方法 boolean addAll(Collection c) 该方法的作用是将容器对象c中的每个元素依次添加到当前容器的末尾。 另外一个addAll方法: boolean addAll(int index, Collection c) 该方法的作用是将容器对象c中的第一个元素插入到当前容器中索引值为index的位置,第二个元素插入到当前容器中索引值为index+1的位置,依次类推。而当前容器中原来位于index以及index索引值以后的元素则依次向后移动。 c、get方法 Object get(int index) 该方法的作用是返回当前容器对象中索引值为index的元素的内容。 d、indexOf方法 int indexOf(Object o) 该方法的作用是查找当前容器中是否存在对象o,如果存在则返回该对象第一次出现位置的索引值,如果不存在则返回-1。 另外一个方法lastIndexOf则是从末尾向前查找,返回从末尾向前第一次出现位置的索引值,如果不存在则返回-1。 e、remove方法 Object remove(int index) 该方法的作用是删除索引值为index的对象的内容,如果删除成功则返回被删除对象的内容。 另外一个remove方法: boolean remove(Object o) 该方法的作用是删除对象内容为o的元素,如果相同的对象有多个,则只删除索引值小的对象。如果删除成功则返回true,否则返回false。 无论使用哪一个remove方法,类内部都自动移动将被删除位置后续的所有元素向前移动,保证索引值的连续性。 f、set方法 Object set(int index, Object element) 该方法的作用是修改索引值为index的内容,将原来的内容修改成对象element的内容。 g、size方法 int size() 该方法的作用是返回当前容器中已经存储的有效元素的个数。 h、toArray方法 Object[] toArray() 该方法的作用是将当前容器中的元素按照顺序转换成一个Object数组。 下面是一个简单的以ArrayList类为基础实现的List系列中类基本使用的示例,代码如下: ~~~                    import java.util.*; /**  * 以ArrayList类为基础演示List系列类的基本使用  */ public class ArrayListUse {          public static void main(String[] args) {                    //容器对象的初始化                    List list = new ArrayList();                                     //添加数据                    list.add("1");                    list.add("2");                    list.add("3");                    list.add("1");                    list.add("1");                                //插入数据                    list.add(1,"12");                                   //修改数据                    list.set(2, "a");                                      //删除数据                    list.remove("1");                                  //遍历                    int size = list.size();  //获得有效个数                    //循环有效索引值                    for(int i = 0;i < size;i++){                             System.out.println((String)list.get(i));                    }          } } ~~~ 该程序的运行结果为: 12 a 3 1 1 在List系列中,还包含了Stack(栈)类和Vector(向量)类,Stack类除了实现List系列的功能以外,还实现了栈的结构,主要实现了出栈的pop方法和入栈的push方法。 而Vector类由于需要兼容老版本JDK中缘故,所以在实现的方法中需要提供老版本Vector类中对应的方法,这样导致Vector类中相同或类似的功能方法一般是成对出现的。 #### 9.6.3.2 Set系列 Set系列中的类都实现了Set接口,该系列中的类均以Set作为类名的后缀。该系列中的容器类,不允许存储重复的元素。也就是当容器中已经存储一个相同的元素时,无法实现添加一个完全相同的元素,也无法将已有的元素修改成和其它元素相同。 Set系列中类的这些特点,使得在某些特殊场合的使用比较适合。 该系列中常见的类有: 1、CopyOnWriteArraySet 以数组为基础实现的Set类。 2、HashSet 以哈希表为基础实现的Set类。 3、LinkedHashSet 以链表为基础实现的Set类。 4、TreeSet 以树为基础实现的Set类。 以不同的数据结构类型实现的Set类,拥有不同数据结构带来的特性,在实际使用时,根据逻辑的需要选择合适的Set类进行使用。 Set系列中的类的方法和List系列中的类的方法要比List系列中少很多,例如不支持插入和修改,而且对于Set系列中元素的遍历也需要转换为专门的Iterator(迭代器)对象才可以进行遍历,遍历时顺序和Set中存储的顺序会有所不同。 下面是以HashSet类为基础实现的示例代码,代码如下: ~~~ import java.util.*; /**  * 以HashSet为基础演示Set系列类的基本使用  */ public class HashSetUse {          public static void main(String[] args) {                    //容器对象的初始化                    Set set = new HashSet();                             //添加元素                    set.add("1");                    set.add("2");                    set.add("3");                    set.add("1");                    set.add("1");                                                  //删除数据                    //set.remove("1");                              //遍历                    Iterator iterator = set.iterator();                    while(iterator.hasNext()){                             System.out.println((String)iterator.next());                    }          } } ~~~ 该程序的运行结果为: 3 2 1 #### 9.6.3.3 Map系列 Map系列中的类都实现了Map接口,该系列中的部分类以Map作为类名的后缀。该系列容器类存储元素的方式和以上两种完全不同。 Map提供了一种使用“名称:值”这样的名称和数值对存储数据的方法,在该存储方式中,名称不可以重复,而不同的名称中可以存储相同的数值。具体这种存储的格式将在示例代码中进行实现。 在这种存储结构中,任何不为null的对象都可以作为一个名称(key)来作为存储的值(value)的标识,使用这种形式更利于存储比较零散的数据,也方便数据的查找和获得。Map类中存储的数据没有索引值,系统会以一定的形式索引存储的名称,从而提高读取数据时的速度。 该系列中常见的类有: 1、HashMap 以Hash(哈希表)为基础实现的Map类。 2、LinkedHashMap 以链表和Hash(哈希表)为基础实现的Map类。 3、TreeMap 以树为基础实现的Map类。 和上面的结构类似,以不同的数据结构实现的Map类,拥有不同数据结构的特点,在实际的项目中使用时,根据需要选择合适的即可。 该系列的类中常见的方法如下: a、get方法 Object get(Object key) 该方法的作用是获得当前容器中名称为key的结构对应的值。 b、keySet方法 Set keySet() 该方法的作用是返回当前容器中所有的名称,将所有的名称以Set的形式返回。使用这个方法可以实现对于Map中所有元素的遍历。 c、put方法 Object put(Object key, Object value) 该方法的作用是将值value以名称key的形式存储到容器中。 d、putAll方法 void putAll(Map t) 该方法的作用是将Map对象t中的所有数据按照原来的格式存储到当前容器类中,相当于合并两个Map容器对象。 e、remove方法 Object remove(Object key) 该方法的作用是删除容器中名称为key的值。 f、size方法 int size() 该方法的作用是返回当前日期中存储的名称:值数据的组数。 g、values方法 Collection values() 该方法的作用是返回当前容器所有的值组成的集合,以Collection对象的形式返回。 下面是一个简单的示例,在该示例中演示Map系列类的基本使用,代码如下: ~~~ import java.util.*; /**  * 以HashMap为基础演示Map系列中类的使用  */ public class HashMapUse {          public static void main(String[] args) {                    //容器对象的初始化                    Map map = new HashMap();                               //存储数据                    map.put("苹果", "2.5");                    map.put("桔子", "2.5");                    map.put("香蕉", "3");                    map.put("菠萝", "2");                                 //删除元素                    map.remove("桔子");                                  //修改元素的值                    map.put("菠萝", "5");                                 //获得元素个数                    int size = map.size();                    System.out.println("个数是:" + size);                              //遍历Map                    Set set = map.keySet();                    Iterator iterator = set.iterator();                    while(iterator.hasNext()){                             //获得名称                             String name = (String)iterator.next();                             //获得数值                             String value = (String)map.get(name);                             //显示到控制台                             System.out.println(name + ":" + value);                    }          } } ~~~ 该程序的运行结果为: 个数是:3 香蕉:3 菠萝:5 苹果:2.5 #### 9.6.3.4 使用示例 如前所述,集合框架中的类只是提供了一种数据存储的方式,在实际使用时,可以根据逻辑的需要选择合适的集合类进行使用。 下面以一个字符串计算的示例演示集合类的实际使用。 该程序的功能为计算一个数字字符串,例如”1+2*31-5”、”12*30/34-450”等,的计算结果,在该示例中支持四则运算,但是不支持括号。本示例中计算的字符串要求合法。 该程序实现的原理是:首先按照运算符作为间隔,将字符串差分为数字字符串和运算符字符串的序列,由于分拆出的字符串数量不固定,所以存储到List系列的Vector容器中,然后按照运算符的优先级进行计算。 该程序的代码如下: ~~~ import java.util.*; /**  * 计算字符串的值  */ public class CalcStr {          public static void main(String[] args) {                    String s = "1+20*3/5";                    double d = calc(s);                    System.out.println(d);          }                 /**           * 计算字符串的值           * @param s 需要计算的字符串           * @return 计算结果           */          public static double calc(String s){                    //拆分字符串                    Vector v = split(s);                    //print(v); //测试代码                    //计算字符串                    double d = calcVector(v);                    return d;          }                   /**           * 将字符串拆分为数字和运算符。           * 例如:"1+23*4"则拆分为:"1"、"+"、"23"、"*"和"4"           * @param s 需要拆分的字符串           * @return 拆分以后的结果           */          private static Vector split(String s){                    Vector v = new Vector();                    String content = "";                    int len = s.length(); //字符串长度                    char c;                    for(int i = 0;i < len;i++){                             c = s.charAt(i);                             //判断是否为运算符                             if(c == '+' ||                                      c == '-' ||                                       c == '*' ||                                        c == '/'){                                      //存储数字                                      v.add(content);                                      //存储运算符                                      v.add("" + c);                                      //清除已有字符串                                      content = "";                             }else{                                      content += c; //连接字符串                             }                    }                    v.add(content); //添加最后一个数字                    return v;          }                   /**           * 测试代码,输出拆分以后的结果           * @param v 需要打印的Vector对象           */          private static void print(Vector v){                    int size = v.size();                    for(int i = 0;i < size;i++){                             System.out.println((String)v.get(i));                    }          }                   /**           * 计算Vector中的数据           * @param v 存储拆分后字符串的Vector           * @return 计算结果           */          private static double calcVector(Vector v){                    int index1;                    int index2;                    //计算乘除                    while(true){                             index1 = v.indexOf("*"); //乘号索引值                             index2 = v.indexOf("/"); //除号索引值                             //无乘除符号                             if(index1 == - 1 && index2 == -1){                                      break;  //结束循环                             }                                                       //如果有乘号                             if(index1 != -1){                                      //没有除号或乘号在前                                      if(index2 == -1 || index1 < index2){                                                String s1 = (String)v.get(index1 - 1); //第一个数字                                                String opr = (String)v.get(index1); //运算符                                                String s2 = (String)v.get(index1 + 1); //第二个数字                                                //计算                                                String answer = calc(s1,s2,opr);                                                //计算以后的处理                                                handle(answer,index1 - 1,v);                                      }                             }                             //有除号                             if(index2 != -1){                                      //没有乘号或除号在前                                      if(index1 == -1 || index2 < index1){                                                String s1 = (String)v.get(index2 - 1); //第一个数字                                                String opr = (String)v.get(index2); //运算符                                                String s2 = (String)v.get(index2 + 1); //第二个数字                                                //计算                                                String answer = calc(s1,s2,opr);                                                //计算以后的处理                                                handle(answer,index2 - 1,v);                                      }                             }                    }                    //计算加                    int index3 = v.indexOf("+");                    while(index3 != -1){ //有加号                             String s1 = (String)v.get(index3 - 1); //第一个数字                             String opr = (String)v.get(index3); //运算符                             String s2 = (String)v.get(index3 + 1); //第二个数字                             //计算                             String answer = calc(s1,s2,opr);                             //计算以后的处理                             handle(answer,index3 - 1,v);                             //获得下一个加号的位置                             index3 = v.indexOf("+");                    }                                       //计算减                    index3 = v.indexOf("-");                    while(index3 != -1){ //有加号                             String s1 = (String)v.get(index3 - 1); //第一个数字                             String opr = (String)v.get(index3); //运算符                             String s2 = (String)v.get(index3 + 1); //第二个数字                             //计算                             String answer = calc(s1,s2,opr);                             //计算以后的处理                             handle(answer,index3 - 1,v);                             //获得下一个减号的位置                             index3 = v.indexOf("-");                    }                    //反馈结果                    String data = (String)v.get(0);                    return Double.parseDouble(data);          }                   /**           * 计算两个字符串类型的值运算结果           * @param number1 数字1           * @param number2 数字2           * @param opr 运算符           * @return 运算结果           */          private static String calc(String number1,String number2,String opr){                    //将字符串转换为数字                    double d1 = Double.parseDouble(number1);                    double d2 = Double.parseDouble(number2);                    //判断运算符                    if(opr.equals("+")){                             return "" + (d1 + d2);                    }                    if(opr.equals("-")){                             return "" + (d1 - d2);                    }                    if(opr.equals("*")){                             return "" + (d1 * d2);                    }                    if(opr.equals("/")){                             return "" + (d1 / d2);                    }                    return "0";  //运算符错误时返回0          }                   /**           * 计算以后的处理           * @param answer 计算结果           * @param index 参与计算的三个字符串中第一个字符串的起始位置           * @param v 存储字符串的容器           */          private static void handle(String answer,int index,Vector v){                    //删除计算过的字符串                    for(int i = 0;i < 3;i++){                             v.remove(index);                    }                    //将计算结果插入到index位置                    v.insertElementAt(answer, index);          } } ~~~ 该程序的运行结果为: 13.0 ### 9.7 总结 在本章中,主要介绍了java.lang包和java.util包中比较常见的类的使用,熟悉了JDK API的基本使用,掌握了文档的查阅以及方法调用等一些基本的技能,为后续章节的学习打下坚实的基础。