美文网首页
Java基础之数据结构

Java基础之数据结构

作者: KD小帅 | 来源:发表于2018-11-16 14:10 被阅读0次

    1.List 

    ArrayList 、Vector 、LinkedList的区别?

    答:ArrayList 底层是数组

    特点:查询快,增删慢,线程不安全

    LinkList 底层是链表

    特点:查询慢,增删快,线程不安全

    Vector 底层是数组

    特点:查询快,增删慢,线程安全

    Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?

    答:Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。

    Array大小是固定的,ArrayList的大小是动态变化的。

    ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。

    对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

    适用场景: 如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里,但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择ArrayList。而且还有一个地方是必须知道的,就是如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用ArrayList就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择LinkedList

    2.Set

    HashSet是如何保证数据不可重复的?HashSet和TreeSet有什么区别?TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

    答:HashSet原理

    我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数

    当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象

    如果没有哈希值相同的对象就直接存入集合

    如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

    TreeSet原理

    TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列

    TreeSet排序方式有两种自然顺序和比较器顺序

    总结一下他们的特点:

    1.TreeSet 是二差树实现的,TreeSet中的数据是自动排好序的,不允许放入null值

    2.HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null 

    3.HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 

    4.TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。

    Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象必须实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

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

    答:Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。

    equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个

    分离的对象的内容和类型相配的话,返回真值。

    equals()和==的区别

    ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存

    储的数值是否相同, 要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作

    符。

    如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存, 对象本身占用一块

    内存(堆内存),变量也占用一块内存,例如 Objet obj = new Object();变量 obj 是一个内存,

    new Object()是另一个内存,此时,变量 obj 所对应的内存中存储的数值就是对象占用的那

    块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即

    要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。

    equals 方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相

    同,它比较的两个对象是独立的。例如,对于下面的代码:

    String a=newString("foo");

    String b=newString("foo");

    两条 new 语句创建了两个对象,然后用 a/b 这两个变量分别指向了其中一个对象,这是两

    个不同的对象,它们的首地址是不同的,即 a 和 b 中存储的数值是不相同的,所以,表达

    式 a==b 将返回 false,而这两个对象中的内容是相同的,所以,表达式 a.equals(b)将返回

    true。

    在实际开发中,我们经常要比较传递进行来的字符串内容是否等,例如, String input

    = …;input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的,随便从网上

    找几个项目实战的教学视频看看,里面就有大量这样的错误。记住,字符串的比较基本上都

    是使用 equals 方法。

    如果一个类没有自己定义 equals 方法,那么它将继承 Object 类的 equals 方法, Object 类

    的 equals 方法的实现代码如下:

    boolean equals(Object o){returnthis==o;

    }

    这说明,如果一个类没有自己定义 equals 方法,它默认的 equals 方法(从 Object 类继承

    的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用

    equals 和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回 false。如果你

    编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖 equals

    方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的

    由上述可见:

    总结如下:

    ==:

      基本类型:比较的是值是否相同

      引用类型:比较的是地址值是否相同

    equals():

      引用类型:默认情况下,比较的是地址值,可进行重写,比较的是对象的成员变量值是否相同

    3.Map

    HashMap的实现机制,底层数据结构是什么?怎么样HashMap线程安全,HashMap和HashTable有什么区别?

    答:HashMap和Hashtable的底层实现都是数组+链表结构实现的,这点上完全一致。添加、删除、获取元素时都是先计算hash,根据hash和table.length计算index也就是table数组的下标,然后进行相应操作。

    1.两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全

    Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合(Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理)

    2.HashMap可以使用null作为key,而Hashtable则不允许null作为key

    虽说HashMap支持null值作为key,不过建议还是尽量避免这样使用,因为一旦不小心使用了,若因此引发一些问题,排查起来很是费事

    HashMap以null作为key时,总是存储在table数组的第一个节点上

    3.HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类

    4.HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75

    HashMap扩容时是当前容量翻倍即:capacity*2,Hashtable扩容时是容量翻倍+1即:capacity*2+1

    5.两者计算hash的方法不同

    Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模

    HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸

    6.HashMap和Hashtable的底层实现都是数组+链表结构实现

    为什么HashMap中String、Integer这样的包装类适合作为K?如果我想要让自己的Object作为K应该怎么办呢?

    答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

    都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况

    内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;

    如果我想要让自己的Object作为K,就重写hashCode()和equals()方法

    重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;

    重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;

    HashMap是怎么解决哈希冲突的?

    答:Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

    所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。

    什么是哈希冲突?当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。

    HashMap的数据结构

    在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做链地址法的方式可以解决哈希冲突:

    这样我们就可以将拥有相同哈希值的对象组织成一个链表放在hash值所对应的bucket下,但相比于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表,所以我们还需要对hashCode作一定的优化

    hash()函数

    上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下:

    static final inthash(Object key){

    int h;

    return(key ==null) ?0: (h = key.hashCode()) ^ (h >>>16);// 与自己右移16位进行异或运算(高低位异或)

    }

    这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动);

    JDK1.8新增红黑树

    通过上面的链地址法(使用散列表)和扰动函数我们成功让我们的数据分布更平均,哈希碰撞减少,但是当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn);

    总结

    简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:

    1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;

    2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;

    3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

    TreeMap 是采用什么树实现的?TreeMap、HashMap、LindedHashMap的区别。

    答:TreeMap的实现是红黑树算法的实现,LinkedHashMap可以保证HashMap集合有序。存入的顺序和取出的顺序一致。TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。HashMap不保证顺序,即为无序的,具有很快的访问速度。HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步。

    4.数组

    在java中,声明一个数组过程中,是如何分配内存的?

    答:1.当声明数组类型变量时,为其分配了(32位)引用空间,由于未赋值,因此并不指向任何对象;

    2.当创建了一个数组对象(也就是new出来的)并将其地址赋值给了变量,其中创建出来的那几个数组元素相当于引用类型变量,因此各自占用(32位的)引用空间并按其默认初始化规则被赋值为null

    3.程序继续运行,当创建新的对象并(将其地址)赋值给各数组元素,此时堆内存就会有值了

    怎么判断数组是 null 还是为空

    答:1.数组为null和数组为空的区别

    数组为null:是创建了数组的引用,但在堆中并没有数组中的元素

    例:

    int[] array1 = null;

    array1是数组类型的空引用,栈中名为array1的内存空间没有存放任何地址。

    数组为空:数组是空其实就是数组的长度为0,数组是真正的对象,只是对象中没有元素,也就是说里面没有内容

    例:

    int[] array = {};

    此时创建了数组,数组的长度为0,是一个空数组,但是array不是null,它也是一个对象,只不过它的元素个数为0。

    2.判断数组是否为空?

    判断数组为空,使用array.length==0可以

    但array==null不可以,这种会报错,Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1

    3.判断数组是否为null?

    直接使用变量名==null

    例:

    String[ ]  arr = null;

    if(arr == null){......}

    相关文章

      网友评论

          本文标题:Java基础之数据结构

          本文链接:https://www.haomeiwen.com/subject/uoipfqtx.html