时间

时间处理重点是格式统一,老项目常见 Date + SimpleDateFormat,新项目优先 java.time

1
2
3
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date start = sdf.parse(startTime);
String text = sdf.format(new Date());

高频操作:

1、计算两个日期之间的天数

2、生成指定日期时间

3、取当前时间、前一天、前一月、前一年

4、在旧 Date 体系和 LocalDateTime 之间切换

常见模板:

1
2
3
4
5
6
7
8
9
10
long days = (date.getTime() - nowDate.getTime()) / (24 * 60 * 60 * 1000);

LocalDateTime now = LocalDateTime.now();
LocalDateTime yesterday = now.minusDays(1);
LocalDateTime lastMonth = now.minusMonths(1);
LocalDateTime lastYear = now.minusYears(1);

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String text = LocalDateTime.now().format(dtf);
LocalDateTime time = LocalDateTime.parse("2020-11-04 00:00:00", dtf);

Date 和新时间 API 互转也很常见:

1
2
3
Date date = new Date();
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
Date newDate = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());

字符串、时间戳、LocalDate 互转也经常写:

1
2
3
4
5
6
7
8
9
10
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String strTime = "2020-11-04 00:00:00";
LocalDateTime parsed = LocalDateTime.parse(strTime, dtf);
Date parsedDate = Date.from(parsed.atZone(ZoneId.systemDefault()).toInstant());

long milli = 1604458818000L;
LocalDateTime milliTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(milli), ZoneId.systemDefault());

LocalDate localDate = LocalDate.now();
Date localDateToDate = Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());

老系统如果还在用 Calendar,至少要能看懂这类代码:

1
2
3
4
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DATE, -1);
Date yesterdayDate = calendar.getTime();

队列

按场景选数据结构,不要只背接口。

常见:

1、基于数组的普通队列

2、基于链表的链式队列

3、循环队列

4、双端队列

5、优先队列

核心点:

1、数组队列实现简单,但可能发生数据迁移

2、链式队列入队出队简单,但有额外节点开销

3、循环队列用取模解决尾指针回绕问题

4、双端队列两端都能入队出队

5、优先队列默认不是 FIFO,而是按优先级出队

JDK 里常用实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1);
queue.offer(2);
Integer first = queue.poll();

Deque<Integer> deque = new ArrayDeque<>();
deque.addFirst(1);
deque.addLast(2);

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(3);
priorityQueue.offer(1);
priorityQueue.offer(2);
Integer min = priorityQueue.poll();

Filter、Listener、Interceptor 的区别

三者分层不同:

1、Filter 更偏 Servlet 规范层,适合做统一编码、鉴权、日志等横切逻辑。

2、Listener 更偏事件监听,适合感知应用、Session 等生命周期事件。

3、Interceptor 更偏 Spring MVC 层,适合做接口级拦截与上下文处理。

生命周期和顺序也要记住:

1、web.xml 加载顺序通常是 context-param -> listener -> filter -> servlet

2、Filter 需要实现 initdoFilterdestroy

3、Interceptor 常看 preHandlepostHandleafterCompletion

常用 Filter 写法:

1
2
3
4
5
6
7
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
}

如果还是老 Servlet 项目,web.xml 里通常这样配:

1
2
3
4
5
6
7
8
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.demo.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Interceptor 代码一般至少能写出这个骨架:

1
2
3
4
5
6
7
public class UserRoleAuthorizationInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
}

Listener 也常见于启动初始化和 Session 生命周期统计:

1
2
3
4
5
6
7
8
9
public class DemoServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}

对象传参与引用语义

Java 只有值传递。对象参数传的是引用副本,不是引用本身。

高频误区:

1、值传递和引用传递的边界

2、集合 clear 后底层内存是否会马上释放

3、List 拷贝到底是浅拷贝还是深拷贝

4、交集、并集、差集这类常见集合操作怎么写更稳妥

Web

1、请求转发和重定向的区别

2、Cookie 和 Session 的配合方式

3、数组和集合打印结果的差异

4、finallyreturn 对返回值的影响

1、转发还是一次请求,适合服务端内部跳转和带上原始 request

2、重定向会让浏览器重新发请求,适合登录后跳首页、避免表单重复提交

1
2
request.getRequestDispatcher("/index.jsp").forward(request, response);
response.sendRedirect("/login");

Cookie 常见写法:

1
2
3
4
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
cookie.setMaxAge(7 * 24 * 60 * 60);
response.addCookie(cookie);

Cookie 读取通常也会顺手封个工具方法:

1
2
3
4
5
6
7
8
9
10
11
12
public static String getCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return null;
}
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
return cookie.getValue();
}
}
return null;
}

登录态一般是 Session 存用户信息,Cookie 存 token 或 session id。

1
2
3
session.setAttribute("loginUser", user);
Object loginUser = session.getAttribute("loginUser");
session.invalidate();

Interceptor 一般这样注册:

1
2
3
4
5
6
7
8
9
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/static/**");
}
}

Session 负责服务端状态,Cookie 负责浏览器侧标识和持久化;拦截器负责把未登录请求拦在 Controller 之前。

零散

日期

1
2
3
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date start = sdf.parse(startTime);
String nowText = sdf.format(new Date());

数组打印

数组不要直接 println

1
2
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.deepToString(arr2));

DTO 转换

字段接近时可以临时这么转:

1
List<UserDTO> userDTOs = JSON.parseArray(JSON.toJSONString(users), UserDTO.class);

finally

trycatch 里只要准备 returnfinally 一定会先执行;如果 finally 里也写了 return,最终返回的就是 finally 里的值。