1)Java 的基本数据类型有哪些?
其中byte、short、int、long都是表示整数的,只不过他们的取值范围不一样
-
byte的取值范围为-128~127,占用1个字节(-2的7次方到2的7次方-1)
-
short的取值范围为-32768~32767,占用2个字节(-2的15次方到2的15次方-1)
-
int的取值范围为(-2147483648~2147483647),占用4个字节(-2的31次方到2的31次方-1)
-
long的取值范围为(-9223372036854774808~9223372036854774807),占用8个字节(-2的63次方到2的63次方-1)
2)浅拷贝和深拷贝的区别?
浅拷贝只拷贝复制所考虑的对象, 不复制它所引用的对象(就是说不会复制对象里面的对象)
深拷贝把要复制的对象所引用的对象都复制了一遍
beanutils.copyproperties 默认状态下是浅拷贝
3)重载和重写的区别?
重载: 发生在同一个类中, 方法名必须相同,参数类型不同、个数不同、顺序不同会让这两个方法成为不同的方法, 但是方法发回执和访问修饰符的不同不会影响, 发生在编译时
重写: 发生在父子类中, 方法名、参数列表必须相同, 返回值范围小于等于父类(这里指的是继承关系, 子类中方法返回值可以是父类中方法返回自的子类), 抛出的异常范围小于等于父类, 访问修饰符权限大于等于父类; 如果父类方法访问修饰符private则子类不能重写该方法.
4)==与equals的区别?
== : 如果使用==比较的是基本数据类型, 那么比较的是其值是否相等, 如果是引用数据类型, 则比较的是地址值是否相等
equals : equals不能比较基本数据类型
一般来说我们都会重写equals方法, 因为如果没有重写equals方法, 那么比较的是引用类型的地址值
equals方法会做类型判断, 不同类型的对象使用equals方法比较返回值是false 类型相同才会继续比较属性值
integer类型使用==比较有时候true有时候false问题: 在integer中-128到127之间的数字已经提前缓存好了 所以使用==比较是true超过了这个范围后使用==比较结果是false
5)String、StringBuffer、StringBuilder 的区别?
string : 是不可变对象, 在底层代码中是使用final修饰的字符数组, 每次对String类型进行改变其实是产生了一个新的String对象, 然后指针指向新的String对象
stringBuffer : 是线程安全的可变字符序列
StringBuilder : 线程不安全, 速度更快, 适合单线程使用
String是一个类, 但确实不可变的, 所以String创建的算是一个字符串常量, StringBuffer和StringBuilder都是可变的. 所以每次修改String对象的值都是新建一个对象再指向这个对象. 而使用StringBuffer则是对StringBuffer对象本身进行操作. 所以在字符串经常改变的情况下, 使用StringBu要快得多
对于三者使用的总结:
-
操作少量数据 = String
-
单线程操作字符串缓冲区下操作大量数据 = StringBuilder
-
多线程操作字符串缓冲区下操作大量数据 = StringBuffer
6)final关键字的一些用法?
final关键字主要用在三个地方 : 变量 方法 类
final修饰的变量 : 如果是一个基本类型,那么其数值在初始化后就不能更改了; 如果是引用类型的变量, 则在对其初始化之后便不能在让其指向另一个对象, 当前对象的值可以改变
final修饰类 : 表明这个类不能被继承. final类中所有成员方法都会被隐式的指定为final方法
final修饰方法 : 把方法锁定, 防止继承类修改它的含义, (在早期的java实现版本中, 会将final方法转为内嵌调用. 但是如果方法过于庞大, 可能看不到内嵌调用带来的任何性能提升(现在的java版本已经不需要使用final方法进行这些优化了), 类中所有的private方法都隐式指定为final)
7)接口和抽象类的区别是什么?
实现 : 抽象类的子类使用extends来继承且只能继承一个 ; 接口必须使用implements来实现接口可以实现很多接口
构造函数 : 抽象类可以有构造函数, 接口不能有
访问修饰符 : 接口中的方法默认使用public修饰; 抽象类中的方法可以是任意访问修饰符
接口中定义的变量一定是静态的常量写不写都会被static final修饰
8)throw 和 throws 的区别?
throw :
-
throw语句用在方法体内, 表示抛出异常, 由方法体内的语句处理
-
throw是具体向外抛出异常的动作, 所以它抛出的是一个异常实例, 执行throw一定是抛出了某种异常
throws :
-
throws语句是用在方法声明后面, 表示如果抛出异常, 由该方法的调用者来进行异常的处理
-
throws主要是声明这个方法会抛出某种异常的类型, 让它的使用者知道需要捕获的异常类型
-
throws表示出现异常的一种可能性, 并不一定会发生这种异常
9)请写出你最常见的5 个 RuntimeException?
1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
5)java.lang.IllegalArgumentException 方法传递参数错误。
6)java.lang.ClassCastException 数据类型转换异常。
10)Set,List,Map有什么区别?
结构特点 :
list和set是存储单列数据的集合, map是存储键值对数据的集合
list是有索引有序可重复, map是无序 键不可重复,值可以重复, set是无序不可重复的, 不过set中元素的位置是有hashcode决定的, 位置是固定的(set集合根据hashcode来进行数据的存储, 所以位置是固定的, 但是位置不是用户可以控制的, 多以对于用户来说set集合还是无序的)
实现类 :
list接口有三个实现类 :
linkedList : 基于链表实现, 链表内存是散乱的, 每一个元素存储本身内存地址的同时还存储下一个元素的地址. 链表增删快, 查找慢
ArrayList : 基于数组实现, 非线程安全的, 查找快, 增删慢;
vector : 基于数组实现, 线程安全的, 效率低
Map 接口有三个实现类 :
HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支 持 null 值和 null 键;
HashTable:线程安全,低效,不支持 null 值和 null 键;
LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序;
SortMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。
Set 接口有两个实现类 :
HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法; LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)
set集合底层其实使用的就是map集合 只不过他只使用了map集合的key 没有使用value
11)往ArrayList 集合加入一万条数据,应该怎么提高效率?
ArrayList 的构造方法有三种。当数据量比较大,这里又已经明确是一万条了,我们应该在
初始化的时候就给它设置好容量。
ArrayList 默认容量是 10,如果初始化时一开始指定了容量,或者通过集合作为元素,则容量为指定的大小或参数集合的大小。每次扩容为原来的 1.5 倍,如果使用无参构造器初始容量只有 10,后面要扩容,扩容又比较伤性能,因为涉及到数组的复制和移动,将原来的数组复制到新的存储区域中去。
12)Hashmap、hashtable、ConcurrentHashMap区别?
区别对比一(HashMap 和 HashTable 区别):
1、HashMap 是非线程安全的,HashTable 是线程安全的。
2、HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。
3、因为线程安全的问题,HashMap 效率比 HashTable 的要高。
4、Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线程环境,而 Hashtable 适合于多线程环境。一般现在不建议用 HashTable, ① 是 HashTable 是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下, 现在也有同步的ConcurrentHashMap 替代,没有必要因为是多线程而用HashTable。
区别对比二(HashTable 和 ConcurrentHashMap 区别):
HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是JDK1.7 使用了锁分段技术来保证线程安全的。JDK1.8ConcurrentHashMap 取消了Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。
synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
13)ArrayList的扩容机制
-
ArrayList()会使用长度为零的数组作为
-
ArrayList(int initialCapacity) 会使用指定容量的数组
-
public ArrayList(Collection<? extends E> c) 会使用 c 的大小作为数组容量
-
add扩容 第一次创建一个集合长度为0, 添加一个元素后扩容为10, 存满后触发扩容(新创建一个长的集合, 把数据存入新的集合中, 旧的集合作为垃圾回收)扩容长度为当前长度右移一位(大概是除于二向下取整)
-
addAll扩容 在下一次扩容的容量和实际的参数个数之间选一个较大的值作为下次扩容的容量
14)fail-fast 与 fail-safe
ArrayList 是 fail-fast 的典型代表,遍历的同时不能修改,尽快失败 CopyOnWriteArrayList 是 fail-safe 的典型代表,遍历的同时可以修改,原理是读写分离 所以ArrayList是非线程安全的,CopyOnWriteArrayList线程安全
15)ArrayList 与 LinkedList 的比较(数据结构、效率方法)
-
ArrayList
-
基于数组,需要连续内存
-
随机访问快(指根据下标访问)
-
尾部插入、删除性能可以,其它部分插入、删除都会移动数据,因此性能会低
-
可以利用 cpu 缓存,局部性原理 (了解)
-
-
LinkedList
-
-
基于双向链表,无需连续内存
-
随机访问慢(要沿着链表遍历)
-
头尾插入删除性能高
-
占用内存多
-
16)简单谈谈HashMap底层的数据结构
-
1.7 数组 + 链表
-
1.8 数组 + (链表 | 红黑树)
jdk1.7
\1) 初始化一个数组(默认长度16)
\2) 当put 值时,计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
\3) 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
\4) 如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则插入链表
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在
\1) 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,
\2) 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,
\3) 如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链
表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
\4) 如果存储的数据 key为null,存在下标0的位置
总结:
1.树化 :单个槽中链表长度 > 8 a.数组容量如果小于64,优先扩容 b.如果数组容量大于等64,转红黑树
2.树退化: a.扩容时,原来红黑树上一部分数据可能会转移到别的槽中,当红黑树中的元素小于等于6时,退化成链表
b.调用remove方法删除数据时,删除之前校验红黑树根节点 左儿子 左孙子 右儿子是否存在,如果有一个不存在,退化成链表
put总结:
1.key 计算 两次hash值,用当前数组长度取模,得到桶下标
2.将数据插入到桶中(链表/红黑树) 1.7头插法 1.8尾插法
2.1 总容量 >= 64 并且 链表长度 达到8个,1.8转成红黑树
3.检查是否需要扩容,如果满足扩容条件(元素个数 > 容量 * 加载因子0.75),触发扩容 容量 * 2,将原数据
搬运到新的数组中
17)jdk8HashMap底层设计为什么加入红黑树
红黑树用来避免dos攻击,防止链表超长时性能下降, 树化应当是偶然情况, 正常情况下概率非常低
18)HashMap什么时候会触发树化
树化 :单个槽中链表长度 > 8 a.数组容量如果小于64,优先扩容 b.如果数组容量大于等64,转红黑树
19)HashMap什么时候会触发树退化
a.扩容时,原来红黑树上一部分数据可能会转移到别的槽中,当红黑树中的元素小于等于6时,退化成链表
b.调用remove方法删除数据时,删除之前校验红黑树根节点 左儿子 左孙子 右儿子是否存在,如果有一个不存在,退化成链表
20)HashMap为什么要进行二次哈希
为了防止一次哈希在数组长度比较小的情况下, 造成数据分布不均匀的情况
21)HashMap数组的长度为什么是2的n次幂
-
为了保证%运算和&运算的结果一致, 提高计算效率 减少hash碰撞
-
扩容时hash(一次hash) & oilCap(原始长度) == 0 的元素留在原来的位置, 否则新位置 = 旧索引位置 + 旧数组长度, 可以提高扩容时重新计算存储索引
-
容的容量