大数据开发面试准备——java集合

一、集合整体框架
collection:
collections
map:
在这里插入图片描述

一、Collecton体系——List容器

1.List的实现类

Collection体系是指以单值的形式存储对象数据的容器体系,List 则是有序的 Collection。List容器允许重复元素的存储,可以存放null值。(最常用的为ArrayList以及LinkedList;另外在JDK1.2集合框架出现之前,该类型使用的容器为Vector)。因此Java List 容器一共三个实现类,分别是 ArrayList、Vector 和 LinkedList。

  • ArrayList线程不同步、底层的数据结构是数组结构、元素存储顺序有序、访问元素速度相对较快,存取或删除元素等操作速度较慢
    ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。排列有序,可重复,容量不够的时候,当前容量*1.5+1。

  • Vector线程同步、 底层的数据结构是数组结构、元素存储顺序有序、访问,存取,删除元素的操作都很慢
    Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。默认扩展一倍容量。

  • LinkList线程不同步、底层的数据结构是双向链表结构、元素存储顺序有序、访问元素速度相对较慢,存取或删除元素等操作速度快
    LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。底层使用双向循环链表数据结构。线程不安全。

2.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?

Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

3.ArrayList和LinkedList有什么区别?

ArrayList和LinkedList都实现了List接口
他们有以下的不同点:

  • ArrayList的实现是基于数组,LinkedList的实现是基于双向链表
    -对于随机访问,ArrayList优于LinkedList。ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
  • 对于插入、添加和删除操作,LinkedList优于ArrayList 。因为当元素被添加到集合任意位置的时候,LinkedList不需要像数组那样重新计算大小或者是更新索引。
  • LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。 ArrayList使用一个内置的数组来存储元素,这个数组的起始容量是10,当数组需要增长时,新的容量按如下公式获得:新容量 = 旧容量*1.5 + 1,也就是说每一次容量大概会增长50%

4.ArrayList和Vector有何异同点?

ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。

以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。

二、Collecton体系——Set容器

Set容器不允许重复元素的存储,该容器中存储的对象都是唯一的。即Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等性本质是对象 hashCode 值。

  • Set集合中的对象无排列顺序,且没有重复的对象。可以把Set集合理解为一个口袋,往里面丢的对象是无顺序的。
  • 对Set集合中成员的访问和操作是通过对集合中对象的引用进行的,所以Set集合不能有重复对象(包括Set的实现类)。
  • Set判断集合中两个对象相同不是使用"=="运算符,而是根据equals方法。每次加入一个新对象时,如果这个新对象和当前Set中已有对象进行equals方法比较都返回false时,则允许添加对象,否则不允许。

Set集合的主要实现类:

  • **HashSet:**按照哈希算法来存储集合中的对象,速度较快。
  • **LinkedHashSet:**不仅实现了哈希算法,还实现了链表的数据结构,提供了插入和删除的功能。当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。
  • **TreeSet:**实现了SortedSet接口(此接口主要用于排序操作,即实现此接口的子类都属于排序的子类)。
  • **EnumSet:**专门为枚举类设计的有序集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式、或隐式地指定。

1.HashSet(线程不同步、底层的数据结构为哈希表结构、无序)

哈希表边存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。
当两个对象equals()方法比较返回true时(即两个对象的equals相同),但hashCode()方法返回不同的hashCode值时,对象可以添加成功(集合中有两个A对象)。如果两个对象的hashCode相同,但是它们的equlas返回值不同,HashSet会在这个位置用链式结构来保存多个对象(B@1, B@1)。而HashSet访问集合元素时也是根据元素的HashCode值来快速定位的。
HashSet集合通过hashCode()采用hash算法来决定元素的存储位置,如上输出的(B,B)和(A,A),但是这并不符合Set集合没有重复的对象的规则,所以如果需要把某个类的对象保存到HashSet集合时,在重写这个类的equlas()方法和hashCode()方法时,应该尽量保证两个对象通过equals()方法比较返回true时,它们的hashCode()方法返回值也相等。

哈希值相同 equals 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。HashSet 通过 hashCode 值来确定元素在内存中的位置。一个 hashCode 位置上可以存放多个元素。
hashSet场景:

public class MyCollectionsDemo {
    public static void main(String[] args)
    {
        HashSet books = new HashSet();
        //分别向books集合中添加两个A对象,两个B对象,两个C对象
        books.add(new A());
        books.add(new A());

        books.add(new B());
        books.add(new B());

        books.add(new C());
        books.add(new C());
        System.out.println(books);
    }
}

//类A的equals方法总是返回true,但没有重写其hashCode()方法。不能保证当前对象是HashSet中的唯一对象
class A {
    @Override
    public boolean equals(Object obj) {
        return true;
    }
}

//类B的hashCode()方法总是返回1,但没有重写其equals()方法。不能保证当前对象是HashSet中的唯一对象
class B {
    @Override
    public int hashCode() {
        return 1;
    }
}

//类C的hashCode()方法总是返回2,且有重写其equals()方法
class C
{
    public int hashCode()
    {
        return 2;
    }
    public boolean equals(Object obj)
    {
        return true;
    }
}

结果:

[B@1, B@1, A@1b28cdfa, C@2, A@65ab7765]

2.TreeSet(线程不同步、底层的数据结构为二叉树结构、会按自然排序方式或指定排序方式对元素进行排序)

  1. TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增

加一个对象都会进行排序,将对象插入的二叉树指定的位置。

  1. Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。

  2. 在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序

  3. 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

LinkHashSet(HashSet+LinkedHashMap)

对于 LinkedHashSet 而言,它继承与 HashSet、又基于 LinkedHashMap 来实现的。

LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。

public class MyCollectionsDemo {
    public static void main(String[] args)
    {
        TreeSet treeSet = new TreeSet();
        //添加四个Integer对象
        treeSet.add(9);
        treeSet.add(6);
        treeSet.add(-4);
        treeSet.add(3);

        //输出:[-4, 3, 6, 9](集合元素自动排序)
        System.out.println(treeSet);

        //输出:-4
        System.out.println(treeSet.first()); //输出集合里的第一个元素

        //输出:9
        System.out.println(treeSet.last());  //输出集合里的最后一个元素

        //输出:[-4, 3]
        System.out.println(treeSet.headSet(6)); //返回小于6的子集,不包含6

        //输出:[3, 6, 9]
        System.out.println(treeSet.tailSet(3)); //返回大于3的子集,包含3

        //输出:[-4, 3]
        System.out.println(treeSet.subSet(-4 , 6)); //返回大于等于-4,小于6的子集
    }
}

可以看出TreeSet会自动排序好存入的数据。TreeSet采用红黑树的数据结构来存储集合元素,支持两种排序方式: 自然排序、定制排序。

自然排序:调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排序。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序会抛出异常。
定制排序:TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果我们需要实现定制排序,则可以通过Comparator接口里的int compare(T o1, T o2)方法,该方法用于比较大小。
如下为定制排序实例:

public class MyCollectionsDemo {
    public static void main(String[] args)
    {
        TreeSet treeSet = new TreeSet(new Comparator()
        {
            //根据M对象的age属性来决定大小
            public int compare(Object o1, Object o2)
            {
                M m1 = (M)o1;
                M m2 = (M)o2;
                return m1.age > m2.age ? -1
                        : m1.age < m2.age ? 1 : 0;
            }
        });
        treeSet.add(new M(5));
        treeSet.add(new M(-3));
        treeSet.add(new M(9));
        System.out.println(treeSet);
    }
}
class M
{
    int age;
    public M(int age)
    {
        this.age = age;
    }
    public String toString()
    {
        return "M[age:" + age + "]";
    }
}

TreeSet总结
当把一个对象加入TreeSet集合时,TreeSet会调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置(如果两个对象通过compareTo(Object obj)方法比较相等,则添加失败)。
自然排序、定制排序、Comparator决定的是谁大的问题,即按什么顺序(升序、降序)进行排序。

3.LinkedHashSet

LinkedHashSet场景:

public class MyCollectionsDemo {
    public static void main(String[] args)
    {
        HashSet hashSet = new HashSet();
        hashSet.add("hello");
        hashSet.add("world");
        hashSet.add("hashSet");
        //输出:[world, hashSet, hello]
        System.out.println(hashSet);

        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add("Hello");
        linkedHashSet.add("World");
        linkedHashSet.add("linkedHashSet");
        //输出:[Hello, World, linkedHashSet]
        System.out.println(linkedHashSet);

        //删除 Hello
        linkedHashSet.remove("Hello");
        //重新添加 Hello
        linkedHashSet.add("Hello");
        //输出:[World, linkedHashSet, Hello]
        System.out.println(linkedHashSet);
        
        //再次添加 Hello
        linkedHashSet.add("Hello");
        //输出:[World, linkedHashSet, Hello]
        System.out.println(linkedHashSet);
    }
}

可以看出,linkedHashSet最大的特点就是有排序(以元素添加的顺序)。同时,linkedHashSet作为HashSet的子类且实现了Set接口,所以不允许集合元素重复。

4.EnumSet

EnumSet场景

enum SeasonEnum
{
    SPRING,SUMMER,FALL,WINTER
}
public class MyCollectionsDemo {
    public static void main(String[] args)
    {
        //allOf:集合中的元素就是SeasonEnum枚举类的全部枚举值
        EnumSet es1 = EnumSet.allOf(SeasonEnum.class);
        //输出:[SPRING,SUMMER,FALL,WINTER]
        System.out.println(es1);

        //noneOf:指定SeasonEnum类的枚举值。
        EnumSet es2 = EnumSet.noneOf(SeasonEnum.class);
        //输出:[]
        System.out.println(es2);
        //手动添加两个元素
        es2.add(SeasonEnum.WINTER);
        es2.add(SeasonEnum.SPRING);
        //输出:[SPRING,WINTER](EnumSet会自动排序)
        System.out.println(es2);

        //of:指定枚举值
        EnumSet es3 = EnumSet.of(SeasonEnum.SUMMER , SeasonEnum.WINTER);
        //输出:[SUMMER,WINTER]
        System.out.println(es3);
    }
}

5.Set总结

(1)HashSet的性能比TreeSet好(包括添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。
当需要一个始终保持排序的Set时,才使用TreeSet,否则使用HashSet。
(2)对于LinkedHashSet,普通的插入、删除操作比HashSet要略慢一点,因为维护链表会带来的一定的开销。好处是,遍历比HashSet会更快
(3)EnumSet是所有Set实现类中性能最好的。
(4)HashSet、TreeSet、EnumSet都是"线程不安全"的,可以通过Collections工具类的synchronizedSortedSet方法来"包装"该Set集合。
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…));

6.list与set 区别

(1)List

  • 可以允许重复的对象。
  • 可以插入多个null元素。
  • 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
  • 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

(2)Set
1、不允许重复对象  
2、无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
3、只允许一个 null 元素
4、set接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。

三、Map体系

Map体系是指,以“键值对”的形式存储数据的容器体系。即是将键映射到值的对象;一个映射不能包含重复的键;每个键最多只能映射到一个值。
Map体系与Collection体系最大的不同之处就在于:不再采用单值,而是采用键值对的方式存储元素。

1.HashMap(线程不同步、底层数据结构是哈希表结构、无序、允许NUll作为键或值)

HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。

java7的hashmap实现
在这里插入图片描述
HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个蓝色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
loadFactor:负载因子,默认为 0.75。
threshold:扩容的阈值,等于 capacity * loadFactor
java8的实现
在这里插入图片描述
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树组成。

根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

2.Hashtable (线程同步 、底层数据结构是哈希表结构、无序、不允许NUll作为键或值)

Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。

3.TreeMap :线程不同步、底层数据结构是二叉树结构、可以按照自然或指定排序方式对映射中的键进行排序

TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。
如果使用排序的映射,建议使用 TreeMap。
在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。

class R implements Comparable
{
    int count;
    public R(int count)
    {
        this.count = count;
    }
    public String toString()
    {
        return "R[count:" + count + "]";
    }
    //根据count来判断两个对象是否相等。
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj!=null
                && obj.getClass()==R.class)
        {
            R r = (R)obj;
            return r.count == this.count;
        }
        return false;
    }
    //根据count属性值来判断两个对象的大小。
    public int compareTo(Object obj)
    {
        R r = (R)obj;
        return count > r.count ? 1 :
                count < r.count ? -1 : 0;
    }
}
public class MyCollectionsDemo {
    public static void main(String[] args)
    {
        TreeMap treeMap = new TreeMap();
        treeMap.put(new R(10010) , "treemap10010");
        treeMap.put(new R(-4396) , "treemap-4396");
        treeMap.put(new R(10086) , "treemap10086");

        //treeMap自动排序
        //输出:{R[count:-4396]=treemap-4396, R[count:10010]=treemap10010, R[count:10086]=treemap10086}
        System.out.println(treeMap);

        //返回该TreeMap的第一个Entry对象
        //输出:R[count:-4396]=treemap-4396
        System.out.println(treeMap.firstEntry());

        //返回该TreeMap的最后一个key值
        //输出:R[count:10086]
        System.out.println(treeMap.lastKey());

        //返回该TreeMap的比new R(2)大的最小key值。
        //输出:R[count:10010]
        System.out.println(treeMap.higherKey(new R(2)));


        //返回该TreeMap的比new R(2)小的最大的key-value对。
        输出:R[count:-4396]=treemap-4396
        System.out.println(treeMap.lowerEntry(new R(2)));

        //返回该TreeMap的子TreeMap
        输出:{R[count:-4396]=treemap-4396, R[count:10010]=treemap10010}
        System.out.println(treeMap.subMap(new R(-5000) , new R(10086)));
           }
}

Map 集合总结
Set和Map的关系十分密切,java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
HashMap和Hashtable的效率大致相同,因为它们的实现机制几乎完全一样。但HashMap通常比Hashtable要快一点,因为Hashtable需要额外的线程同步控制
TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对
使用TreeMap的一个好处就是: TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作

参考:
1.https://www.pianshen.com/article/9773590260/
2.https://blog.csdn.net/qq_44614710/article/details/113698325
3.https://www.jianshu.com/p/5f449f13c682
4.基础:https://blog.csdn.net/qq_44614710/article/details/113694683
5.https://www.cnblogs.com/fooss/p/10182201.html
6.https://blog.csdn.net/qq_44614710/article/details/113698325
7.https://www.cnblogs.com/jichi/p/12834238.html
8.https://copyfuture.com/blogs-details/20201013112324152vbwxe1dft7bdyew
9.https://www.cnblogs.com/xuxinstyle/p/9537249.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值