本文目录
- 把Java编程语言精通到底有多难
- 你经历过哪些有意思的面试题目
- 阿里Java研发岗如何面试:数据结构+MySQL+缓存雪崩+高并发等
- java方向的学生面试哪些东西可以加分
- 三年Java开发的工程师能接面试电话接到手软吗
- jdk8中的ConcurrentHashMap究竟为什么高效
- 面试一个5年经验的java,不知数据结构,却大谈分布式,这样的候选人能要吗
- 南京万和Java培训分享Java高频面试题—如何对HashMap按键值排序
把Java编程语言精通到底有多难
作为一名从业多年的程序员,同时也出版过Java编程书籍,所以我来回答一下这个问题。
Java语言随着互联网的发展,其自身的生态体系不断得到完善,应用边界也不断得到拓展,目前在Web开发、大数据开发、移动终端开发等领域均有广泛的应用,可以说不同的应用方向也需要具备不同的知识结构,所以说精通Java还是具有一定难度的。
Java语言自身的构成分为两个大的部分,一大部分为Java虚拟机,另一部分为Java语言自身的语法。按照Java语法要求编写的程序需要通过Java虚拟机完成加载、校验、编译和运行,而Java虚拟机的作用就相当于Java的运行环境(容器),它自身需要完成大量的操作,包括代码安全性、垃圾处理、事件处理、资源管理等内容。所以精通Java开发一方面需要清晰Java语法,另一方面需要了解Java虚拟机的运行机制。
对于初学者来说,学习Java编程都是从学习Java语法开始的,然后学习Java的Web开发、数据库开发、分布式开发等内容,这个过程通常是大部分学习者的学习路线,难点在于Java面向对象概念的理解,也就是理解各种“抽象”。这部分虽然具备一定的难度,但是大部分学习者是能够学得会的,区别往往在学习时间上。
对于从事平台开发的研发级程序员来说,还需要系统的学习Java虚拟机的内部机制,通过从深层次了解Java虚拟机的构成从而辅助平台类产品的研发,重点在于性能的提高。通常情况下需要了解Java虚拟机的体系结构、核心算法等内容,这部分内容的难度还是比较大的。当然,要想系统了解Java虚拟机的整体结构,通常还需要阅读大量的源代码。
我从事互联网行业多年,目前也在带计算机专业的研究生,主要的研究方向集中在大数据和人工智能领域,我会陆续写一些关于互联网技术方面的文章,感兴趣的朋友可以关注我,相信一定会有所收获。
如果有互联网方面的问题,或者考研方面的问题,都可以咨询我,谢谢!
你经历过哪些有意思的面试题目
一、String 原理,String 、StringBuffer、StringBuilder区别。
String是final类,属于不可变字符串,采用char数组。
StringBuffer是线程安全的,内部采用synchronized。
StringBuilder是非线程安全的。
二、String与StringBuilder拼接字符串哪个性能好,为什么?
StringBuilder性能比较好,String在拼接的时候会new出多个对象,消耗资源。尤其是在for循环下进行拼接性能差距很明显。
三、接口与抽象类的区别
1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
3. 一个类可以实现多个接口,但最多只能实现一个抽象类
4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
四、重载与重写的区别
重载(Overload)是让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者类型的同名函数(返回值类型可随意,不能以返回类型作为重载函数的区分标准)同时存在于同一个类中,是一个类中多态性的一种表现(调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性)。
重写(Override)是父类与子类之间的多态性,实质是对父类的函数进行重新定义,如果在子类中定义某方法与其父类有相同的名称和参数则该方法被重写,不过子类函数的访问修饰权限不能小于父类的;若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用 super 关键字。
五、数组与链表的区别
1.数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。最大的特点就是支持随机访问,但插入、删除操作也因此变得比较低效,平均情况时间复杂度为O(n)。
2.链表它并不需要一块连续的内存空间,它通过“指针”(前后节点指向)将一组零散的内存,空间可扩容,比较常用的是单链表,双链表和循环链表。和数组相比,链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高。
六、介绍一下ArrayList
1、ArrayList底层数据结构是一个数组,数组元素类型为Object,对ArrayList的所有操作都是基于数组。
2、ArrayList不是线程安全的。对ArrayList进行添加元素的操作的时候是分两个步骤进行的,即第一步先在object的位置上存放需要添加的元素;第二步将size的值增加1。由于这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。
如果非要在多线程的环境下使用ArrayList,就需要保证它的线程安全性,通常有两种解决办法:第一,使用synchronized关键字;第二,可以用Collections类中的静态方法synchronizedList();
3、ArrayList默认大小为10
4、ArrayList扩容机制:
第一,在add()方法中调用ensureCapacityInternal(size + 1)方法来确定集合确保添加元素成功的最小集合容量minCapacity的值。参数为size+1,代表的含义是如果集合添加元素成功后,集合中的实际元素个数。换句话说,集合为了确保添加元素成功,那么集合的最小容量minCapacity应该是size+1。在ensureCapacityInternal方法中,首先判断elementData是否为默认的空数组,如果是,minCapacity为minCapacity与集合默认容量大小中的较大值。
第二,调用ensureExplicitCapacity(minCapacity)方法来确定集合为了确保添加元素成功是否需要对现有的元素数组进行扩容。首先将结构性修改计数器加一;然后判断minCapacity与当前元素数组的长度的大小,如果minCapacity比当前元素数组的长度的大小大的时候需要扩容,进入第三阶段。
第三,如果需要对现有的元素数组进行扩容,则调用grow(minCapacity)方法,参数minCapacity表示集合为了确保添加元素成功的最小容量。在扩容的时候,首先将原元素数组的长度增大1.5倍(oldCapacity + (oldCapacity 》》 1)),然后对扩容后的容量与minCapacity进行比较:① 新容量小于minCapacity,则将新容量设为minCapacity;②新容量大于minCapacity,则指定新容量。最后将旧数组拷贝到扩容后的新数组中。
七、ArrayList在遍历的时候可以删除数据吗?
for循环遍历不可以,会报ConcurrentModificationException异常,迭代器Iterator下可以。
内部有一个modCount 进行修改次数检查。
如果没checkForComodification去检查expectedModCount与modCount相等,这个程序肯定会报ArrayIndexOutOfBoundsException
八、LinkedList底层数据结构是什么?说明ArrayList,LinkedList二者区别?
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构每个元素都包含了 上一个和下一个元素的引用,所以add/remove 只会影响到上一个和下一个元素,。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
九、简单介绍一下HashMap?
1、负载因子0.75 , 提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小。
2、扰动函数就是解决碰撞问题,减少碰撞。
3、hasMap的容量尽量是2的倍数,这样使散列均匀,不易出现hash冲突
HashMap默认初始化大小16,数据结构是一个数组+链表+红黑树(平衡二叉树红黑节点)。
Put的时候首先拿到hash值看是否存在值如果没有值直接放入,存在则进行比较是否相等,不等生成链表,当链表大于8的时候生成红黑树。
HashMap是线程不安全的。
十、介绍一下ConcurrentHashMap
concurrentHashMap是一个线程安全的高可用HashMap。
1.8抛弃了之前的segement分段上锁,采用了CAS+synchronized来保证并发安全,并且吧put的流程更加细化,而且resize的时候不会阻塞任何一个线程。
重要参数:
MIN_TRANSFER_STRIDE: 意思是resize时候每个线程每次最小的搬运量(搬运多少个entry),范围会继续细分以便可以允许多个resize线程。
RESIZE_STAMP_BITS :位的个数来用来生成戳,用在sizeCtrl里面;
MAX_RESIZERS:最大参与resize的线程数量;
RESIZE_STAMP_SHIFT:用来记录在sizeCtrl中size戳的位偏移数。
Put主逻辑:
1、首先判断key\value都不能为null
2、进行自旋操作,每次把当前的table赋给tab
3、计算key的hash
4、如果当前table没有初始化,现代用initTable方法将tab初始化。
5、Tab索引i的位置元素为null,则直接使用CAS插入
6、判断是否在resize(扩容),在扩容走helpTransfer - transfer
7、没有resize走正常put
8、用synchronized针对单个节点加锁
9、判断节点hash(正常node大于0,树为-2)
10、正常节点插入,插入到队尾
11、树节点插入
initTable方法
1、自旋操作每次针对tab赋值而且判断长度
2、如果抢锁失败(sizeCtl作为自旋锁使用),则告诉cpu让出时间片(Thread.yield())
3、抢锁成功,再次检测tab的赋值以及数组长度
4、SizeCtl置为n的0.75
5、finally里面逻辑,sizeCtl赋值并解锁
addCount方法
增加表容量的计数,如果这个表需要resize但还没开始resize,则初始化transfer(这个东西用来进行resize)。如果已经开始resize了,则看看需不需要加入帮助一起resize。然后重新检查表的计数看看需不需要再次resize
transfer方法(resize用此方法):
原理就是让每一个put完的线程,或者想要put的线程到了整个表需要resize的时候都过来进入resize流水线工作,直到有一个线程说自己已经全部弄完了,方法才能返回。
以前的正常设计是resize的时候整个表就阻塞住了,但是现在resize的时候,想要操作的线程都会来参与一起resize,这么一来其他的线程就不用干等着了。
参考文档:
阿里Java研发岗如何面试:数据结构+MySQL+缓存雪崩+高并发等
面试题如下
一面(主要是jvm,并发,锁,数据结构等基础)
1.自我介绍(说说自己的擅长及拿手的技术)
2.自我介绍(说说自己的擅长及拿手的技术)说说treemap和HashMap的区别?HashMap和ConcurrentHashMap的区别?
3.HashMap底层如何实现(JDK1.8有所改动)?
4.说说Hash的一致算法?
5.你知道的GC算法和回收策略有哪些?GC的机制是什么?
6.垃圾回收器的基本原理?是否可以立即回收内存?怎么样主动的通知JVM进行垃圾回收?
7.双亲委派模型机制
8.线程池创建的几个核心构造参数是什么?
9.乐观锁和悲观锁?可重入锁和Synchronized?
10.他们都是可重入锁吗?哪个效率更高?
11.CountDownLaunch和Cylicbarrior的区别以及分别是在哪样场景下使用的?
12.Http和Https的区别以及Https加密的方式?
13.以后的职业规划和想法
二面(主要是数据库,协议,Spring等)
1.自我介绍,聊下自己认为做得很好的项目!
2.InnoDB支持的四种事务隔离级别名称是什么? 之间的区别是什么?MySQL隔离级别是什么?
3.说说事务的特性?讲讲对慢查询的分析?
4.你理解的BTree机制?
5.有哪些MySQL常用的优化方法?
6.Http请求过程,DNS解析的过程?
7.三次握手和四次握手的过程?
8.B+树索引和Hash索引之间的区别?
9.Spring IOC如何管理Bean之间的依赖关系,怎么样避免循环依赖?
10.SpringBean创建过程中的设计模式?
11.说说AOP的实现原理?
12.Tomcat的基本架构是什么?
三面(主要是缓存,高并发,分布式)
1.自己项目中的总结的并发经验
2.说说MySQL的锁并发?加锁的机制是什么?
3.高并发场景下如何防止死锁,保证数据的一致性?
4.集群和负载均衡的算法与实现?
5.说说分库与分表设计?
6.分库分表带来的分布式困境与对应之策有哪些?
7.Redis和Setnx命令使如何实现分布式锁的?使用Redis怎么进行异步队列?会有什么缺点?
8.缓存击穿的概念和解决方案?
9.Redis的数据结构? 线程模型? Redis的数据淘汰机制
10.Redis的数据一致性问题
11.MQ底层原理的实现?
12.阻塞队列不用Java提供的该怎么实现?
13.讲讲负载均衡的原理?
14.如何实现高并发环境下的削峰、限流?
四面(主要项目入手)
1.讲讲项目中用到的中间件(Dubbo/MQ/Zookeeper/Redis/Kafka)?
2.什么情况下会造成雪崩?该怎么避免这种情况?
3.高并发架构的设计思路?
4.以前的项目中遇到的问题和解决策略?
5.生活中遇到过哪些挫折?最后怎么解决的?
---------------------
java方向的学生面试哪些东西可以加分
对于应届生来说,考察的重点都是基础,对于每一个知识点都要做到知其然知其所以然,不必要太担心项目经验。
Java方向需要学习的知识点有:基础语法、IO、并发、集合、多线程、JVM、GC、Spring等等,对于这些知识,需要能够熟练会用,知道它们的实现原理,并且都动手实践过。
三年Java开发的工程师能接面试电话接到手软吗
老码农来分析下:
1、工作3年,应该算是中级工程师,技术好点的能算是半个高级
2、面试电话的多少取决于市场的需求和你的求职匹配度
3、求职简历是否吸引hr,也是一个重要的因素
综上,如果想接到很多面试电话,那么你的简历要找专业人员给把把关,还有自己的技能要过硬。不管是否接到手软,只要能找到自己满意的工作,那就很好了
个人观点,欢迎讨论!!
jdk8中的ConcurrentHashMap究竟为什么高效
从源码来窥其一斑!
我们都知道hashMap不是线程安全的,因为在扩容方法中很容易出现死循环,hashTable使用锁的方式比较简单暴力,几乎在所有操作方法上都加了synchronized锁,导致总体性能很差,concurrentHashmap凭借线程安全且性能优异一直都是高并发中的首选key-value型数据结构;
concurrentHashmap的高性能有以下原因:
一,分段锁:jdk8中对concurrentHashmap进行了改进,抛弃了jdk7中新建segment作为分段锁的过程,jdk8中虽沿用了这种分段锁的思想,却直接使用数组中的数据作为分段锁保证concurrentHashmap在上锁的时候只针对数组下标下的数据进行上锁(比如如果数组长度为256,那么每次put平均只有1/256的数据被锁),而大多数其他的数据还是能进行正常的增删改操作,无需阻塞等待,这无疑极大的降低了锁的粒度,提升了性能。
二,红黑树 :jdk8中引入了红黑树结构,在单个数组下标内的数据达到8以后,会自动转换为红黑树进行存储,使用大O表示法表示效率的话,红黑树的查找效率为O(log(n)),而链表的效率为O(n),当数据量越来越大的时候,红黑树的效率明显好于链表,所以concurrentHashmap性能得到很大提升;
现在我们主要从put方法中的主要方法来分析性能的提升:
spread(key.hashCode());//作用是再次哈希,减少冲突 ,源码如下
其中涉及到的位运算有
》》》 16:无符号右移16位,空位以0补齐 。
^:异或运算符--》相同为0,不同为1; &:与运算符--》全1得1,否则0;
(h ^ (h 》》》 16)) & HASH_BITS; 所以这句代码的意思就是不仅消除高16位的影响,同时获得正整数的hash值
再来看后面的方法, 如上图:
1,就是判断当这个hash表还是空的时候,调用initTable进行初始化; 2,使用(n - 1) & hash)计算数组下标,如果数据指定下标处为null,则直接插入,注:cas是java8中的concurrentHashmap引入的线程安全判断,CAS算法做为乐观锁;
3,(fh = f.hash) == MOVED,走到此处说明下标内有node,且该node的值为-1(MODED=-1),搜索全类发现MODED是在调用有参构造器ForwardingNode中默认写入的,而这个调用处刚好在transfer方法中,所以我们推断,扩容的时候先将数组下标内的node.hash置为-1! 同时在3这一步中调用helpTransfer(tab, f)参与扩容,并把数据写入;
4,走到这说明node不是空的,也没在扩容,那么锁住该下标下的node,并把新value插入链表中; 5,如果锁住的这个node能实例化为TreeBin,则代表已经转化为红黑树进行存储,将数据插入红黑树中; 6,判断在4,5中计算得到的数组下标内所有节点总数,如果满足转化为红黑树的条件(节点数大于8),则自动转化为红黑树进行存储!
总的来说,concurrentHashmap之所以性能高就是因为使用了分段锁和红黑树!
至于conrrentHashmap其他的方法的源码分析,后期会补上的,更多的技术分享,敬请关注!
面试一个5年经验的java,不知数据结构,却大谈分布式,这样的候选人能要吗
我估计你是问了人家 jdk各种数据结构底层实现原理,其实我一直很纳闷啊,知道底层实现原理 这当然很OK 很加分,但若是不是那么知道,那又怎样呢?人家知道哪些数据结构适合哪些场景并能熟练使用它们,这...不够么?对你们公司的用人需求不够么?难道你是指望他给你们公司创造一个新的数据结构?又或者觉得jdk已经实现的数据结构性能遇到瓶颈 指望求职者给你再实现一遍一模一样但性能比jdk提供的还优秀的数据结构啊?
南京万和Java培训分享Java高频面试题—如何对HashMap按键值排序
Java中HashMap是一种用于存储“键”和“值”信息对的数据结构。不同于Array、ArrayList和LinkedLists,它不会维持插入元素的顺序。
1. HashMap存储每对键和值作为一个Entry《K,V》对象。例如,给出一个HashMap,
view plain copy print?
Map《String,Integer》 aMap = new HashMap《String,Integer》();
键的每次插入,都会有值对应到散列映射上,生成一个Entry 《K,V》对象。通过使用这个Entry 《K,V》对象,我们可以根据值来排序HashMap。
2.创建一个简单的HashMap,并插入一些键和值。
view plain copy print?
Map《String,Integer》 aMap = new HashMap《String,Integer》();
// adding keys and values
aMap.put(“Five“, 5);
aMap.put(“Seven“, 7);
aMap.put(“Eight“, 8);
aMap.put(“One“,1);
aMap.put(“Two“,2);
aMap.put(“Three“, 3);
3.从HashMap恢复entry集合,如下所示。
view plain copy print?
Set《Entry《String,Integer》》 mapEntries = aMap.entrySet();
4.从上述mapEntries创建LinkedList。我们将排序这个链表来解决顺序问题。我们之所以要使用链表来实现这个目的,是因为在链表中插入元素比数组列表更快。
view plain copy print?
List《Entry《String,Integer》》 aList = new LinkedList《Entry《String,Integer》》(mapEntries);
5.通过传递链表和自定义比较器来使用Collections.sort()方法排序链表。
view plain copy print?
Collections.sort(aList, new Comparator《Entry《String,Integer》》() {
@Override
public int compare(Entry《String, Integer》 ele1,
Entry《String, Integer》 ele2) {
return ele1.getValue().compareTo(ele2.getValue());
}
});
6.使用自定义比较器,基于entry的值(Entry.getValue()),来排序链表。
7. ele1.getValue(). compareTo(ele2.getValue())——比较这两个值,返回0——如果这两个值完全相同的话;返回1——如果第一个值大于第二个值;返回-1——如果第一个值小于第二个值。
8. Collections.sort()是一个内置方法,仅排序值的列表。它在Collections类中重载。这两种个方法是
view plain copy print?
public static 《T extends Comparable《? super T》》 void sort(List《T》 list)
public static 《T》 void sort(List《T》 list, Comparator《? super T》 c)
9.现在你已经排序链表,我们需要存储键和值信息对到新的映射中。由于HashMap不保持顺序,因此我们要使用LinkedHashMap。
view plain copy print?
Map《String,Integer》 aMap2 = new LinkedHashMap《String, Integer》();
for(Entry《String,Integer》 entry: aList) {
aMap2.put(entry.getKey(), entry.getValue());
}
10.完整的代码如下。
view plain copy print?
package com.speakingcs.maps;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class SortMapByValues {
public static void main(String args) {
Map《String,Integer》 aMap = new HashMap《String,Integer》();
// adding keys and values
aMap.put(“Five“, 5);
aMap.put(“Seven“, 7);
aMap.put(“Eight“, 8);
aMap.put(“One“,1);
aMap.put(“Two“,2);
aMap.put(“Three“, 3);
sortMapByValues(aMap);
}
private static void sortMapByValues(Map《String, Integer》 aMap) {
Set《Entry《String,Integer》》 mapEntries = aMap.entrySet();
System.out.println(“Values and Keys before sorting “);
for(Entry《String,Integer》 entry : mapEntries) {
System.out.println(entry.getValue() + “ - “+ entry.getKey());
}
// used linked list to sort, because insertion of elements in linked list is faster than an array list.
List《Entry《String,Integer》》 aList = new LinkedList《Entry《String,Integer》》(mapEntries);
// sorting the List
Collections.sort(aList, new Comparator《Entry《String,Integer》》() {
@Override
public int compare(Entry《String, Integer》 ele1,
Entry《String, Integer》 ele2) {
return ele1.getValue().compareTo(ele2.getValue());
}
});
// Storing the list into Linked HashMap to preserve the order of insertion.
Map《String,Integer》 aMap2 = new LinkedHashMap《String, Integer》();
for(Entry《String,Integer》 entry: aList) {
aMap2.put(entry.getKey(), entry.getValue());
}
// printing values after soring of map
System.out.println(“Value “ + “ - “ + “Key“);
for(Entry《String,Integer》 entry : aMap2.entrySet()) {
System.out.println(entry.getValue() + “ - “ + entry.getKey());
}
}
}