Java集合(一)—— Collection

1、集合的引入

数组、集合是对多个数据进行存储操作的,简称容器。

在引入集合使用我们更多的使用的是数组。

1.1 数组的特点

  1. 数组一旦指定了长度,那么长度就被确定了,不可以更改了;
  2. 数组一旦声明了类型以后,数组中只能存放这个类型的数组,数组中只能存放同一种类型的数据。

1.2 数组的缺点

  1. 数组一旦指定了长度,那么长度就被确定了,不可以更改;
  2. 删除,增加元素,效率低;
  3. 数组中实际元素的数量是没有办法获取的,没有提供对应的方法或者属性来获取;
  4. 数组存储:有序,可重复;对于无序的,不可重复的数组不能满足要求

集合的引入正是为了解决了以上的缺点。

分类

集合主要包括两种 CollectionMap 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。

参考博客

http://www.cyc2018.xyz/Java/Java%20%E5%AE%B9%E5%99%A8.html#%E4%B8%80%E3%80%81%E6%A6%82%E8%A7%88

https://snailclimb.gitee.io/javaguide/#/docs/java/collection/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98

2、Collection

image-20191208220948084

2.1 List

存储的元素是有序的、可重复的

  • ArrayList:Object[] 数组;
  • Vector:Object[] 数组,线程安全;
  • LinkedList:双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
2.1.1 说说它们的区别?从何说?

从何说:从底层组成,数据结构的特性说

2.1.2 ArrayList 和 LinkedList 的区别
  1. 底层数据结构ArrayList底层使用的是Object数组;LinkedList 底层使用的是双向链表 数据结构(JDK 1.6 之前为双向循环链表,JDK 1.7 后取消了循环);
  2. 效率ArrayList 查找快(支持快速随机访问)、插入删除慢;LinkedList 查找慢、插入删除快;
  3. 内存空间占用ArrayList 的空间浪费主要体现,在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费主要在它的每一个元素都需要消耗比 ArrayList更多的空间,存放直接前驱和直接后继以及数据本身。
  4. 是否保证线程安全ArrayListLinkedList 都是不同步的,也就是不保证线程安全;
2.1.3 ArrayList 的扩容机制

从添加元素开始,判断是否扩容,默认扩容至原容器的 1.5倍左右(oldCapacity为奇数时,进行 oldCapacity >> 1)

以无参构造的ArrayList为例分析

  1. ArrayList 的成员变量和三个构造函数

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
    /**
     * 默认初始容器大小:10
     */
        private static final int DEFAULT_CAPACITY = 10;
    /**
     * 用于表示空数组的共享数组实例
     */
        private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * 用于默认大小的空实例的共享空数组实例。
     * 我们将其与EMPTY_ELEMENTDATA区别开来,以便了解在添加第一个元素时应该膨胀多少。
     */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     *  保存ArrayList数据的数组
     */
        transient Object[] elementData; // non-private to simplify nested class access
    
    /**
     * ArrayList 所包含的元素个数
     * 和 elementData.length 区分开 后者表示的是当前 elementData 的总容量
     */
        private int size;
    
    /**
      * 默认无参构造函数,创建一个具有标识的空数组实例
      * 对,使用无参构造函数创建实例后,这时容器仍是一个空数组
      * 在第一次添加元素时,容量扩容为10
      */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
        
    /**
      * 带初始容量参数的构造函数。(用户自己指定容量)
      */
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
    /**
      * 构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
      * 如果指定的集合为null,throws NullPointerException。
      */    
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    }
    
  2. ArrayList 的 add() 方法

    /**
    * 将指定的元素附加到列表的末尾。
    */
    public boolean add(E e) {
        // 添加元素之前执行方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // ArrayList 添加元素的本质就是给数组赋值
        elementData[size++] = e;
        return true;
    }
    
  3. ensureCapacityInternal(int minCapacity) 方法

    minCapacity = size + 1,意思就是新增元素时,该容器至少的容量大小是多于当前元素个数的 1 个

    // 第一次调用时,此时 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
    // 返回 DEFAULT_CAPACITY = 10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    // 此时的 minCapacity 是 size+1,也就是 当前元素的个数 + 1 = 1
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
  4. ensureExplicitCapacity(int minCapacity) 方法

    // 第一次调用时,此时 minCapacity = 10,当前 elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,长度为0
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    • 当我们要 add 进第 1 个元素到 ArrayList 时,elementData.length 为 0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为 10。此时,minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法。
    • 当 add 第 2 个元素时,minCapacity 为 2,此时 e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
    • 添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。
  5. grow(int minCapacity)方法

    /**
     * 数组能分配的最大大小
     */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    /**
     * ArrayList 扩容方法
     */
    private void grow(int minCapacity) {
        // 原容器的大小
        int oldCapacity = elementData.length;
        // 扩容,新容器的大小 = 原容器的1.5倍。
        // 使用位运算的速度远快于整除运算
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 主要用在当前容器容量为0时
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 当容器容量大于MAX_ARRAY_SIZE时
        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);
    }
    /**
     *	当 minCapacity 大于 MAX_ARRAY_SIZE 时,则返回 Integer.MAX_VALUE
     */
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
    }
    
    • 当 add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 hugeCapacity 方法。数组容量为 10,add 方法中 return true,size 增为 1。
    • 当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。
    • 以此类推······
2.1.4 System.arraycopy 和 Arrays.copyOf 的区别
  • System.arraycopy()将一个数组复制到目标数组中,目标数组长度必须足够,否则报错 ArrayIndexOutOfBoundsException

    // src:源数组;srcPos:源数组起始下标;dest:目标数组;destPos:目标数组起始下标;length:要复制的源数组的长度
    System.arraycopy(Object src,int  srcPos,Object dest,int destPos,int length)
        
    public static void test2(){
        System.out.println("==== System.arraycopy ====");
        int[] arr1 = {2,4,2,34,12,4};
        int[] arr2 = new int[10];
        arr2[0] = 7;
        arr2[1] = 8;
        arr2[2] = 9;
        System.arraycopy(arr1,0,arr2,1,arr1.length);
        System.out.println(Arrays.toString(arr2));
    }
    // 复制后的元素,原数组与目标数组重叠的下标,源数组的元素替换目标数组的元素
    ==== System.arraycopy ====
    [7, 2, 4, 2, 34, 12, 4, 0, 0, 0]
    
  • Arrays.copyOf 返回一个包含源数组所有元素的新数组,长度自定义

    public static int[] copyOf(int[] original, int newLength) 
    
    public static void test3(){
    System.out.println("==== Arrays.copyOf ====");
            int[] arr3 = {11,22,33,44,55};
            int[] arr4 = Arrays.copyOf(arr3,10);
            System.out.println(Arrays.toString(arr4));
    }
    
    ==== Arrays.copyOf ====
    [11, 22, 33, 44, 55, 0, 0, 0, 0, 0]
    
  • System.arraycopy() 需要原数组目标数组 ,将原数组拷贝到自定义的数组中,而且可以选择原数组的拷贝起点、长度、放在目标数组的位置;

  • Array.copyOf() 是系统自动在内部新建了一个数组,并返回该数组。(源码内部调用的仍是 System.arraycopy()

2.2 Set

存储的元素是无序的、不可重复的

  • HashSet:(无序、唯一)基于 HashMap 实现的,底层采用 HashMap 来保存元素,线程不安全,可以存储 null 值;
  • LinkedHashSet:LinkedHashSetHashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的,能够按添加顺序遍历
  • TreeSet:(有序、唯一)红黑树(自平衡的排序二叉树)

2.3 Comparable 和 Comparator 区别

  • 它们都是接口,都必须实现并重写相应方法才能使用,目的是为了 自定义排序;

  • Comparable 接口是排序的元素(一般为自定义类)实现的,并重写compareTo(T t) 方法,在方法中自定义排序方式;

    • compareTo(T t) 返回的是 int 类型,一般记忆为:this的值 大于 参数的值,return 1;this的值 小于 参数的值,return -1;升序
    • IntegerString 等类都实现了 Comparable 接口;
    public class Person implements Comparable<Person>{
        private Integer num;	// 注意这里类型是 Integer ,后面会用到
        private String name;
        private int age;
    
        // get、set方法省略
    	setter()/getter()...
    
        public Person() {
        }       
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public Person(Integer num, String name, int age) {
            this.num = num;
            this.name = name;
            this.age = age;
        }
        // 年龄升序
        @Override
        public int compareTo(Person p) {
            if(this.age > p.getAge()){
                return 1;
            } else if(this.age < p.getAge()){
                return -1;
            }
            return 0;
        }
        @Override
        public boolean equals(Object o) {
    		...
        }
        @Override
        public int hashCode() {
            ...
        }
        @Override
        public String toString() {
            ...
        }
    }
    
    /**
     *  要排序的类 Person 实现了 Comparable 接口,
     *  并重写了 Comparator 方法,按年龄升序
     */
        public static void comparableTest(){
    System.out.println("+++++++ comparableTest ++++++++");
    System.out.println("============ list ==========");
            List<Person> personList = new ArrayList<>();
            personList.add(new Person(3,"小贱",22));
            personList.add(new Person(8,"小红",5));
            personList.add(new Person(7,"小华",15));
            personList.add(new Person(9,"小明",20));
    
            Collections.sort(personList);
            Iterator iterator = personList.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next());
            }
            
    System.out.println("============ set ==========");
            TreeSet<Person> personTreeSet = new TreeSet<>();
            personTreeSet.add(new Person("小贱",22));
            personTreeSet.add(new Person("小红",5));
            personTreeSet.add(new Person("小华",15));
            personTreeSet.add(new Person("小明",20));
    
            Iterator iterator1 = personTreeSet.iterator();
            while(iterator1.hasNext()){
                System.out.println(iterator1.next());
            }
        }
    
    ============= 输出 ============
    +++++++ comparableTest ++++++++
    ============ list ==========
    Person{num=8, name='小红', age=5}
    Person{num=7, name='小华', age=15}
    Person{num=9, name='小明', age=20}
    Person{num=3, name='小贱', age=22}
    ============ set ==========
    Person{num=null, name='小红', age=5}
    Person{num=null, name='小华', age=15}
    Person{num=null, name='小明', age=20}
    Person{num=null, name='小贱', age=22}
    
  • Comparator 接口需自定义实现类,或在排序时使用匿名类实现,并重写 compare(T t1,T t2) 方法

    • compare(T t1,T t2) 返回的是 int 类型,一般记忆为: p1的值 大于 p2的值,return 1;p1的值 小于 p2的值,return -1;升序

      /**
       * 排序是实现 Comparator 接口,自定义排序
       */
          public static void comparatorTest(){
      System.out.println();
      System.out.println("+++++++ comparatorTest ++++++++");
              List<Person> personList = new ArrayList<>();
              personList.add(new Person(3,"小贱",22));
              personList.add(new Person(8,"小红",5));
              personList.add(new Person(7,"小华",15));
              personList.add(new Person(9,"小明",20));
      
              // 定制排序
              Collections.sort(personList, new Comparator<Person>() {
                  @Override
                  public int compare(Person p1, Person p2) {
                      if(p1.getNum() > p2.getNum()){
                          return -1;
                      } else if(p1.getNum() < p2.getNum()){
                          return 1;
                      }
                      return 0;
                      // 因为 num 定义为了 Integer 类型,这里也可以使用 Integer 定义的 compareTo 方法
                      // return p1.getNum().compareTo(p2.getNum());		// 默认升序
                  }
              });
              Iterator iterator = personList.iterator();
              while(iterator.hasNext()){
                  System.out.println(iterator.next());
              }
          }
      
      ========= 输出 =============
      +++++++ comparatorTest ++++++++
      Person{num=9, name='小明', age=20}
      Person{num=8, name='小红', age=5}
      Person{num=7, name='小华', age=15}
      Person{num=3, name='小贱', age=22}
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8中新增的Stream是一种处理集合的优雅姿势。 Stream是对集合Collection)对象功能的增强,它能以一种声明的方式来处理数据,实现类似于SQL语句的操作。Stream不会改变原有的数据结构,它会生成一个新的Stream,同时支持并行化操作。 Stream的核心思想是将数据看作是流,而流上可以进行各种操作,比如过滤、排序、映射等。这样可以将数据处理过程变得非常简洁和灵活。 下面是一些Stream的常用操作: 1. filter:过滤符合条件的元素 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().filter(i -> i % 2 == 0).forEach(System.out::println); //输出2, 4 ``` 2. map:将元素转换成另一种类型 ``` List<String> list = Arrays.asList("apple", "banana", "orange"); list.stream().map(s -> s.toUpperCase()).forEach(System.out::println); //输出APPLE, BANANA, ORANGE ``` 3. sorted:对元素进行排序 ``` List<Integer> list = Arrays.asList(5, 2, 1, 4, 3); list.stream().sorted().forEach(System.out::println); //输出1, 2, 3, 4, 5 ``` 4. distinct:去重 ``` List<Integer> list = Arrays.asList(1, 2, 3, 2, 1); list.stream().distinct().forEach(System.out::println); //输出1, 2, 3 ``` 5. limit:限制元素个数 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().limit(3).forEach(System.out::println); //输出1, 2, 3 ``` 6. skip:跳过元素 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); list.stream().skip(2).forEach(System.out::println); //输出3, 4, 5 ``` 7. reduce:对元素进行聚合操作 ``` List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); int sum = list.stream().reduce(0, (a, b) -> a + b); System.out.println(sum); //输出15 ``` Stream的操作可以组合起来形成一个流水线,每个操作都会返回一个新的Stream对象,这样就可以形成一个操作序列。最后调用终止操作(如forEach、findAny等)才会触发所有中间操作的执行。 使用Stream处理集合的代码通常比使用传统的循环更简洁,同时也更易于并行化处理,提高了程序的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值