Java集合基础整理

  • Collection接口
  • Collection接口的常用方法
  • Collection与Collections的区别
  • List接口
  • List接口的常用方法
  • ArrayList
  • Arrays类的常见操作
  • LinkedList
  • Vector
  • List 相关题目
  • List 两种遍历方法
  • Set接口
  • HashSet
  • LinkedHashSet
  • TreeSet
  • TreeSet自然排序
  • TreeSet定制排序
  • Set相关题目
  • Map接口
  • Map接口的常用方法
  • HashMap
  • LinkedHashMap
  • TreeMap
  • Hashtable
  • Properties
  • Map相关题目


集合是java中提供的一种容器,可以用来存储多个数据。集合数组的区别在于:数组的长度是固定的。集合的长度是可变的。数组中存储的是同一类型的元素,可以存储任意类型数据。集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类型。Java里边主要有两种集合,Collection接口和Map接口

java集合的练习 java基础集合_数组

Collection接口

Collection接口:单列数据,定义了存取一组对象的方法的集合。集合层次中的根接口,JDK没有提供这个接口直接的实现类。

Collection接口是List、Set接口的父接口,该接口里定义的方法既可用于操作List集合,也可用于操作Set集合。

Collection接口的常用方法

方法名称

描述

public boolean add(E e)

向集合中插入一个元素

public boolean addAll(Collection<? extends E> c)

将指定集合中的所有元素添加到此集合

public void clear( )

清空集合中的所有元素

public boolean contains(Object o)

查找一个元素是否存在

public boolean containsAll(Collection<?> c)

如果集合包含指定集合中的所有元素,则返回true

public Iterator< E> iterator( )

返回此集合中元素的迭代器

public boolean remove(Object o)

从集合中删除一个对象

public boolean removeAll(Collection<?> c)

从集合中删除一组对象

public boolean removeIf(Predicate<? super E> filter)

删除满足给定谓词的所有此集合的元素

public boolean retainAll(Collection<?> c)

仅保留此集合中包含在指定集合的元素

public int size( )

返回此集合中的元素数

public int hashCode( )

返回此集合的哈希吗值

public boolean isEmpty( )

判断集合是否为空

public Object[ ] toArray( )

返回一个包含此集合中所有元素的数组

< T>T[ ] toArray(T[ ] a)

返回包含此集合中所有元素的数组;返回的数组的运行时类型是指定数组的运行时类型

public boolean equals(Object o)

将指定的对象与此集合进行比较以获得相等性

Collection与Collections的区别

Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collections 是一个包装类,它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

成员方法

sort(List< T>)

根据元素的自然顺序,将指定列表按升序排序

max(Collection< T>)

返回集合的最大元素

reverse(List< T>)

反转List集合元素

shuffle(List< T>)

使用默认的随机源随机置换指定的列表

public class Test{
    public static void main(String[] args) {

        //创建一个List
        List<Integer> list = new ArrayList<>();

        //添加数据
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(5);
        list.add(5);
        list.add(6);
        list.add(2);
        list.add(4);
        System.out.println(list);
        
        System.out.println("集合中最大的元素为:" + Collections.max(list));
        Collections.sort(list);
        System.out.println("排序之后的集合:" + list);
        Collections.reverse(list);
        System.out.println("反转之后的集合:" + list);
        Collections.shuffle(list);
        System.out.println("打乱之后的集合:" + list);
    }
}

List接口

List接口是 Collection的子接口,记录元素的保存顺序,且允许有重复元素。和数组类似,List可以动态增长,查找元素效率高,相对的插入删除元素效率低,因为会引起其他元素位置改变。
List接口的实现类主要有:ArrayListLinkedListVector

List接口的常用方法

方法名称

描述

public boolean add(E e)

将指定的元素追加到此列表的末尾

public void add(int index, E element)

将指定的元素插入此列表中的指定位置

public boolean addAll(Collection c)

清按指定集合的迭代器返回的顺序将指定集合中的所有元素附加到此列表的末尾

public boolean addAll(int index, Collection c)

将指定集合中的所有元素插入到此列表中的指定位置

public void clear( )

从此列表中删除所有元素

public boolean contains(Object o)

如果此列表包含指定的元素,则返回true

public E get(int index)

返回此列表中指定位置的元素

public int hashCode( )

返回此列表的哈希码值

public int removeIf(Predicate<? super E> filter)

删除满足给定谓词的所有此集合的元素

public boolean retainAll(Collection<?> c)

仅保留此集合中包含在指定集合的元素

public int size( )

返回此集合中的元素数

public int indexOf(Object o)

返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1

public boolean isEmpty( )

判断集合是否为空

public Iterator iterator( )

以正确的顺序返回该列表中的元素的迭代器

public int lastIndexOf(Object o)

返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1

public E remove(int index)

删除该列表中指定位置的元素

public E set(int index, E element)

用指定的元素替换此列表中指定位置的元素

public int size( )

返回此列表中的元素数

ArrayList

ArrayList是List接口的主要实现类,ArrayList是一个动态数组,由于基于数组方式实现,无容量的限制,在执行插入元素时可能要扩容,在删除元素时并不会减少数组的容量,非线程安全。允许任何符合规则的元素插入包括null,它的规模可变并且能像链表一样被访问。它提供的功能类似Vector类但不同步,它是以Array方式实现的List,允许快速随机访问存储的元素,支持随机访问, 查询速度快, 增删元素慢。

Arrays类的常见操作

此处再辨别下Java中的Arrays类,这是JDK中提供了一个专门用于操作数组的工具类,位于java util 中,常用方法有:

方法名称

描述

public String toString (array)

将数组array转换成字符串

public void sort (array)

对数组进行升序排列

public void fill (arr, val)

将数组arr全部元素赋值为val

public boolean equals (arr1, arr2)

判断两个数组是否相等

public arr copyOf (arr, ength)

将数组arr复制成一个长度为length的新数组

public int binarySearch(arr, val)

查询元素val在arr中的下标值

import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        int a[]={12,20,13,42,72,26,35,10,46,26,53};
        int b[]={3,5,7,8,54,23,9};
        int c[]={3,5,7,8,54,23,9};

        String str=Arrays.toString(a);       //将特点数组转为字符串
        System.out.println("字符串:"+str);

        Arrays.sort(a);                      //对数组array的元素进行升序排列
        System.out.println("排序后:"+Arrays.toString(a));

        Arrays.fill(a,99);               //所以元素都赋值成特定值
        System.out.println("赋值后:"+Arrays.toString(a));

        boolean boo1=Arrays.equals(a,b);     //判断两个数组是否相等
        boolean boo2=Arrays.equals(b,c);
        System.out.println("a:"+a);
        System.out.println("b:"+b);
        System.out.println("c:"+c);
        System.out.println("ab相等?"+boo1);
        System.out.println("bc相等?"+boo2);


        int d[]=Arrays.copyOf(b,b.length);   //把数组复制成特定长度的数组,与直接赋值不同
        System.out.println("d:"+Arrays.toString(d));
        System.out.println("d:"+d);
        System.out.println("b:"+b);

        int i=Arrays.binarySearch(b, 5); //查询特定因素在数组中的下标
        System.out.println("下标是:"+i);
    }
}

LinkedList

LinkedList基于双向链表机制实现,提供最佳顺序存取,适合插入和移除元素,非线程安全。由这个类定义的链表也可以像栈或队列一样被使用。特点是频繁的插入或删除元素时有较好的性能,随机访问集合中的元素性能较差。

Vector

Vector基于Object数组的方式来实现的。基于synchronized实现的线程安全的ArrayList,是线程同步的,效率很低,在插入元素时容量扩充的机制和ArrayList稍有不同。

List 相关题目

1、Collection框架中实现比较要实现什么接口

  1. Comparable/Comparator

2、ArrayList和Vector的区别

  1. 线程同步,Vector线程安全,ArrayList线程不安全
  2. 效率问题,Vector效率低,ArrayList效率高
  3. 增长数量,Vector以2倍增长,ArrayList以1.5倍增长
  4. Vector还有一个子类Stack

3、ArrayList和LinkedList的区别

  1. 都是线程不安全,相对线程安全的Vector,执行效率高。
  2. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  3. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  4. 对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

4、说出ArrayList,Vector, LinkedList的存储性能和特性

  1. ArrayList和Vector使用数组存储元素;LinkedList使用链表存储元素
  2. ArrayList和Vector插入删除数据时,需要搬运数据,效率较差;LinkedList使用链表,不需要搬运数据,效率高
  3. ArrayList和Vectory查询时,按数组下标查询,不需要遍历,效率高;LinkedList需要遍历,查询效率底

5、去掉一个Vector集合中重复的元素

  1. 自行遍历,用另外一个Vector来判断是否有重复
  2. 用Set(TreeSet或HashSet)来去重
  3. 用Apache的CollectionUtil工具类去重
  4. 还有一种简单的方式,HashSet set = new HashSet(vector);

List 两种遍历方法

增强for循环

//简化数组和集合的遍历
for(Object o : list) {
    System.out.println(o);
}

迭代器
对过程的重复,称为迭代。迭代器是遍历Collection集合的通用方式。可以在对集合遍历的同时进行添加、删除等操作。
常用方法
next() :返回迭代的下一个元素对象
hasNext() :如果仍有元素可以迭代,则返回true
列表迭代器是List体系独有的遍历方式,可以在对集合遍历的同时进行添加、 删除等操作,但是必须通过调用列表迭代器的方法来实现。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class Test {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");

        Iterator it = list.iterator();
        while (it.hasNext()){                      //如果迭代器中有元素,就一直迭代
            String s = (String) it.next();
            System.out.println(s);
        }
        //需求:判断集合中如果有字符串"b",就在其后边添加一个新的字符串: java
        ListIterator lit = list.listIterator();    //创建列表迭代器
        while (lit.hasNext()){
            String s1 = (String) lit.next();
            if ("b".equals(s1)) {
                lit.add("java");                   //只有列表迭代器可以添加值
            }
            System.out.println(s1);
        }
        System.out.println(list);
        
    }
}

Set接口

Set 是Collection的子接口,不记录元素的保存顺序,且不允许有重复元素,该接口没有提供额外的方法,检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 。Set集合中的元素不按特定方式排序,只是简单的把对象加入集合中,就像往口袋里放东西。Set接口不允许包含相同的元素果试把两个相同的元素加入同一个Set集合中,则会添加操作失败。Set接口判断两个对象是否相同是根据equals() 方法 ,而不是使用 == 运算符。

HashSet

HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。HashSet 能够快速定位一个元素,无序不可重复,底层是HashMap,存放的值在HashMap的key部分。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
1、HashSet 具有以下特点:

  • 不能保证元素的排列顺序
  • HashSet不是线程安全的
  • 集合元素可以是null

2、HashSet集合判断两个元素相等
两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。

3、HashSet添加元素的原理
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值,通过某种散列函数决定该对象在HashSet底层数组中的存储位置。如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,则添加失败。如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会通过链表的方式继续链接存储。如果两个元素的equals() 方法返回true,但它们的 hashCode() 返回值不相等,hashSet将会把它们存储在不同的位置,但依然可以添加成功。

4、重写hashCode() 方法的基本原则
在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法的返回值应该相等。对象中用作equals()方法比较的实例变量,都应该用来计算hashCode值。

import java.util.Objects;

public class Student {
    private int id;
    private String name;

    //无参构造与全参构造
    // get与set方法
    // toString方法
    // equals与hashCode方法
    ...
}

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        Student s1 = new Student(1, "张三");
        Student s2 = new Student(2, "李四");
        Student s3 = new Student(3, "王二");
        Student s4 = new Student(3, "王二");

        set.add(s1);
        set.add(s2);                           //多次添加会自动依据equals()和hashCode()两种方法去重
        set.add(s3);
        set.add(s4);

        Iterator<Student> it = set.iterator(); //使用迭代器来遍历
        while (it.hasNext()) {
            Student s = it.next();
            System.out.println(s);
        }

        System.out.println(set);
        for (Student student : set) {
            System.out.println(student);       //打印顺序与添加顺序并不相同
        }
    }
}

LinkedHashSet

LinkedHashSet是HashSet的子类,LinkedHashSet也是根据元素的hashCode值来决定元素的存储位置。但它同时使用双向链表维护元素的次序,元素的顺序与添加顺序一致。
由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。LinkedHashSet不允许集合元素重复。

TreeSet

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。TreeSet底层使用红黑树结构存储数据元素。TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。

TreeSet自然排序

TreeSet会调用集合元素的compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口。实现Comparable的类必须实现compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小。对于TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj) 方法比较返回值。当需要把一个对象放入TreeSet 中,重写该对象对应的equals() 方法时,应保证该方法与compareTo(Object obj) 方法有一致的结果。

TreeSet定制排序

TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素,希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2。要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。

Set相关题目

1、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用==还是equals( )?它们有何区别?

  • Set里的元素是不能重复的,元素重复与否视具体情况而定:
  1. HashSet使用equals比较
  2. TreeSet使用compareTo进行比较

2、两个对象值相同(x.equals(y) == true),但却可有不同的hashcode,这句话对不对?

  • 不对,如果两个对象x 和 y 满足equals等,它们的hashCode应当相同,因此重写equals方法必须重写hashCode
  1. hashCode等,equals不一定同,但hashCode最好散列化
  2. 任何对象equals null都得false
  3. 没有继承关系的两个类,equals都得false
  4. 重写equals方法的类最好是值类,即不可变

3、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常?

  • 应该是没有针对问题的确切的答案,当前的add方法放入的是哪个对象,就调用哪个对象的compareTo方法,至于这个compareTo方法怎么做,就看当前这个对象的类中是如何编写这个方法的

Map接口

MapCollection并列存在,Map提供key到value的映射。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。Map中的key用Set来存放,不允许重复,即同一个Map对象所对应的类,须重写hashCode()和equals()方法。Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。

Map接口的常用方法

方法名称

描述

public V put(K key, V value)

向集合中添加元素

public V get(Object key)

通过指定key获取value

public void clear( )

清空集合中的所有元素

public int size( )

返回此集合中的元素数

public boolean isEmpty( )

判断集合是否为空

public boolean containsKey(Object key)

判断集合中是否包含指定key

public boolean containsValue(Object value)

判断集合中是否包含指定value

public Set<泛型> keySet( )

获取集合中所有的key,返回一个包含所有key元素的Set集合

Collection values( )

获取集合中所有的value,返回一个包含所有value元素的Collection集合

V remove(Object key)

删除指定key的键值对

public Set<Map.Entry<K,V>> entrySet()

将Map集合转换成Set集合

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Test {
    public static void main(String[] args) {
        Map<Integer,Student> map = new HashMap<>();

        Student s1 = new Student(1,"张三");
        Student s2 = new Student(2,"李四");
        Student s3 = new Student(3,"张三");

        System.out.println(map.put(1,s2));                   //利用键去覆盖数值的时候会返回被覆盖的值
        System.out.println(map.put(1,s1));
        map.put(2,s2);
        map.put(3,s3);
        map.remove(3);

        System.out.println(map);
        
        Set<Integer> keys = map.keySet();                     //获取map中的key,组成一个set集合
        Iterator<Integer> it = keys.iterator();               //将set集合变为一个迭代器
        while (it.hasNext()){                                 //遍历key组成的迭代器
            Integer key = it.next();
            System.out.println("key:"+ key +" value:"+map.get(key));    //打印结果
        }

        for (Integer integer : map.keySet()) {                //使用for遍历所有的key
            System.out.println(map.get(integer));
        }
        for (Student value : map.values()) {                  //使用for遍历所有的value
            System.out.println(value);
        }
    }
}

HashMap

HashMap是Map接口使用频率最高的实现类。HashMap 实际上是一个 “链表散列” 的数据结构,即数组和链表的结合体。HashMap 的底层结构是一个数组,数组中的每一项是一条链表。HashMap 是 Hashtable 的轻量级实现,不同之处在于HashMap是非同步的,允许使用null键和null值,与HashSet一样,不保证映射的顺序。

  • 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
  • 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
  • HashMap判断两个key相等的标准是:两个key通过equals() 方法返回true, hashCode值也相等。
  • HashMap判断两个value相等的标准是:两个value通过equals()方法返回true。
  • HashMap可以使用null值为key或value

HashMap的存储结构

  • HashMap的存储结构是数组+链表+红黑树实现。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
  • 每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

LinkedHashMap

LinkedHashMap 是 HashMap的子类,在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。 该链表负责维护Map的迭代顺序,与插入顺序一致,因此性能比HashMap低,但在迭代访问Map里的全部元素时有较好的性能。

TreeMap

TreeMap存储Key-Value时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value对处于有序状态,非线程安全,底层采用红黑树的数据结构,无容量限制。TreeSet底层是用TreeMap实现的,TreeMap也有两种排序方式,自然排序和定制排序。

Hashtable

  • HashMap线程不安全,Hashtable是线程安全的。
  • HashMap可以使用null值为key或value,Hashtable不允许使用null作为key和value。
  • Hashtable实现原理和HashMap相同,底层都使用哈希表结构,查询速度快。
  • Hashtable和HashMap一样也不能保证其中Key-Value对的顺序。

Properties

Properties类是Hashtable 的子类,是线程安全的,该对象用于处理属性文件。由于属性文件里的key、value都是字符串类型,所以Properties里的key和value都是字符串类型 。

import java.util.Properties;

public class Test {

    public static void main(String[] args) {

        Properties properties = new Properties();

        properties.setProperty("url","jdbc:msql://localhost:3306");
        properties.setProperty("username","root");
        properties.setProperty("password","root");

        System.out.println(properties.getProperty("url"));
        System.out.println(properties.getProperty("username"));
        System.out.println(properties.getProperty("password"));
    }
}

Map相关题目

1、HashMap与HashTable的区别?

  • HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的
  • 效率问题,Hashtable效率低,HashMap效率高
  • HashMap允许K/V都为null;hashtable后者K/V都不允许为null
  • HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类
  • HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey。因为contains方法容易让人引起误解。
  • 取值不同,HashMap用的是Iterator接口,而Hashtable中还有使用Enumeration接口

2、如何决定选用HashMap还是TreeMap?

  • 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

3、List和 Map区别?

  • 一个是存储单列数据的集合,另一个是存储键和值的双列数据的集合,List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
  1. List有重复值,Map没有重复key,但可以有重复值
  2. List有序,Map不一定有序
  3. List只能存单列值,Map可以存双列值

4、List, Set, Map是否继承自Collection接口?

  • List,Set是,Map不是

5、List、Map、Set三个接口,存取元素时,各有什么特点?

  • List使用get(index)取值,也可以使用Iterator、toArray取值
  • Set只能通过Iterator、toArray取值
  • Map取值使用get(key)取值,也可以使用keySet取键值集合,也可使用values取值集合,entrySet取全部映射。