zoukankan      html  css  js  c++  java
  • 面试之Java集合

     1.Collection 和 Map 的集成体系

     Collection框架:

    Map框架:

     补充:

    Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,用户可以控制的,所以对于用户来说 set 中但是位置不是的元素还是无序的);

    TreeSet 类,可以按照默认顺序,也可以通过实现 Java.util.Comparator<Type>接口来自定义排序

    2.ArrayList 内部用什么实现的?

    • ArrayList是List接口的一个可变大小的数组的实现
    • ArrayList的内部是使用一个Object对象数组来存储元素的
    • 初始化ArrayList的时候,可以指定初始化容量的大小,如果不指定,就会使用默认大小,为10
    • 当添加一个新元素的时候,首先会检查容量是否足够添加这个元素,如果够就直接添加,如果不够就进行扩容,扩容为原数组容量的1.5倍
    • 当在index处放置一个元素的时候,会将数组index处右边的元素全部右移
    • 当在index处删除一个元素的时候,会将数组index处右边的元素全部左移

    3.ArrayList和LinkedList的区别?(链表和数组的优缺点)

    1.ArrayList是一个动态的数组结构,而LinkedList是双向链表结构;存取数据的时候,ArrayList更适合按位随机存取,而LinkedList更适合顺序读存取;

    2.插入/删除数据时,ArrayList的开销就比LinkedList更大,因为对于ArrayList,插入/删除一个数据时需要移动其后所有数据,而LinkedList只需要修改几个指针即可。

    3.ArrayList 的空间浪费主要体现在在 list 列表的结尾预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗相当的空间
    4.当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList 会提供比较好的性能;

    当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用 LinkedList 了。

     

    4.HashMap是怎样的结构?工作原理是什么?

    HashMap的本质仍然是数组,不过数组中存储的不是数据,而是一个链表的头节点。所以准确的说,其实现就是链表数组

    HashMap中保存的是一个键值对,插入对象时必须提供一个键对象;查找对象时必须给定一个键对象(因此必须记住键)。

    键对象时不允许重复的,但是允许null空键的存在。 

    HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,

    让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

    HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象。

    当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对。 
    key对象相同则覆盖,hashcode相同则添加到链表尾。 

    下面解释bucket:

    对于 HashMap 及其子类而言,它们采用 Hash 算法来决定集合中元素的存储位置。当系统开始初始化 HashMap 时,系统会创建一个长度为 capacity 的 Entry 数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。 


    无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。如图 

    详细:http://www.admin10000.com/document/3322.html

    4.栈和队列的区别是什么?

    1、队列先进先出,栈先进后出。

    2、对插入和删除操作的"限定"不同。

    栈是限定只能在表的一端进行插入和删除操作的线性表。     

    队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。  

    3、遍历数据速度不同。

    栈只能从头部取数据,也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性。

    队列则不同,它基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多

    5.HashMap 和 HashTable 有什么区别?

     HashMap是线程不安全的,HashMap是一个接口,是Map的一个子接口,是将键映射到值得对象,不允许键值重复,允许空键和空值;由于非线程安全,HashMap 的效率要较 HashTable 的效率高一些.

    HashTable 是线程安全的一个集合,不允许 null 值作为一个 key 值或者 Value 值;HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,

    而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步;   Collections.synchronizedMap()

    6.数组和链表分别比较适合用于什么场景,为什么?

    数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效
    率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大
    小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现
    越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低
    链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需
    要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任
    意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)

    数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。
    链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。

    7.List a=new ArrayList()和 ArrayList a =new ArrayList()的区别?

    List list = new ArrayList();这句创建了一个ArrayList的对象后把上溯到了List。此时它是一个List对象了,有些ArrayList有但是List没有的属性和方法,

    它就不能再用了。而ArrayList list=new ArrayList();创建一对象则保留了ArrayList的所有属性。 

    拓展内容如下:

    ArrayList独有方法trimToSize(): ArrayList所说没有用的值并不是null,而是ArrayList每次增长会预申请多一点空间,1.5倍+1,

    而不是两倍这样就会出现当size() = 1000的时候,ArrayList已经申请了1200空间,trimToSize 的作用只是去掉预留元素位置,

    就是删除多余的200,改为只申请1000,内存紧张的时候会用到.

    为什么一般都使用 List list = new ArrayList() ,而不用 ArrayList alist = new ArrayList()呢? 
    问题就在于List有多个实现类,如 LinkedList或者Vector等等,现在你用的是ArrayList,也许哪一天你需要换成其它的实现类呢?,

    这时你只要改变这一行就行了:List list = new LinkedList(); 其它使用了list地方的代码根本不需要改动。

    8.ArrayList和Vector的区别

    共同点:都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,

    我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是与HashSet之类的集合的最大不同处,

    HashSet之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。

    不同点: 

    同步性:Vector是线程安全的,它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。 

    数据增长:ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。即Vector增长原来的一倍,ArrayList增加原来的0.5倍。 

    9.什么是线程安全?

     java中的线程安全就是线程同步的意思,就是当一个程序对一个线程安全的方法或者变量进行访问的时候,

    其他的程序不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问,否则将会造成错误发生;

    线程安全就是说,如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。

    如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    线程安全问题都是由全局变量及静态变量引起的,定义在方法内部的局部私有变量是没有线程安全与否一说的。 

    10.HashMap和Hashtable的区别

    1.HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

    2.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;

    而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

    3.由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

    4.HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);

    11.HashSet,TreeSet和LinkedHashSet的区别

    Set接口
    Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
    Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象

    HashSet有以下特点
    1..不能保证元素的排列顺序,顺序有可能发生变化 ;   2.不是同步的;     3.集合元素可以是null,但只能放入一个null

    HashSet:底层数据结构是哈希表,本质就是对哈希值的存储.

    当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
    简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
    注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

    TreeSet有以下特点:

    TreeSet底层用的是TreeMap树结构存储,TreeMap的底层实现是红黑树. 因为TreeSet需要进行排序不允许放入null值.

    TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式

      自然排序:  Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。

      定制排序:   应该使用Comparator接口,实现 int compare(To1,To2)方法

    向 TreeSet中加入的应该是同一个类的对象。TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0

    拓展内容:

    与HashSet不同,TreeSet插入元素时的判断标准其实只需要实现Comparable接口的compareTo()方法就可以了,但是一般情况了,我们推荐同时重写实现equals()这个方法,原因在于我们写这个类的时候,无法确认在后面的情况下,会不会用到equals()方法,比如可能会要把这个类的实例加入到HashSet中?这是可能出现的,我们当然不希望出现compareTo()方法得到的结果为0,但是equals()方法得到的却是flase这样的奇怪情况出现,所以一般实现compareTo()时会同时重写equals()来保证两个方法的结果一致,不产生冲突。

    LinkedHashSet有以下特点:

    LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序

    LinkedHashSet继承了HashSet的全部特性,元素不重复,快速查找,快速插入,

    并且新增了一个重要特性,那就是有序,可以保持元素的插入顺序,所以可以应用在对元素顺序有要求的场景中。

    总结:HashSet,TreeSet,LinkedHashSet之间的区别:HashSet只去重,TreeSet去重并排序,LinkedHashSet去重并保留插入顺序

    12.Comparable和Comparator的区别

    Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,依赖compareTo方法的实现,compareTo方法也被称为自然比较方法.

    compareTo方法的返回值是int,有三种情况:

    1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数

    2、比较者等于被比较者,那么返回0

    3、比较者小于被比较者,那么返回负整数

    Comparator可以认为是是一个外比较器. Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,

    方法返回值和Comparable接口一样是int,有三种情况:

    1、o1大于o2,返回正整数

    2、o1等于o2,返回0

    3、o1小于o3,返回负整数

    1、如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),

    那么可以实现Comparator接口,自定义一个比较器,写比较算法

    2、实现Comparable接口的方式比实现Comparator接口的耦合性 要强一些,如果要修改比较算法,要修改Comparable接口的实现类,

    而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。

    实际上实现Comparator 接口的方式后面会写到就是一种典型的策略模式

     

    参数  Comparable Comparator
    排序逻辑  排序逻辑必须在待排序对象的类中,故称之为自然排序 排序逻辑在另一个实现
    实现 实现Comparable接口 实现Comparator接口
    排序方法 int compareTo(Object o1) int compare(Object o1,Object o2)
    触发排序 Collections.sort(List) Collections.sort(List, Comparator)
    接口所在包  java.lang.Comparable java.util.Comparator

     

     

     

     

     

     

     

     

    13.Java集合类框架的最佳实践有哪些?

     

      1.根据应用需要正确选择要使用的集合类型对性能非常重要,比如:假如知道元素的大小是固定的,那么选用Array类型而不是ArrayList类型更为合适。

      2.有些集合类型允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以指定初始容量来避免重新计算hash值或者扩容等。

      3.为了类型安全、可读性和健壮性等原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。

      4.使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。

      5.编程的时候接口优于实现

      6.底层的集合实际上是空的情况下,返回为长度是0的集合或数组而不是null

     

  • 相关阅读:
    leetcode面试准备:Kth Largest Element in an Array
    leetcode面试准备:Minimum Size Subarray Sum
    leetcode面试准备:Valid Anagram
    leetcode面试准备:Divide Two Integers
    leetcode面试准备:Container With Most Water
    面试:归并排序和分治法
    leetcode面试准备:Lowest Common Ancestor of a Binary Search Tree & Binary Tree
    Leetcode解题思想总结篇:双指针
    leetcode面试准备: CountPrimes
    RF中BDD编写
  • 原文地址:https://www.cnblogs.com/chenshuyong/p/10032175.html
Copyright © 2011-2022 走看看