Java 笔试选择题知识点记录【yy-0904】
线程 Thread.sleep(5000),5 秒后的状态变成?
看这张图应该是 Runnable。
BigDecimal(0.1) 会丢失精度吗?
会丢失精度。
正确做法是:new BigDecimal("0.1")。
位图
BitMap算法的核心思想是用bit数组来记录0-1两种状态,然后再将具体数据映射到这个比特数组的具体位置,这个比特位设置成0表示数据不存在,设置成1表示数据存在。
IO 密集型的程序中,线程池的 workQueue 用什么比较好?
CPU 核数为 8 核,堆内存为 4 G,线程池 coreThread 20,maxThread 40,单次执行平均时间 1s,不超过 3s;平均 20 OPS,不超过 60 OPS。
以下是一些常见的workQueue类型以及它们在不同情况下的适用性:
- LinkedBlockingQueue(链表阻塞队列):适用于大多数IO密集型应用,特别是当你不确定应用的负载和并发性能需求时。它可以无限制地存储任务,因此可以在高负载情况下提供一定的缓冲。但需要注意,如果任务积压过多,可能会导致内存问题。
- ArrayBlockingQueue(数组阻塞队列):适用于有限资源的情况,它可以限制队列的最大容量,防止无限制的任务积压。需要根据系统资源和应用需求来选择合适的容量。
- PriorityBlockingQueue(优先级阻塞队列):当你需要按照任务的优先级来执行时,可以选择这个队列。可以确保高优先级的任务优先执行。
- SynchronousQueue(同步队列):适用于需要严格控制线程数量的场景,它不会缓冲任何任务,只有当有线程来获取任务时才会提交任务。可以用于限制并发度,但需要慎重使用,因为可能导致线程频繁创建和销毁。
- 无界队列(Unbounded Queue):如果你不想限制队列的容量,并且希望尽可能多地缓冲任务,可以选择一些无界队列,如LinkedBlockingQueue。
选择适合你应用的workQueue类型取决于多个因素,包括应用的负载、并发性能需求、系统资源、内存限制等。通常,可以开始使用LinkedBlockingQueue,然后根据实际性能和资源消耗进行微调。监控工具和性能测试是评估和优化线程池性能的有用工具。
@ import 注解
Spring Framework中的@Import注解是用于导入其他配置类(Configuration class)或其他普通类的注解。它允许您在一个配置类中引入另一个配置类,以便共享其定义的bean或配置信息。@Import注解有多种用途,可以根据需要使用不同的方式来导入类。
下面是@Import注解的一些常见用法:
导入配置类:
@Configuration
@Import({DatabaseConfig.class, SecurityConfig.class})
public class AppConfig {
// ...
}
在上面的示例中,AppConfig配置类使用@Import注解导入了DatabaseConfig和SecurityConfig配置类,以便可以访问它们定义的bean或配置。
导入普通类:
@Configuration
@Import(ExternalService.class)
public class AppConfig {
// ...
}
这个示例中,AppConfig配置类使用@Import注解导入了一个普通类ExternalService,Spring将会扫描ExternalService类,将其作为一个bean注册到应用上下文中。
导入条件化的配置类:
@Configuration
@ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")
@Import(FeatureConfig.class)
public class AppConfig {
// ...
}
在这个示例中,@Import注解与Spring的条件化注解@ConditionalOnProperty一起使用,以便只有当myapp.feature.enabled属性值为true时,才会导入FeatureConfig配置类。
总之,@Import注解是一个强大的工具,可以用于在Spring应用程序中组织和管理配置类和bean定义。它允许您将不同的组件集成到一个配置类中,以便更好地组织和管理应用程序的配置和功能。
SpringBoot 启动后执行一些特定的代码
在Spring Boot应用程序启动后执行特定的代码通常需要使用Spring Boot的生命周期回调机制或事件监听器来实现。以下是两种常见的方法:
1. ApplicationRunner 或 CommandLineRunner
如果你需要在 SpringApplication 启动后运行一些特定的代码,你可以实现 ApplicationRunner 或 CommandLineRunner 接口。这两个接口的工作方式相同,并提供一个单一的运行方法,该方法在 SpringApplication.run(…) 完成之前被调用。
还可以实现ApplicationRunner接口,它提供了更丰富的ApplicationArguments参数,使您可以更灵活地处理命令行参数。
2. 事件监听器:
Spring框架提供了一系列事件,您可以编写自定义的事件监听器来监听这些事件并在应用程序启动后执行相应的代码。通常,您需要使用@EventListener注解将监听器方法与特定的事件关联起来。
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。 当直接创建对象的代价比较大时,则采用这种模式。
NIO 模型
NIO(New I/O,也称为Non-blocking I/O)模型是一种用于处理输入和输出的编程模型,与传统的阻塞式I/O模型有所不同。NIO提供了一种更为灵活和高效的方式来管理I/O操作,特别适用于需要处理大量并发连接的网络应用程序,如Web服务器或聊天服务器。NIO的核心思想是非阻塞式I/O,允许一个单独的线程处理多个连接,而无需为每个连接创建一个独立的线程。
NIO模型的主要组成部分包括以下几个概念:
- 通道(Channel):通道是NIO模型中的基本概念,代表了与底层I/O资源(如文件、套接字)的连接。通道可以用于读取和写入数据,而且它们支持非阻塞操作。常见的通道类型包括SocketChannel(用于网络套接字通信)、FileChannel(用于文件I/O操作)等。
- 缓冲区(Buffer):缓冲区是NIO中用于存储数据的容器。数据在通道和缓冲区之间进行传输。通常,您会从通道读取数据到缓冲区,或者将数据从缓冲区写入通道。缓冲区提供了灵活的API,支持读取、写入、翻转、清除等操作。
- 选择器(Selector):选择器是NIO模型的核心组件之一,用于实现非阻塞I/O。选择器允许一个线程同时管理多个通道,监听它们的事件(如可读、可写等)。这允许单个线程有效地处理多个连接,而不需要为每个连接创建一个线程,从而提高了并发性能。
NIO模型的工作流程通常如下:
- 创建一个选择器(Selector)并将一个或多个通道注册到选择器中。
- 不断轮询选择器,检查已注册的通道是否有事件发生。这个过程通常是一个无限循环,但由于是非阻塞的,所以不会阻塞整个程序。
- 如果有事件发生,处理相应的通道,执行读取或写入操作。
- 如果没有事件发生,继续轮询。
总的来说,NIO模型允许在单个线程内有效地管理多个连接,提高了应用程序的性能和扩展性。它特别适用于需要处理大量并发连接的网络应用程序,因为它避免了为每个连接创建一个线程的开销。但NIO模型也更为复杂,需要开发人员处理更多的细节,例如事件处理和缓冲区管理。因此,使用NIO需要更多的编程技巧和经验。
XSS 攻击 & CSRF 攻击
跨站脚本攻击(Cross-Site Scripting,XSS)是一种常见的网络安全漏洞,攻击者通过注入恶意脚本代码来利用应用程序中的信任关系,从而在用户的浏览器上执行恶意操作。
跨站请求伪造(CSRF)是一种网络安全攻击,其目标是欺骗已认证用户执行未经他们明确授权的操作。攻击者利用用户的身份来发送恶意请求,这些请求伪装成用户的合法请求,从而导致潜在的危害。XSS (Cross-Site Scripting) 攻击和 CSRF (Cross-Site Request Forgery) 攻击是两种不同的Web安全威胁,它们利用了Web应用程序的漏洞,但它们的工作方式和目标有所不同。以下是它们之间的主要区别:
- 攻击目标:XSS 攻击:XSS 攻击的目标是窃取用户的信息或执行恶意脚本,通常是在受害者的浏览器上运行。攻击者通过在受害者浏览器中注入恶意JavaScript代码,利用网站的信任来执行该代码。CSRF 攻击:CSRF 攻击的目标是伪造用户的身份,以执行未经授权的操作。攻击者试图迫使已认证的用户在不知情的情况下执行操作,例如更改密码、发送电子邮件或执行其他敏感操作。
- 攻击方式:XSS 攻击:XSS 攻击利用了Web应用程序中的不足,将恶意脚本嵌入到网页中,然后该脚本在用户浏览器中执行。这可以分为三种类型:存储型XSS、反射型XSS和DOM型XSS。CSRF 攻击:CSRF 攻击利用了用户在已经通过身份验证的会话中执行的操作。攻击者会诱使用户访问一个包含恶意请求的网页,该请求将在用户的会话下执行,因此服务器将以受害者的身份执行操作。
- 攻击的结果:XSS 攻击:XSS 攻击的结果通常是窃取用户的Cookie、会话信息、个人数据或执行潜在的恶意操作,如修改页面内容、重定向用户到恶意站点等。CSRF 攻击:CSRF 攻击的结果是执行未经授权的操作,这可能会导致用户账户的被控制或数据的不当修改。
- 防御方法:防御XSS攻击通常需要输入验证和输出转义,以确保不允许恶意脚本进入应用程序,并且任何从应用程序输出的数据都会进行适当的编码。防御CSRF攻击通常需要使用CSRF令牌,以验证请求是否来自合法的用户操作。此外,实施适当的同源策略和CORS (跨源资源共享) 可以帮助减轻CSRF攻击的风险。
总之,XSS攻击和CSRF攻击是两种不同类型的Web安全威胁,它们分别关注用户的浏览器和服务器端的漏洞,因此需要不同的防御措施来保护Web应用程序和用户的数据。
Cookie 的安全性
保证Cookie的安全性对于Web应用程序的安全至关重要,因为Cookie通常用于存储用户身份验证和会话信息。以下是一些保护Cookie安全性的最佳实践:
- 使用HTTPS:将网站配置为使用HTTPS协议,以加密传输的数据。这可以防止中间人攻击和窃听Cookie数据。在使用HTTPS时,浏览器会将Cookie标记为Secure,以确保它们仅在加密连接中传输。
- 设置HttpOnly属性:将Cookie标记为HttpOnly,以防止通过JavaScript访问Cookie。这可以防止跨站点脚本攻击(XSS攻击)从JavaScript中访问敏感Cookie数据。
- Set-Cookie: mycookie=myvalue; HttpOnly
- 设置SameSite属性:使用SameSite属性限制Cookie的跨站点传递。这有助于防止CSRF(跨站请求伪造)攻击。可以设置为Strict、Lax或None,具体取决于应用程序的需求。
- Set-Cookie: mycookie=myvalue; SameSite=Strict
- 限制Cookie的域和路径:通过设置Cookie的域和路径,可以限制哪些页面可以访问Cookie。确保仅允许需要Cookie的页面访问它们,以减少潜在的安全风险。
- Set-Cookie: mycookie=myvalue; Path=/myapp; Domain=example.com
- 定期更新和过期Cookie:对于敏感信息,可以设置Cookie的过期时间,确保它们在一段时间后失效。还可以定期更新Cookie,以减少持久性攻击的风险。
- 使用安全的会话管理:对于身份验证和会话管理,使用安全的方法,例如JWT(JSON Web Tokens)或OAuth,以减少Cookie相关的安全风险。
- 避免在Cookie中存储敏感信息:尽量不要在Cookie中存储敏感信息,特别是未加密的敏感数据。应该将这些信息存储在服务器端,并在需要时使用会话标识符(如Session ID)进行引用。
- 监控和记录安全事件:实施安全监控和日志记录,以及实时响应安全事件,以便及时检测和应对潜在的Cookie安全问题。
- 更新Cookie策略:定期审查和更新Cookie策略,以确保它们与最新的安全最佳实践和法规要求保持一致。
总之,保证Cookie的安全性需要采取多层次的防御措施,包括使用HTTPS、配置Cookie属性、限制访问范围等。此外,定期审查和更新Cookie策略以适应不断变化的威胁也是至关重要的。最重要的是,保持关注安全社区中的最新趋势和最佳实践,以确保应用程序的Cookie保持安全。
笔试
第 1 题:Dijkstra
import java.util.Arrays; import java.util.PriorityQueue; public class Solution { public static void main(String[] args) { Solution s = new Solution(); int[][] flights = {{0,1,100}, {1,2,100}, {0,2,500}}; int src = 1; int n = 3; int[] res = s.findAllCheapestPrice(n, flights, src); for (int i = 0; i < n; i++) { System.out.println(res[i]); } } /** * 现在给定所有的城市和航班,以及出发城市src,你的任务是找到从 scr城市出发到其他所有城市最便宜的机票价格列表。 假设两个城市之间机票价格不会超过Integer.MAX_V * @param n int整型 * @param flights int整型二维数组 * @param src int整型 * @return int整型一维数组 */ public int[] findAllCheapestPrice (int n, int[][] flights, int src) { // write code here long[] ans = new long[n]; Arrays.fill(ans, Long.MAX_VALUE); ans[src] = 0; // 出发节点 long[][] dis = new long[n][n]; for (int i = 0; i < n; i++) { Arrays.fill(dis[i], Long.MAX_VALUE / 2); } for (int[] flight: flights) { int u = flight[0]; int v = flight[1]; int w = flight[2]; dis[u][v] = w; } PriorityQueue<long[]> minHeap = new PriorityQueue<>((a, b) -> { return (int) (a[2] - b[2]); }); // [u, v, w] for (int j = 0; j < n; j++) { if (dis[src][j] < Long.MAX_VALUE / 2) { minHeap.offer(new long[]{src, j, dis[src][j]}); } } while (!minHeap.isEmpty()) { long[] item = minHeap.poll(); int u = (int) item[0]; int v = (int) item[1]; long w = item[2]; if (ans[u] + w > ans[v]) continue; ans[v] = ans[u] + w; // 松弛操作 // 找到所有 v 的邻接的节点 for (int i = 0; i < n; i++) { if (dis[v][i] < Long.MAX_VALUE / 2) { minHeap.offer(new long[]{v, i, dis[v][i]}); } } } int[] res = new int[n]; for (int i = 0; i < n; i++) { if (ans[i] == Long.MAX_VALUE) { res[i] = -1; } else { res[i] = (int) ans[i]; } } return res; } }
第 2 题:动态规划
public class Solution { public static void main(String[] args) { int[][] matrix = {{3,2,1}, {4,3,6}, {1,1,7}}; Solution s = new Solution(); int ans = s.maxValue(matrix); System.out.println(ans); } /** * * @param grid int整型二维数组 * @return int整型 */ public int maxValue (int[][] grid) { // write code here int row = grid.length; int col = grid[0].length; for (int i = 1; i < row; i++) { grid[i][0] += grid[i - 1][0]; } for (int j = 1; j < col; j++) { grid[0][j] += grid[0][j - 1]; } for (int i = 1; i < row; i++) { for (int j = 1; j < col; j++) { grid[i][j] += Math.max(grid[i - 1][j], grid[i][j - 1]); } } return grid[row - 1][col - 1]; } }
第 3 题:并查集+优先队列
import java.util.PriorityQueue; public class Solution { public static void main(String[] args) { int[][] matrix = {{1,2,5}, {1,3,6}, {2,3,1}}; Solution s = new Solution(); int ans = s.minimumCost(3, matrix); System.out.println(ans); } /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 星球间的最短通路 * @param n int整型 星球数量 * @param connections int整型二维数组 星际门连接 * @return int整型 */ public int minimumCost (int n, int[][] connections) { // write code here PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) ->{ return a[2] - b[2]; }); // [u, v, w] UnionFind uf = new UnionFind(n); for (int[] con: connections) { int x = con[0] - 1; int y = con[1] - 1; int m = con[2]; minHeap.offer(new int[]{x, y, m}); } int ans = 0; while (!minHeap.isEmpty()) { int[] item = minHeap.poll(); int x = item[0]; int y = item[1]; if (uf.isCon(x, y)) continue; uf.union(x, y); ans += item[2]; } if (uf.size > 1) return -1; return ans; } } class UnionFind { int[] parents; int size; // 连通图的个数 public UnionFind(int n) { this.parents = new int[n]; for (int i = 0; i < n; i++) { parents[i] = i; } this.size = n; } public void union(int p, int q) { int rp = find(p); int rq = find(q); if (rp == rq) return; parents[rp] = rq; size--; } public int find(int p) { if (parents[p] != p) { parents[p] = find(parents[p]); } return parents[p]; } public boolean isCon(int p, int q) { return find(p) == find(q); } }
Java 后端笔试经验