java基础相关面试题
看了一篇博文说到了一些面试题但没有答案。很多问题自己不知道答案,所以记录下一学习的过程。(我还是小白,有错的地方请指出)
链接地址
1. HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化
HashMap Map的一个实现类,用于存储KV数据结构.
真实存储数据的是它的一个内部类Node
node是往链表后面放的
如果node链表长度超过一定值,链表转换为红黑树,红黑树的具体实现不了解 -_-!
在1.7中,不会进行链表到红黑树的转化,如果整个链表中没有key相同的node,是放在链表的第一个位置,1.8放在后面
//它需要维护一个链表结构static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}
HashMap的构造
public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // 默认值为0.75}
put放入数据
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//如果table(node数组属性),还没有任何东西, resize()进行初始化if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//i = (n - 1) & hash], 根据hash值找出在数组中node应该存放在那个位置,如果没有任何东西直接放入if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {//否则说明数据应该存放的地方有数据存在Node<K,V> e; K k;//如果p(原来位置存放的node),和打算存入的node,的key相同,使用e保存if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//如果p和打算放入的key不相同,如果p节点是TreeNodeelse if (p instanceof TreeNode)//调用放入TreeVale = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//说明第一个位置的p和打算放入的node的key不相同,且,这个节点是普通的链表结构for (int binCount = 0; ; ++binCount) {//遍历整个节点,如果没有下一个节点,创建node保存数据,插入到后面if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//如果链表长度超过(8-1) 进行链表向红黑树的改变if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}//如果,遍历链表时,有node的key和要放入的node的key相同跳出,这是e保存着key相同的nodeif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//如果找到了eif (e != null) { // existing mapping for key//先保存一个e原来的值V oldValue = e.value;//存入新的值,返回原来的值if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}
获取node
public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;//判断非空,链表位置有值if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//如果第一个node就是返回if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;//如果node下面后有继续找if ((e = first.next) != null) {//如果是TreeNode结构if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);//否则就是链表结构,循环找出do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}
2. HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小.
1.8 为例子
明确两个概念, capacity,Cap = table 的容量; threshold 边界,链表的边界
final Node<K,V>[] resize() {# table存储node节点的数组,oldTab保存一下Node<K,V>[] oldTab = table;//table还没有初始化那么 oldCap为0int oldCap = (oldTab == null) ? 0 : oldTab.length;//oldThr threshod //旧的边界int oldThr = threshold;//新的容量,新的边界定义int newCap, newThr = 0;if (oldCap > 0) {//如果oldCap就的容量超过最大值,直接更新threshold边界为最大值,返回,不做table的扩容if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;} //对容量 * 2,对边界也 * 2else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else { // table 还没有初始化进行默认初始化newCap = DEFAULT_INITIAL_CAPACITY; //默认新的容量为 16newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //0.75 * 16 边界默认为12}//容量判断,newThr初始化为 12if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//新的链表边界,新的容量threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;//进行就table到新table的拷贝if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {Node<K,V> e; //保存遍历时的当前节点if ((e = oldTab[j]) != null) {oldTab[j] = null;if (e.next == null)//如果table的位置的链表只有一个元素,直接拷贝newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)//如果是红黑树,那么进行红黑树的拷贝((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order//位置的链表存在多个节点Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;//table的容量扩了两倍,原先的值可能一样,也可能是另一条,根据条件分为拆为两条链表.if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}
HashMap的散列是如何进行呢?
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}首先对键的hashCode向右移动16位,h保存,然后进行异或621009875 例如 hash0010 0101 0000 0011 1101 1011 1101 0011 hash的二进制 int 4 个字节0000 0000 0000 0000 0010 0101 0000 0011 h也就是hash向右移动后0010 0101 0000 0011 1111 1110 1101 0000 进行异或后,不同出1,相同出0在想数组中存放的时候tab[ (tab.length - 1) & hash] 数组的下标直到 length-1,由于是与操作,同1出1,下标前面的二进制都是0,所以结果都被限定在了 0-下标最大值 的范围0010 0101 0000 0011 1111 1110 1101 00000000 0000 0000 0000 0000 0000 0000 1111 (length默认为16,16-15)0000 0000 0000 0000 0000 0000 0000 0000 此对象被存在数组的0位置
3. HashMap,HashTable,ConcurrentHashMap的区别。
- ,HashMap线程不安全,HashTable线程安全,ConcurrentHashMap线程安全,HashTable的线程安全是粗粒度的,通过在方法上添加synchronized来实现,ConcurrentHashMap的线程安全采用锁分段的机制来实现.
- HashTable会进行扩容,但是不会进行链表向红黑树的转变,HashMap和ConcurrentHashMap会把链表转为红黑树.
- HashTable初始容量为11,HashMap为16,
4. 极高并发下HashTable和ConcurrentHashMap哪个性能更好,为什么,如何实现的。
使用ConcurrentHashMap好,ConcurrentHashMap采用锁分段机制,能够减少锁的冲突
/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;//遍历node节点组成的tablefor (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;//如果table还不存在,进行初始化if (tab == null || (n = tab.length) == 0)tab = initTable();// 获取的是,key散列到的位置的节点,如果为null直接添加else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break; // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {V oldVal = null;//如果有多个节点的话,它对第一个节点进行加锁,那么就可以避免其他线程对此散列点的操作,避免冲突,synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {//把节点放在后面pred.next = new Node<K,V>(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;}
5. HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。
多线程put时可能会导致get无限循环,具体表现为CPU使用率100%;
原因:在向HashMap put元素时,会检查HashMap的容量是否足够,如果不足,则会新建一个比原来容量大两倍的Hash表,然后把数组从老的Hash表中迁移到新的Hash表中,迁移的过程就是一个rehash()的过程,多个线程同时操作就有可能会形成循环链表,所以在使用get()时,就会出现Infinite Loop的情况多线程put时可能导致元素丢失
原因:当多个线程同时执行addEntry(hash,key ,value,i)时,如果产生哈希碰撞,导致两个线程得到同样的bucketIndex去存储,就可能会发生元素覆盖丢失的情况
6. java中四种修饰符的限制范围。
权限修饰 | 子类 | 其他类 | 本包类 |
---|---|---|---|
private | 不可见 | 不可见 | 不可见 |
protected | 可见 | 不可见 | 不可见 |
public | 可见 | 可见 | 可见 |
无 | 可见 | 不可见 | 可见 |
7. Object类中的方法。
equals, 判断两个值是否相等,默认实现使用 == 判断
finalize(), 在垃圾准备回收此对象是调用
getClass(), 获得这个类的大Class
hashCode(), 返回对象的hash值
notify(), 唤醒一个在wait状态的线程
notifyAll(), 唤醒所有等待的线程
wait(), 挂起当前线程
wait(long timeout), 挂起线程,直到被其他线程唤醒,或设置的等待时间过去
wait(long timeout, int nanos), 更精细的时间控制
8. 接口和抽象类的区别
- 接口和抽象类的区别
- 抽象类要被子类继承,接口要被类实现。
- 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
- 接口里定义的变量只能是公共的静态的常量(public static final),抽象类中的变量是普通变量
- 抽象类里可以没有抽象方法
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
- 接口可继承接口,并可多继承接口,但类只能单根继承。
- 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
在java8中可以在接口中定义默认方法,默认方法可以在接口中实现,也可以在接口中定义静态方法,
package com.tttiger.lamdba;public interface DefaultInterface {default String getName() {return "haha";}public static String getAge() {return "aaa";}
}
但这就带来一个问题,如果一个类继承一个类和实现一个接口之间,继承类的方法和实现的默认方法冲突,如何解决,它默认调用的是继承类的方法,类优先
那如果继承两个接口,两个接口的默认方法冲突呢,那必须显示的重写,选定一个接口的默认方法
9. 动态代理的两种方式,以及区别。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)
JDK使用反射原理来实现代理,CGLib底层采用ASM字节码生成框架
JDK代理例子
//jdk的动态代理必须保证有接口
public interface Star {public void sing();
}
public class RealStar implements Star{@Overridepublic void sing() {System.out.println("唱歌");}
}//调用者
public class StarHandler implements InvocationHandler{private Star star;public StarHandler(Star star) {super();this.star = star;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("自定义处理");method.invoke(star, args);System.out.println("自定义处理");return null;}}//生成代理类调用public static void main(String[] args) {Star star = new RealStar();StarHandler handler = new StarHandler(star);Star starProxy = (Star) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{Star.class}, handler);starProxy.sing();}
10. Java序列化的方式。
序列化,把一个对象进行持久存储和传输
通过jdk的ObjectOutputStream来完成, 想要进行序列化的对象必须,实现Serializable 接口,标识此类可以进行序列化,不想序列化的属性通过transient标识
/** ObjectInputStream对象的流,可以把对象写入或者写出,写出对象叫做对象的序列化,读入对象叫做* 对象的反序列化,不是所有对象都可以序列化,不是所有属性都可以序列化,反序列化也是一种创建对象的方式*/
public class DemoObjectIO {public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File("e:/1.txt")));//Object 和 data类似也有基本数据类型的读写操作。out.writeObject(new Animal(10,"老虎"));out.flush();out.close();//生成的文件是一堆看不懂的字符//对象的反序列化,把对象从文件中读取出来ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("e:/1.txt")));Object temp = in.readObject();if(temp instanceof Animal){Animal a = (Animal)temp;//被transient修饰```的属性不参加序列化System.out.println(a.getAge());//输入老虎。System.out.println(a.getName());}in.close();}
}
11. 传值和传引用的区别,Java是怎么样的,有没有传值引用。
值传递,传递的位变量的副本,引用传递,只是传递一个引用就是把内存地址传过去. 值传递由于传进来的是副本,所以对它的修改,不会影响原先的值. 如果是引用传递,通过引用修改了对象,那么原有的值也会改变,因为都指向同一内存地址.
Student a = new Student();
test(a);
public void test(Student s){s = new Student();
}
//这只是方法中的引用从新指向了另一个对象,原有的不变,不要混淆
在java中如果传递的是基本数据类型,byte short int long char float double boolean 那么为值传递,其他为引用传递
12. 一个ArrayList在循环过程中删除,会不会出问题,为什么。
如果使用不当,会抛出并发修改异常 ConcurrentModificationException.
如何在算正常的使用? 如果使用 iterator进行遍历,需要在循环内删除,调用iterator的remove方法进行删除,注意一次循环只能调用一次, 如果是使用for遍历,通过所以去获取,可以使用本身的remove进行删除.
for(int i=0;i<arr.size();i++){System.out.println(arr.get(i));arr.remove(i);}Iterator<String> iterator = arr.iterator();while(iterator.hasNext()){String a = iterator.next();System.out.println(a);iterator.remove();}
为什么在使用iterator进行循环,调用list的remove抛出异常.iterator一边遍历,list一边修改list,这显然是不可取的, 保证list的修改,不会发生冲突.
list的 add,remove 等 只要是修改了 保存元素的数组,都会对 modCount ++
ArrayList通过记录一个属性 modCount 来记录修改操作的次数
ArrayList的iterator
private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no such//初始化时,保存了一份当前的修改次数int expectedModCount = modCount;Itr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {// 在操作之前都会进行判断, checkForCommodification, 看当前的 expectedModCount是否和 modCount 相等, 如果不等抛出 concurrentModifiedExceptioncheckForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i]; //重置了lastRet}//如果用 iterator的remove为什么不会抛出异常public void remove() {// 多次删除判断if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {//删除后对expectedModCount进行了从新赋值ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}}
13. @transactional注解在什么情况下会失效,为什么。
@Transactional注解只能应用到public修饰符上,其它修饰符不起作用,但不报错。
默认情况下此注解会对unchecked异常进行回滚,对checked异常不回滚。
unchecked也就是不需要手动抛出的异常, 运行时异常会自动进行回滚. 如果是普通的异常,如果没有显示的在transactional中指定此异常进行回滚,那么不进行回滚
@Transactionalpublic void addBook(String name) throws Exception {dao.insert(name);}//抛出一个普通异常,那么不会进行回滚,先要能够回滚就需要在Transactional中指定需要回滚public void insert(String name) throws Exception {String sql = "insert into test(`name`) value(?)";jdbcTemplate.update(sql,name);if(name.equals("aaa")){throw new Exception();}else if(name.equals("ccc")){//如果抛出一个运行时异常,那么默认就会对其进行回滚操作throw new RuntimeException();}}
咸鱼IT技术交流群:89248062,在这里有一群和你一样有爱、有追求、会生活的朋友! 大家在一起互相支持,共同陪伴,让自己每天都活在丰盛和喜乐中!同时还有庞大的小伙伴团体,在你遇到困扰时给予你及时的帮助,让你从自己的坑洞中快速爬出来,元气满满地重新投入到生活中!
java基础相关面试题相关推荐
- 【成神之路】Java基础相关面试题
面向对象的特征: 继承.封装和多态 final, finally, finalize 的区别 final final关键字可以用于类,方法,变量前,用来表示该关键字修饰的类,方法,变量具有不可变的特性 ...
- Java基础常见面试题(一)
Java基础常见面试题(一) 1. 为什么说 Java 语言"编译与解释并存"? 我们可以将高级编程语言按照程序的执行方式分为两种: 编译型 :编译型语言会通过编译器将源代码一次性 ...
- 分享18道Java基础面试笔试题
转载自 分享18道Java基础面试笔试题(面试实拍) 1.你最常上的两个技术站和最常使用的两个app分別进什么?主要解决你什么需求? 2.请简述http协议中get请求和post请求的区别. 3.请简 ...
- Java基础相关6(IO)
java基础相关6 File类 File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径) 使用File可以做到: 访问其表示的文件或目录的属性信息,例如:名字,大 ...
- 蓝桥杯 Java 基础练习 vip试题
蓝桥杯 Java 基础练习 vip试题 高精度加法 其实这道题调用函数就会变得更简单 import java.math.BigDecimal; import java.util.Scanner; pu ...
- Java 基础面试题,java基础面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
- java log4j基本配置及日志级别配置详解,java基础面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
- Java学习资源整理(超级全面),java基础面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
- java后台导出excel代码详细讲解,java基础面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
最新文章
- 内存分配器memblock【转】
- 20210810 所有图像数据准备一条龙(labelme_json转mask、数据增强Augmentor、随机种子设比例生成train.val、转格式(.jpg转.png)、尺寸、位深度变换
- AI领域的人才短缺,原因是什么?该如何解决?
- c语言,字符串原地翻转
- k8s edit命令使用示例
- java操作mongodb_Java操作MongoDB
- 文件描述符与打开文件的关系
- 快速搭建springmvc+spring data jpa工程
- Ubuntu 14.04 安装 MongoDB
- python count函数用法 comm_Python学习第六天课后练习案例 (主要针对的内容是python函数的定义和使用)...
- spring Bean的生命周期管理
- pytorch中的反卷积的output_padding参数
- jQuery源码阅读(一)---jQuery源码整体架构
- 5个免费全球DEM数据源-数字高程模型
- eclipse下载以及下载web插件速度慢的解决方法
- matlab符号表达式vpa,Matlab符号运算总结
- UVA - 10827 Maximum sum on a torus(dp最大子矩阵和)
- php 月柱计算,月柱推算
- iPhone与iPad开发实战读书笔记
- 递归算法和文件队列算法----实现多级文件夹的遍历,删除和复制操作
热门文章
- define定义的函数如何引用_C语言快速入门——使用#define让程序更易维护
- 与mysql数据库的交互实战_基于 Go 语言开发在线论坛(二):通过模型类与MySQL数据库交互...
- 苹果发布第四财季财报 iPhone 13上市初期表现如何将见分晓
- 消息人士:欧盟下月将对英伟达收购Arm交易展开正式调查
- 投行称在三季度所产iPhone中 iPhone 13将超过35%
- 投行精英接班后,贵人鸟能否再高飞?
- 微信新表情戒烟了!腾讯:雪茄大佬成了歪嘴战神
- iPhone 12 Pro系列变贵的原因在这儿!
- 联姻寺库,一次半斤八两的合作,趣店的奢侈品生意仍看不见未来
- 供应商否认iPhone 12延迟推出传闻 称生产按计划进行