相信很多同行小伙伴会因为许多原因想跳槽,不论是干得不开心还是想跳槽涨薪,在如此内卷的行业,我们都面临着“面试造火箭,上班拧螺丝”的局面,鉴于当前形势博主呕心沥血整理的干货满满的造火箭的技巧来了,本博主花费2个月时间,整理归纳java全生态知识体系常见面试题!总字数高达百万! 干货满满,每天更新,关注我,不迷路,用强大的归纳总结,全新全细致的讲解来留住各位猿友的关注,希望能够帮助各位猿友在应付面试笔试上!当然如有归纳总结错误之处请各位指出修正!如有侵权请联系博主QQ1062141499!
目录
3 实现一个容器,提供两个方法add,size。写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数为5的时候,线程2给出提示并退出,线程1继续执行。
1 统计某字符串在文件中出现的次数
public static void main(String[] args) {
String filePath = "/Users/handsome/Desktop/a.txt";
String word = "Lee";
System.out.println(countWordAppearTimes(filePath, word));
}
/**
* 统计每行的出现单词的出现次数之后
* @param filePath
* @param word
* @return
*/
public static int countWordAppearTimes(String filePath, String word) {
int times = 0;
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader(filePath);
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {//读文件每行字符串
//按照单词正则查找出现次数
Pattern p = Pattern.compile(word);
Matcher m = p.matcher(line);
while (m.find()) {
times++;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return times;
}
2 二分查找算法
/**
* @description : 二分查询(非递归方式) {1,3,8,10,11,67,100},编程实现二分查找,要求使用非递归方式完成。
* @date: 2020-06-30 17:25
*/
public class BinarySearchNonRecursive {
public static void main(String[] args) {
int[] arr = {1, 3, 8, 10, 11, 67, 100};
int index = binarySearch(arr, 67);
if (index != -1) {
System.out.println("找到了,下标为:" + index);
} else {
System.out.println("没有找到--");
}
}
private static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1; // 向左找
} else {
left = mid + 1; // 向右找
}
}
return -1;
}
}
3 实现一个容器,提供两个方法add,size。写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数为5的时候,线程2给出提示并退出,线程1继续执行。
关键:容器使用volatile关键字修饰。
方法一:
使用wait(),notify()实现。
package com.junli;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Container {
//volatile关键字,使t2得到通知,否则t2不能判断container的size
volatile List<Object> list = new ArrayList<>();
public void add(Object o){
list.add(o);
}
public int getSize(){
return list.size();
}
public static void main(String[] args) {
Container container = new Container();
final Object lock = new Object();
new Thread(()->{
synchronized (lock){
System.out.println("t2启动");
if(container.getSize() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
//通知t1继续执行
lock.notify();
}
},"t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
synchronized (lock){
System.out.println("t1启动");
for(int i =0;i < 10;i++){
container.add(new Object());
System.out.println("add"+i);
if(container.getSize() == 5){
lock.notify();
try {
//释放锁,让t2可以获得锁得以执行
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"t1").start();
}
}
方法二:
使用Latch(门闩)代替wait,notify来进行通知
好处通信方式简单,同时也可以指定等待时间
使用await和countdown方法替代wait和notify
CountDownLatch不涉及锁定,当count的值为零时线程继续执行
当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
package com.junli;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Container {
//volatile关键字,使t2得到通知,否则t2不能判断container的size
volatile List<Object> list = new ArrayList<>();
public void add(Object o) {
list.add(o);
}
public int getSize() {
return list.size();
}
public static void main(String[] args) {
Container container = new Container();
//调用countDown方法后-1,门就开了
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2启动");
if (container.getSize() != 5) {
try {
latch.await();//阻塞当前线程,直到计数器的值为0
//也可以指定等待时间
//latch.await(5,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
container.add(new Object());
System.out.println("add" + i);
if (container.getSize() == 5) {
//打开门闩,让t2得以执行
latch.countDown();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
}
}
4 两个数组求交集
public class ArrayJoinDemo {
private static final int[] ARR_1 = { 51, 32, 35, 24, 15 };
private static final int[] ARR_2 = { 11, 24, 32, 14, 5 };
/**
* 使用Set的特性(不允许重复),把数组1保存在HashSet中,然后遍历数组2的值,如果当前值已存在Set中,则该值为交集。同理,我们将交集保存在Set中去重,最后转换成int数组返回。
*/
@Test
void test1() {
HashSet<Integer> hashSet = new HashSet<>();
HashSet<Integer> hashSet2 = new HashSet<>();
for (int i : ARR_1) {
hashSet.add(i);
}
for (int i : ARR_2) {
if (hashSet.contains(i)) {
hashSet2.add(i);
}
}
int[] arr = new int[hashSet2.size()];
int i = 0;
for (Integer integer : hashSet2) {
arr[i] = integer;
i++;
}
Console.log(arr);
}
/**
* 暴力破解法,遍历两个数组,比较值。借用第三个数组来装载相同的值。时间复杂度为O(n).
*/
@Test
void test2() {
int N = 0;
if (ARR_1.length > ARR_2.length) {
N = ARR_2.length;
} else {
N = ARR_1.length;
}
int[] n = new int[N];
int k = 0;
for (int i = 0; i < ARR_1.length; i++) {
for (int j = 0; j < ARR_2.length; j++) {
if (ARR_1[i] == ARR_2[j]) {
n[k++] = ARR_1[i];
}
}
}
n = Arrays.copyOf(n, k);
Console.log(n);
}
}
5 动态代理算法
interface Target {
void test();
}
class TargetImp implements Target {
@Override
public void test() {
System.out.println("test");
}
}
/**
* 动态代理算法
* 不修改原始代码的情况下,执行自定义代码
*/
public class TargetProxy {
/**
* invoke() 代表的是执行代理对象的方法
* method:代表目标对象的方法字节码对象
* args:代表目标对象的响应的方法的参数
*/
@Test
void test1() {
Target target = (Target) Proxy.newProxyInstance(
Target.class.getClassLoader(),
new Class[] { Target.class },
(proxy, method, args) -> {
Console.log("before");
// 反射执行
Object invoke = method.invoke(new TargetImp(), args);
Console.log("after");
return invoke;
});
target.test();
}
@Test
void test2() {
TargetImp targetImp = new TargetImp();
Target target = (Target) Proxy.newProxyInstance(
targetImp.getClass().getClassLoader(),
targetImp.getClass().getInterfaces(),
(proxy, method, args) -> {
Console.log("test2 before");
Object invoke = method.invoke(new TargetImp(), args);
Console.log("test2 after");
return invoke;
});
target.test();
}
}
6 10个线程执行完成后才能执行下一步操作的编码
方法1,利用Future
// 构造方法参数列表解释:
// corePoolSize - 池中所保存的线程数,包括空闲线程。
// maximumPoolSize - 池中允许的最大线程数。
// keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
// unit - keepAliveTime 参数的时间单位。
// workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
private static ExecutorService executor =
new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
/**
* 10个线程执行完成后才能执行下一步操作
*/
@Test
public void testThreadOver() {
List<Future> futureList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<?> future = executor.submit(() -> {
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
futureList.add(future);
}
poolExceptionHanding(executor, ArrayUtil.toArray(futureList,Future.class));
log.info("所有线程执行完毕,执行下一步操作");
}
/**
* @param pool 线程池
* @param futures Future对象
* @description: 处理线程池的异常
* @return: void
* @author: lijun
* @date: 2020-06-30 15:11:06
*/
private void poolExceptionHanding(ExecutorService pool, Future... futures) {
try {
for (Future future : futures) {
future.get();
}
log.info("所有线程无异常");
} catch (Exception e) {
pool.shutdownNow();
log.error("异常: " + e.getMessage());
throw new RuntimeException(e.getCause());
} finally {
// 不再接受新的线程,所有线程完成则结束
pool.shutdown();
log.info("关闭线程池");
}
}
方法2,利用CountDownLatch
/**
* 方法2,利用CountDownLatch
* 10个线程执行完成后才能执行下一步操作,
*/
@Test
public void testCountDownLatch() {
long start = System.currentTimeMillis();
final ExecutorService service = Executors.newFixedThreadPool(10);
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
service.execute(() -> {
try {
Thread.sleep(1000l);
countDownLatch.countDown();//当前线程调用此方法,则计数减一
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
try {
countDownLatch.await();//阻塞当前线程,直到计数器的值为0
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("所有线程执行完毕,耗时{}毫秒,执行下一步操作", System.currentTimeMillis() - start);
}
7 两个线程交替打印 1a2b3c4d5e
方法1-synchronized关键字实现
public class AlternateThread {
private static volatile Object lock = new Object();
/**
* 两个线程交替打印 1a2b3c4d5e
*
* @param args
*/
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
synchronized (lock) {
try {
System.out.println(i);
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
}
}
});
Thread t2 = new Thread(() -> {
for (char i = 'a'; i <= 'e'; i++) {
synchronized (lock) {
try {
System.out.println(i);
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
}
}
});
t1.start();
t2.start();
}
}
方法2 -LockSupport
private static Thread t1, t2;
public static void main(String[] args) {
waistDemo();
t1.start();
t2.start();
}
public static void waistDemo() {
t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
// 唤醒处于阻塞状态的指定线程
LockSupport.unpark(t2);
// 阻塞当前线程,如果调用unpark方法或者当前线程被中断,从能从park()方法中返回
LockSupport.park();
}
});
t2 = new Thread(() -> {
for (char i = 'a'; i <= 'e'; i++) {
LockSupport.park();
System.out.println(i);
// 唤醒处于阻塞状态的指定线程
LockSupport.unpark(t1);
}
});
}
方法3- BlockingDeque阻塞队列实现
private static Thread t1, t2;
private static BlockingDeque<Object> bq1 = new LinkedBlockingDeque<>(1);
private static BlockingDeque<Object> bq2 = new LinkedBlockingDeque<>(1);
public static void main(String[] args) {
blockingDequeDemo();
t1.start();
t2.start();
}
/**
* 使用BlockingDeque两个线程交替打印1a2b3c4d5e
*/
public static void blockingDequeDemo() {
t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
//put()方法向队列中生产数据,当队列满时,线程阻塞
bq1.put(i);
//take()方法从队列中消费数据,当队列为空是,线程阻塞
System.out.println(bq2.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2 = new Thread(() -> {
for (char i = 'a'; i <= 'e'; i++) {
try {
System.out.println(bq1.take());
bq2.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
方法4- ReentrantLock可重入锁实现
private static Thread t1, t2;
private static ReentrantLock reentrantLock = new ReentrantLock(true);
private static Condition condition1 = reentrantLock.newCondition();
private static Condition condition2 = reentrantLock.newCondition();
private static volatile Boolean ifT2Start = false;
public static void main(String[] args) {
reentrantLockDemo();
t1.start();
t2.start();
}
public static void reentrantLockDemo() {
t1 = new Thread(() -> {
try {
reentrantLock.lock();
while (!ifT2Start) {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
//调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中
condition1.signal();
//当调用condition.await()方法后会使得当前获取lock的线程进入到等待队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的lock
condition2.await();
}
}
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
});
t2 = new Thread(() -> {
try {
reentrantLock.lock();
ifT2Start = true;
for (char i = 'a'; i <= 'e'; i++) {
System.out.println(i);
condition2.signal();
condition1.await();
}
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
});
}
方法5-TransferQueue实现
private static Thread t1, t2;
public static void main(String[] args) {
transferQueueDemo();
t1.start();
t2.start();
}
private static TransferQueue<Object> tq = new LinkedTransferQueue<>();
public static void transferQueueDemo() {
t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
tq.transfer(i);
System.out.println(tq.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2 = new Thread(() -> {
for (char i = 'a'; i <= 'e'; i++) {
try {
System.out.println(tq.take());
tq.transfer(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
方法6-自旋方式实现
private static Thread t1, t2;
public static void main(String[] args) {
selfDemo();
t1.start();
t2.start();
}
static volatile int x = 0;
/**
* 使用自旋方式两个线程交替打印1a2b3c4d5e
*/
public static void selfDemo() {
t1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
while (x != 0) {
}
System.out.println(i);
x = 1;
}
});
t2 = new Thread(() -> {
for (char i = 'a'; i <= 'e'; i++) {
while (x != 1) {
}
System.out.println(i);
x = 0;
}
});
}