看了一篇博文说到了一些面试题但没有答案。很多问题自己不知道答案,所以记录下一学习的过程。(我还是小白,有错的地方请指出)
链接地址

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的区别。

  1. ,HashMap线程不安全,HashTable线程安全,ConcurrentHashMap线程安全,HashTable的线程安全是粗粒度的,通过在方法上添加synchronized来实现,ConcurrentHashMap的线程安全采用锁分段的机制来实现.
  2. HashTable会进行扩容,但是不会进行链表向红黑树的转变,HashMap和ConcurrentHashMap会把链表转为红黑树.
  3. 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在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。

  1. 多线程put时可能会导致get无限循环,具体表现为CPU使用率100%;
    原因:在向HashMap put元素时,会检查HashMap的容量是否足够,如果不足,则会新建一个比原来容量大两倍的Hash表,然后把数组从老的Hash表中迁移到新的Hash表中,迁移的过程就是一个rehash()的过程,多个线程同时操作就有可能会形成循环链表,所以在使用get()时,就会出现Infinite Loop的情况

  2. 多线程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. 接口和抽象类的区别

  1. 接口和抽象类的区别
  2. 抽象类要被子类继承,接口要被类实现。
  3. 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
  4. 接口里定义的变量只能是公共的静态的常量(public static final),抽象类中的变量是普通变量
  5. 抽象类里可以没有抽象方法
  6. 抽象方法要被实现,所以不能是静态的,也不能是私有的。
  7. 接口可继承接口,并可多继承接口,但类只能单根继承。
  8. 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

在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基础相关面试题相关推荐

  1. 【成神之路】Java基础相关面试题

    面向对象的特征: 继承.封装和多态 final, finally, finalize 的区别 final final关键字可以用于类,方法,变量前,用来表示该关键字修饰的类,方法,变量具有不可变的特性 ...

  2. Java基础常见面试题(一)

    Java基础常见面试题(一) 1. 为什么说 Java 语言"编译与解释并存"? 我们可以将高级编程语言按照程序的执行方式分为两种: 编译型 :编译型语言会通过编译器将源代码一次性 ...

  3. 分享18道Java基础面试笔试题

    转载自 分享18道Java基础面试笔试题(面试实拍) 1.你最常上的两个技术站和最常使用的两个app分別进什么?主要解决你什么需求? 2.请简述http协议中get请求和post请求的区别. 3.请简 ...

  4. Java基础相关6(IO)

    java基础相关6 File类 File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径) 使用File可以做到: 访问其表示的文件或目录的属性信息,例如:名字,大 ...

  5. 蓝桥杯 Java 基础练习 vip试题

    蓝桥杯 Java 基础练习 vip试题 高精度加法 其实这道题调用函数就会变得更简单 import java.math.BigDecimal; import java.util.Scanner; pu ...

  6. Java 基础面试题,java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  7. java log4j基本配置及日志级别配置详解,java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  8. Java学习资源整理(超级全面),java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  9. java后台导出excel代码详细讲解,java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

最新文章

  1. 内存分配器memblock【转】
  2. 20210810 所有图像数据准备一条龙(labelme_json转mask、数据增强Augmentor、随机种子设比例生成train.val、转格式(.jpg转.png)、尺寸、位深度变换
  3. AI领域的人才短缺,原因是什么?该如何解决?
  4. c语言,字符串原地翻转
  5. k8s edit命令使用示例
  6. java操作mongodb_Java操作MongoDB
  7. 文件描述符与打开文件的关系
  8. 快速搭建springmvc+spring data jpa工程
  9. Ubuntu 14.04 安装 MongoDB
  10. python count函数用法 comm_Python学习第六天课后练习案例 (主要针对的内容是python函数的定义和使用)...
  11. spring Bean的生命周期管理
  12. pytorch中的反卷积的output_padding参数
  13. jQuery源码阅读(一)---jQuery源码整体架构
  14. 5个免费全球DEM数据源-数字高程模型
  15. eclipse下载以及下载web插件速度慢的解决方法
  16. matlab符号表达式vpa,Matlab符号运算总结
  17. UVA - 10827 Maximum sum on a torus(dp最大子矩阵和)
  18. php 月柱计算,月柱推算
  19. iPhone与iPad开发实战读书笔记
  20. 递归算法和文件队列算法----实现多级文件夹的遍历,删除和复制操作

热门文章

  1. define定义的函数如何引用_C语言快速入门——使用#define让程序更易维护
  2. 与mysql数据库的交互实战_基于 Go 语言开发在线论坛(二):通过模型类与MySQL数据库交互...
  3. 苹果发布第四财季财报 iPhone 13上市初期表现如何将见分晓
  4. 消息人士:欧盟下月将对英伟达收购Arm交易展开正式调查
  5. 投行称在三季度所产iPhone中 iPhone 13将超过35%
  6. 投行精英接班后,贵人鸟能否再高飞?
  7. 微信新表情戒烟了!腾讯:雪茄大佬成了歪嘴战神
  8. iPhone 12 Pro系列变贵的原因在这儿!
  9. 联姻寺库,一次半斤八两的合作,趣店的奢侈品生意仍看不见未来
  10. 供应商否认iPhone 12延迟推出传闻 称生产按计划进行