Java2021面试基础总结

1.String,StringBuilder,StringBuffer联系与区别

String算是最常用的java数据类型,内部是由私有的char[]数组及hash构成
String类型主要用于操作字符串
问题1:
String被设计成不可变,为什么值却不一样?

在这里插入图片描述
可以从图看出,第一次值为123,第二次为234,为什么说String不可变呢?是因为a对象在内存中被创建了两次,第一次为123,第二次234,第一次a的地址指向了’123’,第二次指向’234’。
问题2:String被设计成不可变有什么好处?
1、便于实现字符串池(String pool)
在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。
2.加快字符串处理速度
由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。
3.安全问题

问题3:
String s1 = “a”;
String s2 = s1 + “b”;
String s3 = “a” + “b”;
System.out.println(s2 == “ab”);
System.out.println(s3 == “ab”);

答案:false true
原因:s3之所以==“ab”,是因为一个新创建的String对象而不是string常量。“a”+“b"等价于"ab”,只是生成一个字符串常量,保存在栈中。
而s1+"b"类似于操作StringBuilder先创建s1对象赋值“a”,再进行append(),将“b”拼接到s1上,并保存在堆中。
问题4:String和StringBuilder的区别?
1.它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。
2.String实现了equals方法,new String(“abc”).equals(newString(“abc”)的结果为true,而StringBuffer没有实现equals方法,所以,new StringBuffer(“abc”).equals(newStringBuffer(“abc”)的结果为false。
3.StringBuilder创建字符串的效率比String高,例如:
在这里插入图片描述
又比如:
String str=“a”+“b”+“c”;
StringBuilder sb=new StringBuilder(“a”).append(“ b”).append(“ c”);
str的创建速度是快于sb的,是因为在编译期,javac自动将它们拼到了一起。
但如果是:
String str1=“a”;
String str2=“b”;
String str3=“c”;
String str=str1+str2+str3;这样速度就会不如sb
问题5:StringBuffer与StringBuilder的区别?
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

2.List,Set,Map

问题1:ArrayList数据结构,扩容机制,初始化
ArrayList是动态数组,相当于Array的复杂版本,也就是说,ArrayList对象既有数组的特征,也有列表的特征。ArrayList实现了List接口,允许对元素进行快速随机访问。

扩容及初始化:
1.以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。
2.当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为10。此时,minCapacity - elementData.length > 0 成立,所以会进入 grow(minCapacity) 方法。
当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。
直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。
当ArrayList不为空时,并且它的大小不超过10时,它的容量都是10.但当大小从10增加到11时,容量变成了15,扩大了1.5倍.

问题2:如果并发操作ArrayList会产生什么效果?
1.并发操作add():
在这里插入图片描述
2.循环时删除arraylist:
在这里插入图片描述
在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。
迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何的保证。快速失败迭代器只是尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
问题3:如何解决ArrayList并发下的问题?
1.Collections.synchronizedList(new ArrayList());
2.Vector类(方法加了synchronized)
3.使用jdk并发包下的CopyOnWriteArrayList:CopyOnWrite 写入时复制。避免了再迭代时候对容器的加锁和复制。通常更适合用于迭代。
在这里插入图片描述
在多插入的情况下由于多次的复制性能会一定的下降。
问题4:HashSet是啥?可以干嘛?
Set的实现类,主要用于存储不重复的无序集合。
Set无序性:计算key的hash值和当前数组长度的 & 运算,计算保存数据的下标位置。所以说set是无序。
Set不重复:由于HashSet的add()底层是用的HashMap的put()实现,HashMap的put方法key重复直接覆盖。
问题5:HashMap是线程安全的吗?并发操作会发生什么问题?
并发问题1:HashMap并不是线程安全的。多线程并发操作HashMap的put()会导致get()死循环,导致cpu占有率100%。原因:HashMap容量有限,当容量到达限制时需要进行扩容,也就是Resize的过程,Resize主要通过两个步骤:扩容(创建新数组,长度为之前的2倍),ReHash(遍历原Entry数组,并用新的hash算法将原entry数组赋值到新Entry数组)。当多个线程同时操作hashmap的put(),可能导致形成环状链表,如果此时去get(),可能会get()到一个不存在的key,形成死循环。(这个问题在jdk1.8已解决)
详情参考:https://blog.csdn.net/bjwfm2011/article/details/81076736?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162307331416780255234881%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162307331416780255234881&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-2-81076736.first_rank_v2_pc_rank_v29&utm_term=HashMap%E5%B9%B6%E5%8F%91%E6%93%8D%E4%BD%9C%E7%9A%84%E9%97%AE%E9%A2%98%C2%B7&spm=1018.2226.3001.4187
并发问题2:多线程put会导致元素丢失。
并发问题3:put非null元素可能会导致get取出来为null。
问题6:HashMap底层实现原理?
jdk1.8之前:数组+链表,jdk1.8之后:数组+链表/红黑树(长度大于8)。数组是用来确定桶的位置,利用元素的key的hash值对数组长度取模得到。
链表是用来解决hash冲突问题,当出现hash值一样的情形,就在数组上的对应位置形成一条链表。
问题7:可以用LinkedList替代数组吗?
在这里插入图片描述
所以可以用LinkedList替代数组。
问题8:那为啥要用数组不用LinkedList?
在这里插入图片描述

问题9:红黑树效率比链表高吗?为什么不直接用红黑树替代链表?
因为红黑树的查找效率为log(n),链表的查找效率为n/2,当n=8,红黑树查找所需要时间为3,链表为4.因此当长度为8的时候红黑树效率高于链表。
问题10:HashMap get/set过程,造成hash冲突的解决方法?
put:对key的hashCode()做hash运算,计算index;

如果没碰撞直接放到bucket里;

如果碰撞了,以链表的形式存在buckets后;

如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树(JDK1.8中的改动);

如果节点已经存在就替换old value(保证key的唯一性)

如果bucket满了(超过load factor*current capacity),就要resize。

get:对key的hashCode()做hash运算,计算index;

如果在bucket里的第一个节点里直接命中,则直接返回;

如果有冲突,则通过key.equals(k)去查找对应的Entry;

若为树,则在树中通过key.equals(k)查找,O(logn);

若为链表,则在链表中通过key.equals(k)查找,O(n)。
hash冲突:如果两个不同对象的hashCode相同,这种现象称为hash冲突。
解决hash冲突:
开发定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
再哈希法
链地址法
建立一个公共溢出区
java中hashmap采用的是链地址法
详情参考:
https://blog.csdn.net/qq_36771269/article/details/79728243?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162307617316780265499925%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162307617316780265499925&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-79728243.first_rank_v2_pc_rank_v29&utm_term=%E4%BB%80%E4%B9%88%E6%98%AFhash%E5%86%B2%E7%AA%81&spm=1018.2226.3001.4187
问题11:一般用什么数据类型作为HashMap的key,为什么?
一般用Integer、String这种不可变类当HashMap当key,而且String最为常用。
(1)因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
(2)因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的覆写了hashCode()以及equals()方法。
问题12:HashMap的线程安全类?
1、HashTable
2、Collections.synchronizedMap(new HashMap());
3、CurrentHashMap
CurrentHashMap提供锁分离机制,并不是对整个集合加锁,效率要高于同步集合类和HashTable。

3.多线程

问题1:怎么实现线程间通信?
在这里插入图片描述
synchronized关键字:通过对象锁的方式,实现通信。可能会导致服务器资源消耗过大(因为线程会一直尝试获得锁)。
volatile关键字修饰变量:主要用于多个线程共享变量一致。也就是当线程a修改了变量,在线程b就能立刻看到效果(可见性)。但例如 变量++ 这种操作并不能保证线程安全(原子性)。解决方法:在需要执行 变量++ 这一部分的代码块用synchronized(this)上锁/或者用lock锁。
wait/notify:例如生产者/消费者模型实现。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
问题2:sleep()和wait()区别?
sleep()睡眠时,保持对象锁,仍然占有该锁;
wait()睡眠时,释放对象锁。
问题3:了解有哪些锁机制吗?
主要有乐观锁/悲观锁,公平锁/非公平锁,分段锁等
乐观锁:在并发操作过程中,认为数据不会冲突。
操作方式:版本号机制,CAS原理。
版本号机制:每次操作数据以后版本号+1,更新数据时比较版本号,如果一致则更新,否则重新操作。
CAS:主要通过三个操作数(需要读写的内存位置V,进行比较的预期原值A,新写入的值B),如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。可能出现的问题:ABA问题:CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加1。只有检查当前引用等于预期引用并且当前版本号等于预期版本号时,才使用原子方式更新给定的新值。如果长时间不成功,会给CPU带来非常大的执行开销。只能保证一个共享变量的原子操作。

悲观锁:在并发操作过程中,认为数据会冲突。
操作方式:Synchronized和ReentrantLock
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值