java面试问题总结

1.Java的8种数据类型有哪些?
int short long float double byte boolean char 
2.Integer缓存数据的范围?
-128-127
3.Obejct类有那些方法?
clone getClass toString finalize equals hashcode wait notifyAll
4.String A = "123"; String B = new String("123");,问我生成了几个String对象?
常量池中 要是没有“123”就生成俩对象 要是有“123”就1个对象生成
5.wait和sleep有什么区别?
wait的话 会释放对象锁 sleep的话不会释放对象锁
6.由于还提及了hashcode,面试官接着问我,hashcode用在哪里?
这个我不假思索地说,hashmap和ConcurrentMap,这里我猜面试官肯定要继续问我这两个东西了。
7、果不其然,面试官说,讲一讲hashmap?

1.HashMap采用Entry数组来存储key-value对
2.每一个键值对组成一个Entry实体
3.Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,解决哈希冲突问题
4.HashMap按照key的hash值来计算Entry在HashMap中存储的位置
5.hash值相同,key不相等,那么用链表解决这种hash冲突。

默认初始化容量16
负载因子0.75 (达到75%时就进行扩容操作)
进行扩容操作,长度必须2的n次方  
阈值,用于判断是否需要扩容(threshold = 容量*负载因子)

put
1.首先进行初始化判断
2.判断key是否为null ,是 放在Entry[]的0号位置 
3.判断该位置上是否有元素 
4.如果有 遍历Entry[]数组位置上的单链表 判断key是否存在 存在用新的value
  如果key不存在 将key-value 生成Entry实体 添加到HashMap中的Entry[]数组中


addEntry
1.先进行容量的判断,如果达到阈值,先进行扩容操作,扩容的容量为table长度的2倍
2.重新计算hash值,和数组存储的位置
3.扩容后的链表与之前的链表顺序相反
4.将新添加的Entry实体存放在当前Entry[]位置链表的头部
注:但是这种操作在高并发的环境下容易导致死锁,所以1.8之后,新插入的元素都放在了链表的尾部。

get方法
1.首先判断size是否等于0
2.计算key的hash值
3.调用indexFor()方法得到key在table中的位置,得该位置的单链表
4.遍历列表找到key内容相等的Entry 返回value

删除方法
1.先计算指定key的hash值,然后计算出table中的存储位置,
2.判断当前位置是否Entry实体存在,如果没有直接返回,若当前位置有Entry实体存在,则开始遍历列表。
3.定义了三个Entry引用,分别为pre, e ,next。 
4.在循环遍历的过程中,首先判断pre 和 e 是否相等,若相等表明,table的当前位置只有一个元素,直接将table[i] = next = null 。若形成了pre -> e -> next 的连接关系,判断e的key是否和指定的key 相等,若相等则让pre -> next ,e 失去引用。


JDK 1.8的 改变
在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,下面来看一下这些改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,在性能上进一步得到提升。

总结

HashMap采用hash算法来决定Map中key的存储,并通过hash算法来增加集合的大小。hash表里可以存储元素的位置称为桶,如果通过key计算hash值发生冲突时,那么将采用链表的形式,来存储元素。HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容。HashMap的线程是不安全的,多线程环境中推荐是ConcurrentHashMap。


8.这里提及了hashMap是非线程安全的,面试问我为啥不是线程安全的,举几个例子?
在扩容的时候hashmap会可能产生环 在成死循环 hashmap在插入新的阶段的时候 多个线程同时插入 会
把出了最后的那个线程的其他线程插入的节点丢失,
对于修改的时候,多线程修改,对只保留最后的一个线程的修改结果
扩容的时候,会只保留最后一个线程的扩容后的那个数组 从扩容修改增加说了一边
9.让我说意思JVM的分为哪几块?
方法区 虚拟机区 本地方法栈 堆 程序计数器。
方法区和堆是线程共享的
虚拟机区、本地方法栈、程序计数器是线程私有的
除了程序计数器不会发生内存溢出,其它都会发生内存溢出。
那些发生堆溢出 那些发生栈溢出?

所以我们可以理解为栈溢出就是方法执行是创建的栈帧超过了栈的深度。那么最有可能的就是方法递归调用产生这种结果。

heap space表示堆空间,堆中主要存储的是对象。如果不断的new对象则会导致堆中的空间溢出
10.由于提及到了内存溢出,面试官问我内存溢出和内存泄漏的区别?
内存泄漏 就是一块申请了一个块内存以后,无法去释放掉这块内存,丢失了这段内存的引用
内存溢出就是申请的内存不够,撑不起我们需要的内存
11.数据库4大特性是什么?
原子性:原子性是指事务包含的操作要么全部完成,要么全部回滚
一致性:比如a+b=100 一个事务改变了a 比如增长了a的值 那么必须同时改变b,保证在事务结束以后a+b=100
仍然成立。
持久性: 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
隔离性:当多个线程都开启事务来操作数据库中的数据时,数据库系统要进行隔离操作,以保证各个线程获取数据的准确性。

12.数据库的隔离级别

1.READ UNCIMMITTED(未提交读)

白话:事务中发生了修改,即使没有提交,其它事务也是可见的

专业点说:就是一个事务可以读取另一个未提交事务的数据。

举例:对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100。

2.Read committed (读取未提交)

白话:对于一个事务从开始直到提交之前,所做的任何修改是其它事务不可见的。

专业点说:就是一个事务要等另一个事务提交后才能读取数据。

举例:就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取到了A是50,刚读取完,A就被修改成100了,这个时候另一个事务再进行读取发现A就变成100了。

3.Repeatable read 可重复读

白话:就是对于一个记录读取多次的记录是相同的

专业点:就是在开始读取数据(事务开启)时,不再允许修改操作

举例:对于一个数A读取的话一直是A,前后两次读取到的A是一致的。

4.Serializable 序列化

白话:就是说在并发情况下,和串行化的读取的结果是一致的,没有什么不同

专业点说:是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

Oracle默认是Read committed(读取未提交)Mysql的默认隔离级别是Repeatable read(可重复读)

13.计算机网络,有哪7层?

物理层,数据链路层,网络层,传输层,会话层,表示层,应用层;

14.TCP在哪层,UDP在哪层,HTTP在哪层?

TPC和UDP在传输层,HTTP在应用层

15.ArrayList和LinkedList的区别?

ArrayList底层是数组,LinkedList底层是链表,ArrayLIst查找数据快,LinkedList插入删除快;

16.linkedList可以用for循环遍历吗?

能不用尽量不要用,linkedList底层是链表,它使用for进行遍历,访问每一个元素都是从头开始访问然后直到找到这个元素,比如说找第三个节点,需要先找到第一个节点然后找到第二个节点;继续找第4个节点,不是从第三个节点开始找的,还是从第一个节点开始,所以非常的慢,不推荐,可以用迭代器进行遍历。

抽象类和接口的区别?
1.接口是公开的,不能有私有方法和变量。抽象类反之。
2.一个类可以实现多个接口,一个类只能继承一个抽象类。
3.实现接口一定要实现接口的所有的方法。实现抽象类可以选择的重写需要用到的方法。

4.接口定义的变量默认是public static finl型 且必须给其初值。所以实现类不能重新定义,也不能改变其值。抽象类反之。

重写和重载的区别?

静态变量和实例变量的区别?

静态变量存储在方法区,属于类所有。实例变量存储在堆当中,其引用存在当前线程栈。

java 创建对象的几种方式?

采用new

通过反射

采用clone

通过序列化机制

前2者都需要显式地调用构造方法。造成耦合性最高的恰好是第一种,
因此你发现无论什么框架,只要涉及到解耦必先减少new的使用。

 


java当中的四种引用?

强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上:

强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,
可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。

弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。
不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。

虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。    


java中==和eqauls()的区别,equals()和`hashcode的区别?
==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,
用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,
此时和==的结果一样。
换句话说:基本类型比较用==,比较的是他们的值。默认下,
对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。
    
final, finalize和finally的不同之处?
final 是一个修饰符,可以修饰变量、方法和类。
如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。
finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,
但是什么时候调用 finalize 没有保证。
finally 是一个关键字,与 try 和 catch 一起用于异常的处理。
finally 块一定会被执行,无论在 try 块中是否有发生异常。

final有哪些用法?

final也是很多面试喜欢问的地方,能回答下以下三点就不错了:
1.被final修饰的类不可以被继承 
2.被final修饰的方法不可以被重写 
3.被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。
4.被final修饰的方法,JVM会尝试将其内联,以提高运行效率 
5.被final修饰的常量,在编译阶段会存入常量池中。

回答出编译器对final域要遵守的两个重排序规则更好:
1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

64位的JVM当中,int的长度是多少?

Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。


int和Integer的区别?

Integer是int的包装类型,在拆箱和装箱中,二者自动转换。int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象。

int 和Integer谁占用的内存更多?

Integer 对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。
但是 int 是一个原始类型的数据,所以占用的空间更少。

StringBuffer和StringBuilder
StringBuffer是线程安全的可变字符串,其内部实现是可变数组。
StringBuilder是jdk 1.5新增的,其功能和StringBuffer类似,
但是非线程安全。因此,在没有多线程问题的前提下,使用StringBuilder会取得更好的性能。

在JAVA中一共有八种基本数据类型?
他们分别是 
byte、short、int、long、float、double、char、boolean 
整型 
其中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)

你知道哪些垃圾回收算法?

垃圾回收从理论上非常容易理解,具体的方法有以下几种: 
1. 标记-清除 
2. 标记-复制 
3. 标记-整理 
4. 分代回收 

Thread类中的start()和run()方法有什么区别?
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,
这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在
原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

产生死锁的条件

1.互斥条件:一个资源每次只能被一个进程使用。 
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 
3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁

wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

wait()与sleep()的区别

关于这两者已经在上面进行详细的说明,这里就做个概括好了:

sleep()来自Thread类,和wait()来自Object类。调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁

sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU

sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用


为什么wait, nofity和nofityAll这些方法不放在Thread类当中

一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

什么是乐观锁和悲观锁

乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

什么是CAS?
CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:
内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,
才会将内存值修改为B并返回true,否则什么都不做并返回false。
当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,
否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。

关于volatile关键字

可以创建Volatile数组吗?

Java 中可以创建 volatile类型数组,不过只是一个指向数组的引用,
而不是整个数组。如果改变引用指向的数组,将会受到volatile 的保护,
但是如果多个线程同时改变数组的元素,volatile标示符就不能起到之前的保护作用了。

volatile能使得一个非原子操作变成原子操作吗?

一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,
如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量
不是原子的,需要分成两步,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到
该值的一半(前 32 位)。但是对一个 volatile 型的 long 或 double 变量的读写是原子。

一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long
 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32
 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。
volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。
简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,
在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。

volatile类型变量提供什么保证?

volatile 主要有两方面的作用:
1.避免指令重排2.可见性保证.
例如,JVM 或者 JIT为了获得更好的
性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重
排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某
些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的
(低32位和高32位),但 volatile 类型的 double 和 long 就是原子的。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值