Java复习之集合框架

发表于:2017-9-04 10:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:maoqitian    来源:51Testing软件测试网采编

  俗话说:温故而知新。想想学过的知识,就算是以前学得很不错,久不用了,就会忘记,所以温习一下以前学习的知识我认为是非常有必要的。而本篇文件温习的是 Java基础中的集合框架。
  为什么会有集合框架?
  平时我们用数组存储一些基本的数据类型,或者是引用数据类型,但是数组的长度是固定的,当添加的元素超过了数组的长度时,需要对数组进行重新的定义,这样就会显得写程序太麻烦,所以Java内部为了我们方便,就提供了集合类,能存储任意对象,长度是可以改变的,随着元素的增加而增加,随着元素的减少而减少。
  数组可以存储基本数据类型,也可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址,而集合只能存储引用数据类型(也就是对象),其实集合中也可以存储基本数据类型,但是在存储的时候会自动装箱变成对象。
  有了集合不意味着我们要抛弃数组,如果需要存储的元素个数是固定的,我们可以使用数组,而当存储的元素不固定,我们使用集合。
  集合的种类
  集合分为单列集合和双列集合。单列集合的根接口为Collection,双列结合的根接口为Map,两种集合都有基于哈希算法的集合类(HashMap和HashSet),现在我们可能会有疑问,到底是双列集合基于单列集合还是单列集合基于双列集合呢,下面我们来看往集合HashMap和HashSet添加元素的源码:
  /*
  *HashMap的put 方法 
  */
   public V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
      }
  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                     boolean evict) {
          Node<K,V>[] tab; Node<K,V> p; int n, i;
          if ((tab = table) == null || (n = tab.length) == 0)
              n = (tab = resize()).length;
          if ((p = tab[i = (n - 1) & hash]) == null)
              tab[i] = newNode(hash, key, value, null);
          else {
              Node<K,V> e; K k;
              if (p.hash == hash &&
                  ((k = p.key) == key || (key != null && key.equals(k))))
                  e = p;
              else if (p instanceof TreeNode)
                  e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
              else {
                  for (int binCount = 0; ; ++binCount) {
                      if ((e = p.next) == null) {
                          p.next = newNode(hash, key, value, null);
                          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                              treeifyBin(tab, hash);
                          break;
                      }
                      if (e.hash == hash &&
                          ((k = e.key) == key || (key != null && key.equals(k))))
                          break;
                      p = e;
                  }
              }
              if (e != null) { // existing mapping for key
                  V oldValue = e.value;
                  if (!onlyIfAbsent || oldValue == null)
                      e.value = value;
                  afterNodeAccess(e);
                  return oldValue;
              }
          }
          ++modCount;
          if (++size > threshold)
              resize();
          afterNodeInsertion(evict);
          return null;
      }
  /*
  * HashSet  的add 方法
  */
  public boolean add(E e) {
          return map.put(e, PRESENT)==null;
      }
  // PRESENT是一个Object 对象
   private static final Object PRESENT = new Object();
  由上源码,我们可以看出,双列集合的put方法源码中并没有出现add方法,而单列集合的add源码只能怪出现了put;我们可以知道单列集合是基于双列集合实现的。其实道理很简单,单列集合每次添加元素,只要添加key就可以,而key也是双列集合元素的唯一标识,而其值value则由一个Object对象代替并且隐藏,每次加入,输出元素都是隐藏单列结合的这个值, 底层基于双列集合,隐藏一列是很好实现的,而如果是单列集合要变成双列集合估计就会有很大的难度,就好比魔术师变魔术,魔术师变东西前肯定事先准备好要变的东西,只是将其隐藏,但是如果魔术师变魔术是真的从无到有,那我估计他就是神仙了,想要什么就变出来什么便是。
  单列集合
  首先我们看单列结合的继承图,单列集合的根接口是Collection,而List的实现类为ArrayList、LinkedList、Vector,Set接口的实现类是HashSet和TreeSet。
  集合框架 单列集合.jpg
  其次我们来看看各个集合的功能
  List集合的特有功能概述
      * void add(int index,E element) //集合中添加元素
      * E remove(int index) //删除集合中index位置上的元素
      * E get(int index)  //获取集合中index位置上的元素
      * E set(int index,E element) 将index位置上的元素替换成 element
  Vector类特有功能
      * public void addElement(E obj)  添加元素
      * public E elementAt(int index)  //获取元素
      * public Enumeration elements()  //获取元素的枚举(迭代遍历的时候用到)
  LinkedList类特有功能
      * public void addFirst(E e)及addLast(E e)  //集合头添加元素或者集合尾添加元素
      * public E getFirst()及getLast() //获取头元素 获取尾元素
      * public E removeFirst()及public E removeLast() //删除头元素删除尾元素
      * public E get(int index);//获取index元素
  根据上述LinkedList的功能,我们可以模拟栈获取队列的数据结构,栈是先进后出,队列为先进先出。
  /**
   * 模拟的栈对象
   * @author 毛麒添
   * 底层还是使用LinkedList实现
   * 如果实现队列只需要将出栈变为 removeFirst 
   */
  public class Stack {
      private LinkedList linklist=new LinkedList();
      
      /**
       * 进栈的方法
       * @param obj
       */
      public void in(Object obj){
          linklist.addLast(obj);
      }
      
      /**
       * 出栈
       */
      public Object out(){
          return linklist.removeLast();
      }
      /**
       * 栈是否为空
       * @return
       */
      public boolean isEmpty(){
          return linklist.isEmpty();
      }
  }
  //集合的三种迭代(遍历集合)删除
  ArrayList<String> list=new ArrayList();
          list.add("a");
          list.add("b");
          list.add("c");
          list.add("world");
  //1,普通for循环删除元素
  for(int i=0;i<list,size();i++){//删除元素b
       if("b".equals(list.get(i))){
          list.remove(i--);// i-- 当集合中有重复元素的时候保证要删除的重复元素被删除
    }
  }
  //2.使用迭代器遍历集合
  Iterator<String> it=list.iterator();
  while(it.hasNext){
     if("b".equals(it.next())){
       it.remove();//这里必须使用迭代器的删除方法,而不能使用集合的删除方法,否则会出现并发修改异常(ConcurrentModificationException)
    }
  }
  //使用增强for循环来进行删除
  for (String str:list){
       if("b".equals(str)){
         list.remove("b");//增强for循环底层依赖的是迭代器,而这里删除使用的依旧是集合的删除方法,同理肯定是会出现并发修改异常(ConcurrentModificationException),所以增强for循环一直能用来遍历集合,不能对集合的元素进行删除。
    }
  }
  接下里我们看Set集合,我们知道Set 集合存储无序,无索引,不可以存储重复的对象;我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数,其中HashSet底层基于哈希算法,当HashSet调用add方法存储对象,先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象,如果没有哈希值相同的对象就直接存入集合,如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存。下面给出HashSet存储自定义对象的一个例子,自定义对象需要重写hashCode()和equals()方法。
  /**
   * 自定义对象
   * @author 毛麒添
   * HashSet 使用的bean  重写了equals和HashCode方法
   */
  public class Person1 {
      private String name;
      private int age;
      public Person1() {
          super();
          // TODO Auto-generated constructor stub
      }
      public Person1(String name, int age) {
          super();
          this.name = name;
          this.age = age;
      }
      public String getName() {
          return name;
      }
      public void setName(String name) {
          this.name = name;
      }
      public int getAge() {
          return age;
      }
      public void setAge(int age) {
          this.age = age;
      }
      @Override
      public String toString() {
          return "Person1 [name=" + name + ", age=" + age + "]";
      }
      
      //使HashSet存储元素唯一
      @Override
      public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + age;
          result = prime * result + ((name == null) ? 0 : name.hashCode());
          return result;
      }
      @Override
      public boolean equals(Object obj) {
          System.out.println("equals调用了");
          if (this == obj)
              return true;
          if (obj == null)
              return false;
          if (getClass() != obj.getClass())
              return false;
          Person1 other = (Person1) 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;
      }   
  }
  public class Demo1_Hashset {
      public static void main(String[] args) {
          HashSet<Person1> hs=new HashSet<Person1>();
          hs.add(new Person1("张三", 23));
          hs.add(new Person1("张三", 23));
          hs.add(new Person1("李四", 24));
          hs.add(new Person1("李四", 24));
          hs.add(new Person1("李四", 24));
          hs.add(new Person1("李四", 24));
          System.out.println(hs);
      }
  运行结果如图,达到了不存储重复自定义对象的目的。其实HashSet的下面还有一个LinkedHashSet,底层是链表实现的,是set中唯一一个能保证怎么存就怎么取的集合对象,是HashSet的子类,保证元素唯一,与HashSet原理一样,这里就不多说了。
  HashSet 例子运行结果截图.png
  ●最后是TreeSet集合,该集合是用来进行排序的,同样也可以保证元素的唯一,可以指定一个顺序, 对象存入之后会按照指定的顺序排列。
  指定排序有两种实现:
  Comparable:集合加入自定义对象的时候,自定义对象需要实现Comparable接口,
  实现接口的抽象方法中返回0,则集合中只有一个元素
  返回正数,则集合中怎么存则怎么取,
  返回负数,集合倒序存储
  Comparator(比较器):
  创建TreeSet的时候可以制定 一个Comparator
  如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
  add()方法内部会自动调用Comparator接口中compare()方法排序
  调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
  原理:
  TreeSet底层二叉排序树
  返回小的数字存储在树的左边(负数),大的存储在右边(正数),相等则不存(等于0)
  在TreeSet集合中如何存取元素取决于compareTo()方法的返回值
  下面来看例子:
  /**
   * 自定义对象 用于TreeSet
   * @author 毛麒添
   *
   */
  public class Person implements Comparable<Person>{
      private String name;
      private int age;
      
      public Person(){
          super();
      }
      public Person(String name, int age) {
          super();
          this.name = name;
          this.age = age;
      }
      public String getName() {
          return name;
      }
      public void setName(String name) {
          this.name = name;
      }
      public int getAge() {
          return age;
      }
      public void setAge(int age) {
          this.age = age;
      }
      @Override
      public String toString() {
          return "Person [name=" + name + ", age=" + age + "]";
      }
      
      @Override
      public boolean equals(Object obj) {
          Person p=(Person) obj;
          return this.name.equals(p.name)&&this.age==p.age;
      }
      
      @Override
      public int hashCode() {
          // TODO Auto-generated method stub
          return 1;
      }
      
      /*//按照年龄进行排序(TreeSet)
      @Override
      public int compareTo(Person o) {
          int number=this.age-o.age;//年龄是比较的主要条件
          //当年龄一样的时候比较名字来确定排序
          return number==0?this.name.compareTo(o.name):number;
      }*/
      
      //按照姓名进行排序(TreeSet)
          @Override
          public int compareTo(Person o) {
              int number=this.name.compareTo(o.name);//姓名是比较的主要条件
              //当姓名一样的时候比年龄来确定排序
              return number==0?this.age- o.age:number;
          }
          
          //按照姓名长度进行排序(TreeSet)
                  /*@Override
                  public int compareTo(Person o) {
                      int length=this.name.length()-o.name.length();//姓名长度比较的次要条件
                      int number=length==0?this.name.compareTo(o.name):length;//姓名是比较的次要条件
                      //比年龄也是次要条件
                      return number==0?this.age- o.age:number;
                  }*/
  }
  /**
   * 
   * @author 毛麒添
   * TreeSet集合
   * 集合加入自定义对象的时候,自定义对象需要实现Comparable接口,
   * 实现接口的抽象方法中返回0,则集合中只有一个元素
   * 返回正数,则集合中怎么存则怎么取,
   * 返回负数,集合倒序存储
   * 
   * 将字符按照长度来进行排序在TreeSet中,需要使用有比较的构造方法进行比较。
   */
  public class Demo_TreeSet {
      public static void main(String[] args) {
          // TODO Auto-generated method stub
          demo1();//整数存取排序
          demo2();//自定义对象排序
          
          //将字符按照长度来进行排序在TreeSet中
          TreeSet<String> strLenset=new TreeSet<String>(new compareByLen());
          strLenset.add("aaaaa");
          strLenset.add("lol");
          strLenset.add("wrc");
          strLenset.add("wc");
          strLenset.add("b");
          strLenset.add("wnnnnnnnnnnn");
          
          System.out.println(strLenset);
      }
      private static void demo2() {
          TreeSet<Person> ptreeSet=new TreeSet<Person>();
          
          ptreeSet.add(new Person("李四",12));
          ptreeSet.add(new Person("李四",16));
          ptreeSet.add(new Person("李青",16));
          ptreeSet.add(new Person("王五",19));
          ptreeSet.add(new Person("赵六",22));
          
          System.out.println(ptreeSet);
      }
      private static void demo1() {
          TreeSet< Integer> treeSet=new TreeSet<Integer>();
          treeSet.add(1);
          treeSet.add(1);
          treeSet.add(1);
          treeSet.add(3);
          treeSet.add(3);
          treeSet.add(3);
          treeSet.add(2);
          treeSet.add(2);
          treeSet.add(2);
          
          System.out.println(treeSet);
      }
  }
  //TreeSet 构造器,实现compare对需要存储的字符串进行比较
  class compareByLen implements Comparator<String>{
      //按照字符串的长度进行比较,该方法的返回值和继承Comparable的compareTo方法返回值同理。
      @Override
      public int compare(String o1, String o2) {
          int num=o1.length()-o2.length();//以长度为主要条件
          return num==0?o1.compareTo(o2):num;//内容为次要条件
      }
      
  }
  下面是运行截图,其中compareTo的实现方法对几种条件排序进行了实现,具体可以查看Person自定义类中的实现。
  TreeSet 例子运行截图.png
  单列集合复习就到这里吧。
  双列集合
  同样的,在复习双列结合之前我们先看看双列集合的继承图。
  集合框架 双列集合.jpg

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号