笔试复盘

这篇博客回顾了Java面试中的重点问题,包括int转long、引用传递、ConcurrentHashMap的put方法、Spring循环依赖处理、线程安全、反射机制在泛型中的应用、JVM内存结构以及算法题如LRU实现等。内容涵盖了基础语法、并发编程、JVM原理和数据结构等核心知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. int转long

	long val = 24 * 60 * 60 * 1000 * 1000l;
    long val1 = 24 * 60 * 60 * 1000 * 1000;
    System.out.println(Integer.MAX_VALUE);
    System.out.println(Long.MAX_VALUE);
    System.out.println(Long.MIN_VALUE);
    System.out.println(24 * 60 * 60 * 1000);
    System.out.println(val);
    System.out.println(val1);
    System.out.println(val / 60 / 60 / 1000 / 1000);
    // 1 这里最后结果是0的原因是计算过程中产生的是int再转的long
    System.out.println(val1 / 60 / 60 / 1000 / 1000);

2. 引用传递

另外:对于打印char[] :
char类型的数组就相当于一个字符串。

char类型的数组就相当于一个字符串。

因为输出流System.out是PrintStream对象,PrintStream有多个重载的println方法,其中一个就是public void println(char[] x),直接打印字符数组的话,不像int[]等其他数组,它会直接调用这个方法来打印,因而可以打印出数组内容,而不是地址。

3. ConcurrentHashMap的put方法简述

自己的答案:

  1. 先判断当前容器是否被初始化,如果没有初始化则先初始化
  2. 然后先根据key使用(key & (len - 1))的方式计算出所在位置
  3. 如果该位置为空,则直接使用CAS插入即可,然后判断此时是否达到了阈值,是则进行扩容。
  4. 如果该位置不为空,则需要先加锁
  5. 然后先通过hash进行比较,相等再通过equals比较是否相等,相等则直接替换值,如果不相等再看该节点是链表节点还是红黑树节点,然后按照对应数据结构的遍历方式依次遍历,直到相等后替换值或者找不到则插入对应的数据结构,插入之前如果是链表则需要考虑是否满足条件(元素size大于64,链表长度大于8),是则转换为红黑树。
  6. 看是否达到了阈值,达到了就扩容

根据源码补充的答案:
1. 先对key使用扰动方法((key.hashCode() ^ (key.hashCode() >>> 16)) & 0x7fffffff)得到hash值
2. 判断是否为null,是则进行初始化(初始化过程中只能让一个线程初始化)
3. 然后根据hash值通过hash & (len - 1)得到所在位置
4. 判断所在位置是否为空,为空直接使用CAS插入
5. 如果不为空,则先加锁(Synchronzied),加锁范围限制在通过hash找到的节点上
6. 先判断f节点是链表节点还是红黑树节点,是链表节点的话,通过for循环一个节点一个节点的判断(先判断hash,再判断是否相等),最后找到了节点的话,就替换值,如果没有找到,则在尾部插入
7. 是红黑树的话,则依照红黑树的遍历方式遍历最后插入或替换
8. 此时同步块结束,开始判断如果是链表,看是否满足条件(转化为红黑树的条件),满足则转换。
9. 此时增加size,看是否达到阈值,达到则扩容

4. 有三个bean A、B、C;其中,A依赖于B,B依赖于C,C又依赖于A,这样能否正常启动?

Spring中循环依赖场景有:

(1)构造器的循环依赖

(2)field属性的循环依赖

其中,构造器的循环依赖问题无法解决,只能拋出BeanCurrentlyInCreationException异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法。

https://blog.csdn.net/xx897115293/article/details/108635440

https://www.cnblogs.com/jajian/p/10241932.html

https://zhuanlan.zhihu.com/p/62382615

https://segmentfault.com/a/1190000019286083

5. spring bean 如何保持线程安全,为什么?

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

https://www.cnblogs.com/frankcui/p/14341795.html

6. 现有集合ArrayList list = new ArrayList(); 利用反射机制在这个泛型为Integer的ArrayList中存放一个String类型的对象。

先解释:
可以使用反射技术,来获得add方法,这样的话就可以随便存了,sun规定的泛型只是将错误上升到编译时期,在运行时期没有泛型的。直接使用反射得到add方法就不会再有泛型的限制了

代码:

public static void main(String[] args) throws Exception {
			List<Integer> list = new ArrayList<Integer>(); //定义Integer泛型
			String str = "abc";
			Method[] method=list.getClass().getMethods();//取得list的所有方法
			System.out.println(method.length);

			for(int i=0;i<method.length;i++){
				System.out.println(method[i]);//遍历打印list的方法
			}

			//通过反射来执行list的第一个方法,第一个是list对象,代表该对象的方法,第二个是方法参数
			method[0].invoke(list, str);

			System.out.println(list.size());

			for(int i=0;i<list.size();i++)
				System.out.println(list.get(i));
		}

7. 描述一下HotSpot JVM的内存结构,说明每块区域对应的功能。

自己的答案:
内存结构主要分为两部分:
1. 一部分是线程私有区域:栈、程序计数器、本地栈
2. 一部分是线程共享区域:堆、方法区

堆:主要用来存储对象
方法区:主要存储加载的类的元数据信息

程序计数器:指向执行的字节码的行号,主要用来实现跳转等功能。
栈:里面包含栈帧,每个栈帧用来表述运行的方法,栈帧中包含该方法的局部变量表、操作数栈等
本地栈:和栈功能类似,不过主要用来表述本地的方法

其他答案:
https://blog.csdn.net/chen772209/article/details/107045647/

8. 运行一段代码会抛出什么异常:

private void recursive() {
	recursize();
}

StackOverFlowError
原因:栈的大小是有限的,当申请的栈帧过多时,会抛出此异常
线程请求的栈深度大于虚拟机所允许的深度

9. 列出一些常用的jvm参数,并描述每个参数的作用

-Xss 线程栈大小
-Xmn 初始堆大小
-Xmx 最大堆大小
-XX:NewSize设置年轻代大小
-XX:NewRatio 设置年轻代和老年代的比值,一般是3,代表年轻代是年老代的1/3
-XX:SurvivorRatio 年轻代中Eden和两个Survivor区的比值
-XX:+PrintGCDetails   打印虚拟机的具体细节
-XX:+HeapDumpOnOutOfMemoryError  如果内存溢出就把堆中的信息打印出来

10. 以下哪个对象不可能包含SQL语句

1. 触发器
2. 函数
3. 索引
4. 视图

这个暂时不太清楚。。

11. 下面关于DNS说法错误的是:

1. DNS的作用是域名和IP地址的互相映射
2. DNS协议运行在UDP协议之上
3. DNS协议端口为53
4. DNS的默认缓存时间为1小时

这个目前感觉是4,缓存时间到底指的是游览器的还是其他的

12. 编程实现:给定两个字符串,确定其中一个字符串重排列后是否可以包含另一个字符串?

例如:
给出字符串:ac以及abc, 对字符串重新排列生成acb包含ac,返回true
自己的解决办法:
1. 先判断哪个字符长,短的将其所有字符存入map并计数
2. 然后遍历长的,一一判断每个字符,对map进行消除
3. 判断map是否为空,为空则代表true

13. 实现LRU

https://blog.csdn.net/CodingNO1/article/details/107365282

class LRUCache {

	// 缓存容量
	private int cap = 0;

	// 已经存储的数据个数
	private int size = 0;

	// 用来提高查找效率的工具
	private ConcurrentHashMap<Integer, Node> cache = new ConcurrentHashMap<>();  

	// 维护一个首尾节点 作为一个标记位,实际不存值
	private Node head, tail;

	public LRUCache(int cap) {
		// 对容量进行初始化
		this.size = 0;
		this.cap = cap;

		// 对首尾节点初始化
		head = new Node();
		tail = new Node();

		// 构建一个双向链表
		head.next = tail;
		tail.pre = head;
	}


	/**
	 * 每次取数据后加节点移到最前面
	 * @param key
	 * @return
	 */
	public int get(int key) {
		Node node = cache.get(key);

		// 没有则返回-1
		if (node == null) {
			return -1;
		}

		// 移动
		moveToHead(node);

		return node.val;
	}

	/**
	 * 存入数据,且将这个数据置于首部
	 * @param key
	 * @param value
	 */
	public void put(int key, int value) {
		// 先看是否存在这个key ,存在就直接移动到首部即可
		Node node = cache.get(key);

		if (node != null) {
			// 更新对应的value
			node.val = value;
			// 移到首部
			moveToHead(node);
		} else { // 不存在则需要加入key
			// 先根据key,value构建节点
			Node newNode = new Node();
			newNode.setKey(key);
			newNode.setVal(value);

			// 先判断size是不是小于cap
			if (size == cap) {
				// 等于cap则先将尾部的remove,再添加
				Node tail = popTail();
		        cache.remove(tail.key);
		        size --;
			}

			// 将节点加入cache
			cache.put(key, newNode);

			// 加入以head为头,tail为尾的双向链表,head与tail都是空的节点
			addNode(newNode);

			// size 增加
			size ++;

		}

	}

	/**
	 * 将节点移到头部
	 * @param node
	 */
	private void moveToHead(Node node) {
		// 先删除节点
		removeNode(node);
		
		// 再添加就到了首部了(head的后面一个)
		addNode(node);

	}

	/**
	 * 添加到链表中 
	 * 这里就已经相当于添加到头部了,head和tail只是用来做标记的一个空节点
	 * @param node
	 */
	private void addNode(Node node) {
		node.pre = head;
		node.next = head.next;

		head.next.pre = node;
		head.next = node;

	}
	
	/**
	 * 从链表中移除
	 * @param node
	 */
	private void removeNode(Node node){
		// 获取当前节点的前后节点
	    Node pre = node.pre;
	    Node next = node.next;
	    
	    // 前后节点连接
	    pre.next = next;
	    next.pre = pre;
	  }
	
	/**
	 * 移除尾部节点
	 * @return
	 */
	private Node popTail() {
		// 先取到尾部节点
	    Node res = tail.pre;
	    // 调用移除方法即可
	    removeNode(res);
	    
	    return res;
	  }

}


/**
 * 双向链表
 * @author
 *
 */
class Node {
	// 下一个节点
	Node next;
	// 上一个节点
	Node pre;
	// 键
	int key;
	// 值
	int val;
	public Node() {
	}
	public Node(int val) {
		this.val = val;
	}
	/**get/set方法**/
}

14. 怎么理解高内聚和低耦合

https://zhidao.baidu.com/question/369384616486815764.html

https://blog.csdn.net/villainy13579/article/details/93507954?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

15. 输出所有1到999中不包含5的整数

private void print5() {
    for (int i = 0; i <= 9; i ++) {
        if (i == 5) {
            continue;
        }
        for (int j = 0; j <= 9; j ++) {
            if (j == 5) {
                continue;
            }
            for (int k = 0; k <= 9; k ++) {
                // 先排除0
                if (i == 0 && j == 0 && k == 0) {
                    continue;
                }
                if (k == 5) {
                    continue;
                }
                // 打印剩余数字
                System.out.println(i * 100 + j * 10 + k);
            }
        }
    }

}

16. 申请到一个C类地址,若要分为8个子网,其子网掩码为

将一个C类IP网络分为8个子网,其子网掩码为255.255.255.224

https://zhidao.baidu.com/question/142371759.html

17. 若用一个一维数组表示一个深度为5,节点个数为10的二叉树,数组长度最少为多少

31

18. 从500万个数中选出最大的30个数,时间复杂度最低的是:

1. 快速排序
2. 选择排序                                          
3. 归并排序
4. 堆排序

自己的答案:4

19. 下列聚合函数中不忽略null的是

1. SUM(列名)
2. MAX(列名)
3. COUNT(* )
4. AVG(列名)

count(*)是不忽略的
在聚合函数中遇到空值时,除了COUNT(*)外,都跳过空值而去处理非空值。 
比如count(列名)也会忽略空值

20. 输入arr, 找出其中最小的k个数

// 使用api中的优先队列作为堆
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k <= 0 || arr == null || arr.length == 0) {
            return new int[0];
        }

        // 默认最小堆 可以传参使其变为最大堆
        // 这题如果使用最小堆的话,需要建一个数量为arr.length的堆 而最大堆只需要建立一个k大小的堆
        PriorityQueue<Integer> heap = new PriorityQueue<Integer>((a, b) -> b - a);

        for (int i = 0; i < arr.length; i ++) {
            if (i < k) {
                heap.add(arr[i]);
            } else {
                // 比较堆顶元素和数组元素的大小
                if (arr[i] < heap.peek()) {
                    heap.poll();
                    heap.add(arr[i]);
                }
            }
        }

        // 最后将k个元素加入结果集即可
        int[] res = new int[k];
        for (int i = 0; i < k; i ++) {
            res[i] = heap.poll();
        }

        return res;
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值