24-06-15
final、finally、finalize 的区别?
-
final:用于声明属性,方法或类,分别表示属性不可变,方法不可覆盖,其修饰的类不可继承
-
finally:异常处理语句的一部分,表示总是执行
-
finalize:Object类的一个方法,在垃圾回收器执行时会调用被回收对象的
此方法可以覆盖此方法提供垃圾收集时其他资源回收,例如,关闭文件等。该方法更像一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但要注意,我们主动行为上调用该方法不会造成该对象“死亡”,这是一个被动方法(回调函数),不需要我们调用
String 、StringBuilder 、StringBuffer 的区别?
Java平台提供两种类型的字符串:String和StringBuffer/StringBuilder,它们都可以存储和操作字符串,区别如下:
- String是只读字符串,意味String引用的字符串内容是不能改变的
String str = "abc";
str = "bcd";
如上,字符串str明明是可以改变的,其实不然,str仅仅是一个引用对象,它指向一个字符串对象“abc”。第二行代码的含义是让str重新指向一个新的字符串“bcd”对象,而“abc”对象并没有如何的改变,只不过该对象已经成为一个不可及对象
-
StringBuffer/StringBuilder表示的字符串对象可以直接进行修改
-
StringBuilder是Java5中引进的,它和StringBuffer的方法完全相同,区别在于它是单线程下使用的,因为它的所有方法都没有synchronized修饰,理论上比StringBuffer高
short s1 = 1; s1 = s1 + 1; 有错吗?short s1 = 1; s1 += 1 有错吗;
前者不正确,后者正确,对于short s1 = 1,s1 = s1 + 1;由于1的int类型,因此s1 +1也是int类型,需要强制转换类型才能赋值给short类型。而short s1 = 1,s1 + = 1;可以正常编译,因为s1 += 1;相当于s1 = (short)(s1+1),其中有隐含的强制类型转换
Java 中有几种类型的流
按照流的方向:输入流(inputStream)和输出流(outputStream)
按照实现的功能:节点流(可以从或向一个特定的地方(节点)读写数据)和处理流(是对一个已经存在的流的连接和封装,通过所封装流的功能调用实现数据读写。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象结果其他流的多次包装,称为流的链接)
按照处理数据的单位:字节流和字符流。字节流继承InputStream和OutputStream,字符流继承于InputStreamReader和OutPutStreamWriter
字节流和字符流的区别
- 字节流读取时,读到一个字节返回一个字节;字符流使用字节流读到一个或多个字节时(中文是两个字节,UTF-8中使用3个字节)。先去查指定的编码表,将查到的字符返回。字节流可以处理所有的类型的数据,而字符流只能处理字符流,只要是处理纯文本,优先考虑字符流,除此之外,使用字节流。字节流主要操作byte类型数据,以byte数组为准,主要操作类为OutputStream和InputStream
- 字符流处理的单元为两个字节的Unicode字符,分别操作字符,字符数组或字符串,而字节流处理单元为1字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化成2个字节的Unicode字符为单位的字符组成,对多国语言支持性比较好。Java提供reader,write两个专门操作字符流的类
什么是java 序列化,如何实现java 序列化?
序列化是一种用来处理对象流的机制,所谓对象流就是将对象的内容进行流化。可以对流化的对象进行读写操作,也可以将流化后的对象传输于网络之间。序列化是为了解决对对象流进行读写操作时所引发的问题
序列化的实现:将需要序列化的类实现Serializable接口,该接口没有需要实现的方法, implements Serializable只是为了标记该对象是可被序列化的,然后使用一个输出流来构造一个ObjectOutputStream对象,接着使用其的writeObject(Object object)方法就可以将参数为object的对象写出,要恢复则用输入流
List 和Map、Set 的区别
- 结构特点
- List和Set是存储单列数据的集合,Map是存储键和值这样的双列数据的集合;List中存储的数据是有序的,且允许重复;Map中存储的数据没有顺序的,其键不能重复,它的值是可以重复的,Set中存储的数据是无序的,且不能重复,但元素在集合中的位置是由元素的hashcode决定的,位置是固定的(Set集合根据hashcode来进行数据的存储,所以位置是固定的,但不是用户可以控制的,对于用户来说set中元素还是无序的)
- 实现类
-
List接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每个元素存储本身地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现的,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现的,线程安全的,效率低)
-
Map接口有三个实现类(HashMap:基于hash表的Map接口实现,非线程安全,高效,支持null值和null键;HashTable:线程安全,低效,不支持null值和null键;LinkedHashMap:是HashMap的一个子类,保存记录插入顺序;SortMap:能够把它保存的记录根据键排序,默认是键值的升序排序)
-
Set接口有两个实现类(HashSet:底层是HashMap实现的,不允许集合中出现重复的值,使用该方法时需要重写equals()和hashcode()方法;LinkedHashSet:继承Hash,同时又基于LinkedHashMap来进行实现的,底层实现的LinkedHashMap
-
- 区别
- List集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过,list.get(i)方法来获取集合中的元素;Map中的每个元素包含一个键和一个值,成对存在,键对象不能重复,值对象可以出现重复;Set集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如,TreeSet类,可以按照默认顺序,也可以实现java.util.Comparator
接口来定义排序方式
- List集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过,list.get(i)方法来获取集合中的元素;Map中的每个元素包含一个键和一个值,成对存在,键对象不能重复,值对象可以出现重复;Set集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如,TreeSet类,可以按照默认顺序,也可以实现java.util.Comparator
HashMap 和 HashTable 有什么区别?
-
HashMap是线程不安全的,HashMap是一个接口,是Map的一个子接口,是将键映射到值得到对象,不允许键值重复,允许空键和空值;由于非线程安全,HashMap的效率比HashTabled的效率高
-
HashTable是线程安全的一个集合,不允许null值作为一个key或value
-
HashTable是synchronize,多个线程访问时不需要自己为它的方法实现同步,而HashMap在被多个线程访问时需要自己为它的方法实现同步
数组和链表分别比较适合用于什么场景,为什么
1. 数组和链表的简介
-
在计算机中要对给定的数据集进行若干处理,首要任务是把数据集的一部分(当数据量非常大时,可能一部分一部分地读取数据到哦内存中来处理)或全部存储到内存中,然后再对内存中的数据进行各种处理
-
当内存空间有足够大的连续空间时,可以将数据连续的存放到内存中,各种编程语言中的数组一般都是这种方式存储的;但内存中只有一些离散的可用空间时,想连续存储数据就非常困难,解决办法,把离散的空间聚集成连续的大空间,但存在一定的困难,移动数据会把别人重要的数据丢失。另一种,不影响别人的数据存储方式把数据集中的数据分开离散地存储到这些不连续空间中。这时为了把数据集中的所有数据联系起来,需要在前一块数据的存储空间中记录下一块数据的地址,这样只要知道第一块内存空间的地址就能环环相扣把数据集整体联系起来
-
内存的存储形式可以分成连续存储和离散存储。因此,数据的物理存储结构就有连续存储和离散存储两种,对应我们所说的数组和链表
2. 数据和链表的区别
-
数组是将元素在内存中连续存储;优点:因为数据是连续存储的,内存地址连续,所以在查找数据时效率较高;它的缺点:在存储之前,我们要申请一块连续的内存空间,并且编译时候必须确定它的空间大小。在运行时空间大小是无法随着你的需要进行增加和减少的,当数据量比较大时,有可能出现越界的情况,数据较小,又可能浪费空间。在改变数据个数时,增加,插入,删除数据效率比较低。
-
链表是动态申请内存空间,先不要提前申请内存大小,链表只需在使用时申请,根据需求来动态申请或者删除内存空间,对于数据增加和删除以及插入比较灵活,链表中数据在内存中可以在任意的位置,通过引用来关联(通过存在的元素指针来联系)
3. 链表和数组的使用场景
-
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表比较稳定
-
链表应用场景:对线性表的长度和规模难以估计,频繁做插入删除操作,构建动态性比较强的线性表
Java 中ArrayList 和 Linkedlist 区别?
-
ArrayList和Vector使用数组是实现,可以认为ArrayList或Vector封装了对内部数组的操作,比如向数组中添加,删除,插入新元素或者数据的扩展和重定向
-
LinkedList使用循环双向链表结构。基于数组的ArrayList相比,是截然不同的实现技术
-
LinkedList链表由一系列表项连接而成。一个表项总是包含三个部分:元素内容,前驱表和后驱表。无论LinkedList是否为空,链表内部都有一个header表项,它即表示链表的开始,也表示链表的结束。表项header的后驱表便是链表的第一个元素,表项header的前驱表便是链表中的最后一个元素
HashMap的底层实现
1. HashMap的数据结构
-
属于常用三大数据结构:数组结构,链表结构,哈希表结构
-
哈希表结构结合了数组结构和链表结构的优点,查询快,删除快
2. HashMap 键值对的存储原理和方式
-
HashMap.Node<key,value> Node类是HashMap的一个静态内部类,实现了Map.Entry<Key,value>接口,在调用put方法时,会创建一个Node对象
-
通过将key转换成hashcode值,在将value值和key相关联
-
调用put方法时,首先将k,v封装到Node对象中,然后它的底层会调用K的hashcode方法得出hash值。每个数组的位置就是一个哈希值,key的hashcode值可被看成数组的下标,如果两个哈希值一样,就会占用一个位置,通过链表的方式把value连接起来成为链表。如果没有任何元素,就把Node添加到这个位置上。如果对应的下标上有链表,就拿k与链表上的每个节点k进行equals,如果返回false,则添加到链表末尾,若其中一个返回true,则这个链表节点就会覆盖
-
如果key的类型是可变对象就得重写hashcode方法和equals方法,使可变参数不参加hash计算
-
在Java8对hashmap进行优化,如果相同哈希值,链表的长度超过8,就从链表变成红黑树
-
hashMap内部数据结构是数组(Node[] table)和链表结合组成的复合结构,数组被分成一个个桶(bucket)或槽,通过哈希值决定键值对在这个数组的寻址;哈希值相同的键值对,则以链表的形式存在。当链表大小超过阈值时,链表就会改造成树形结构
-
hashMap红黑树原理分析 :
- 好处就是避免在最极端的情况下链表变得很长,在查询时候,效率非常低
- 红黑树查询:其访问性能近似于折半查找,时间复杂度O(logn)
- 链表查询:需要遍历全部的元素才行,时间复杂度O(n)
- 红黑树是一种近似平衡的二叉查找树,其主要的优点是“平衡”,即左右子树高度一致,以此来防止树退化为链表,通过这种方式来保证查找时间复杂度为log(n)
- 红黑树的特征:
1. 每个节点要么是红色,要么是黑色,但根节点永远是黑色 2. 每个红色界定啊的两个子节点一定是黑色 3. 红色节点不能连续(即红色节点的孩子和父亲都不能是红色) 4. 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点 5. 所有叶子节点都是黑色
-
为何随机增删,查询效率高的原因?
- 增删:首先将K值转换成hashcode然后根据下标找到链表添加删除,而数组的位置未发生改变。
- 查询:查询不需要遍历所有的链表,而是先根据K值的hashcode找到下标,再通过equals找到相对应的value值
-
为什么放在hashmap集合中key部分的元素需要重写equals方法
- 因为equals默认是比较的两个对象的内存地址
-
HashMap集合的key,会调用hashCode和equals方法,这两个方法要重写
-
hashCode重写原因:当向hashMap中存入k1时,首先会调用key这个类的hashcode方法,计算它的hash值,随后把k1放到hash值所指引的内存位置,在key这个类中没有定义hashcode方法,就会调用Object类的hashCode方法,而Object的hashcode方法返回的hash值是对象的地址。这时用k2去拿也会计算k2的hash值到对应的位置去,由于K1和k2的内存地址是不同的,所以k2拿不到k1的值
-
equals重写原因:重写hashcode方法仅能够k1和k2计算得到hash值相同,调用get方法的时候会得到正确的位置去找,但当出现散列冲突时,在同一个位置有可能用链表的形式存放冲突元素,这时就需要使用到equals函数,由于没有重写equals,默认使用Object的equals判断是两个对象的内存地址是不是一样,由于k1和k2都是new出来的,内存地址是不同的。
-
3. HashMap的扩容机制
3.1 Java7中hashMap扩容机制
1. 在put()方法中有调用addEntry()方法,这个方法里面是具体的存值,在存值之前判断是否需要扩容
2. 调用扩容的方法resize()
3. 扩容必须满足两个条件:
1. 存放新值的时候当前也有元素个数必须大于等于阈值
2. 存放新值的时候当前存放数据发生hash碰撞
4. transfer()在实际扩容时候把原来数组中的元素放入新的数组中
5. JDK7以及以前使用头插法:使用头插法在多线程扩容的时候会导致循环指向,从而在获取数据get()的时候陷入死循环,线程无法结束。因为当线程1挂起元素A,指向B,线程2,挂起A,但A不指向B,放入B后,B的next指向A,导致A-><-B,遍历链表时形成死循环
6. 造成影响:
1. hashmap在存值时(默认大小为16,负载因子0.75,阈值12),可能到最后存满16,再存17时才会发生扩容,因为前面16个值,每个值在底层数组中分别占据一个位置,未发生hashp碰撞
2.可能存储更多的值(超过16个,最多达到27)都还没有扩容,前11个值全部碰撞,存放到同一个位置。
3.2 Java8中的HashMap扩容机制
1. java8中不需要满足两个条件,只需要满足:当前存放新值的时候已有元素的个数大于等于阈值(已有元素等于阈值,下一个存放必然触发扩容机制)且扩容发生在存放后。
2. Java8HashMap底层结构发生一定的变化,数组对象换成Entry对象(Entry对,结构语言,存储时会存key/value键值对,当前对象的hash值和指向下一个地址的next Node节点),以前所有的Entry向下延伸都是链表,Java8变成链表加红黑树。当链表长度大于8,且总数据量大于64时,链表就会转化成红黑树
3. treeifyBin()方法盘带你是否扩容还是将当前链表转红黑树
4. (1)Java8在新增数据存入成功后进行扩容
(2)扩容会发生两种情况:
1. 当前存入数据大于阈值即发生扩容
2. 存入数据到某条链表时,此时链表数据大于8.且总量大于64发生扩容
(3)Java7是存入数据之前进行判断,Java8是存入数据后再进行扩容
5. 第一次添加元素时。默认初始长度的16,当同一位置,优先以链表的形式存在,当同一位置个数达到8,如果数组的长度小于64,数组进行扩容,如果数组大于等64,该节点链表转换成红黑树。扩容后,如果某个节点是树,但该节点的个数小于等6,就会从树转换成链表
6. 只有当数据量大于64才会有红黑树+链表