# 易有料面试题
# 1、第一题
## 第一题
用 Java 的集合,实现以下接口,不允许使用任何中间件和第三方 jar 包。
对于进阶问题可以自己编写 service 来调用此 Service,也可以写实现思路。
import java.util.Collection; | |
/** | |
* 房间服务,这个房间服务主要功能为管理房间以及房间内的成员(String 类型)。 | |
* 如果房间不存在则创建这个房间 | |
* | |
* 保证并 ** 况下的房间用户数据一致性 | |
*/ | |
public interface RoomService { | |
/** | |
* 加入房间 | |
* @param userId 用户 Id | |
* @param roomId 房间 Id | |
* @throws RuntimeException | |
*/ | |
void joinRoom(String userId, String roomId) throws RuntimeException; | |
/** | |
* 离开房间 | |
* @param userId 用户 Id | |
* @param roomId 房间 Id | |
* @throws RuntimeException | |
*/ | |
void leaveRoom(String userId, String roomId) throws RuntimeException; | |
/** | |
* 获取房间成员列表 | |
* @param roomId 房间 Id | |
* @return 房间成员列表 | |
* @throws RuntimeException | |
*/ | |
Collection<String> queryUser(String roomId) throws RuntimeException; | |
} |
/** | |
* 未考虑性能问题 因为不能用除 util 外的包 所以只能用不安全的 hashmap 如可能 可以采用 concurrenthashmap | |
* 实际上也可以用 hashtable 或者使用 java.util.Collections.synchronizedMap 方法,将已有的 HashMap 对象包装为线程安全的。 | |
*/ | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.HashMap; | |
public class RoomServiceImpl implements RoomService { | |
private volatile HashMap<String, Room> roomRefUserMap = new HashMap<>(); | |
final Object join = new Object(); | |
final Object leave = new Object(); | |
class Room { | |
private String roomId; | |
private Collection<String> userList; | |
public String getRoomId() { | |
return roomId; | |
} | |
public void setRoomId(String roomId) { | |
this.roomId = roomId; | |
} | |
public Collection<String> getUserList() { | |
return userList; | |
} | |
public void setUserList(Collection<String> userList) { | |
this.userList = userList; | |
} | |
} | |
@Override | |
public void joinRoom(String userId, String roomId) throws RuntimeException { | |
// 全加锁保证同时加入房间不会出问题 但是不推荐 | |
synchronized (join) { | |
Room room = roomRefUserMap.get(roomId); | |
if (room == null) { | |
room = new Room(); | |
room.setRoomId(roomId); | |
Collection<String> users = new ArrayList<>(); | |
room.setUserList(users); | |
roomRefUserMap.put(roomId, room); | |
} else { | |
Collection<String> users = room.getUserList(); | |
if (users.isEmpty()) { | |
// 没有人 ,空房间 | |
room.setUserList(Collections.singletonList(userId)); | |
} else { | |
users.add(userId); | |
room.setUserList(users); | |
} | |
roomRefUserMap.put(roomId,room); | |
} | |
} | |
} | |
@Override | |
public void leaveRoom(String userId, String roomId) throws RuntimeException { | |
synchronized (leave) { | |
Room room = roomRefUserMap.get(roomId); | |
Collection<String> users = room.getUserList(); | |
users.remove(userId); | |
if (users.isEmpty()) { | |
// 销毁房间 | |
roomRefUserMap.remove(roomId); | |
} else { | |
// 重载人 | |
room.setUserList(users); | |
roomRefUserMap.put(roomId, room); | |
} | |
} | |
} | |
@Override | |
public Collection<String> queryUser(String roomId) throws RuntimeException { | |
Room room = roomRefUserMap.get(roomId); | |
Collection<String> userList = room.getUserList(); | |
return userList; | |
} | |
} |
# 进阶
现在基于此房间模型编写网络接入层的代码(HTTP、TCP、WebSocket)。
由于网络的问题调用加入退出房间的请求到的先后顺序可能不一样,但是原始顺序是一样的,需要通过什么样的机制来保证功能正常(可以自定义请求体)?
开放题,可以编写上层代码,可以编写实现思路,不要求实现网络层接入代码。
说下思路:
加入退出请求达到的时间不一样,理解为出现我还没加入就退出的情况,但是不行,也就是必须是用户先加入了才能退出。
在用户加入后,前端的表现就已经加入了,但是后台代码可能还没走完,但是这个时候修改了又退出了。
退出的请求先到了服务,退出没问题,但是这个时候请求加入的请求到了,人又被加了进去。
问题复现。简单的实现方法就是给每个房间增加一个缓存的池子,刚退出房间的用户一段时间内没法加入。
# 2、第二题
文件转存功能
/** | |
* 文件转存功能 | |
*/ | |
public class FileUtil { | |
/** | |
* 将源资源地址上传到目标资源地址。相应的鉴权可以忽略,可以使用第三方 HTTP Client 来完成。 | |
* @param sourceUrl 源资源地址 (例如:腾讯云) | |
* @param targetUrl 目标资源地址 (例如:阿里云) | |
* @throws RuntimeException | |
*/ | |
public static void TransUpload(String sourceUrl,String targetUrl) throws RuntimeException{ | |
File file = new File(sourceUrl); | |
try { | |
postFile(targetUrl,new HashMap<>(),file); | |
}catch (Exception e){ | |
System.out.println("上传失败"); | |
} | |
}; | |
public static String postFile(String url, Map<String, Object> param, File file) throws IOException { | |
String res; | |
CloseableHttpClient httpClient = HttpClients.createDefault(); | |
HttpPost httppost = new HttpPost(url); | |
httppost.setEntity(getMutipartEntry(param, file).build()); | |
CloseableHttpResponse response = httpClient.execute(httppost); | |
HttpEntity entity = response.getEntity(); | |
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { | |
res = EntityUtils.toString(entity, "UTF-8"); | |
response.close(); | |
} else { | |
res = EntityUtils.toString(entity, "UTF-8"); | |
response.close(); | |
throw new IllegalArgumentException(res); | |
} | |
return res; | |
} | |
private static MultipartEntityBuilder getMutipartEntry(Map<String, Object> param, File file) throws UnsupportedEncodingException { | |
if (file == null) { | |
throw new IllegalArgumentException("文件不能为空"); | |
} | |
MultipartEntityBuilder builder = MultipartEntityBuilder.create(); | |
builder.setCharset(StandardCharsets.UTF_8); | |
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);// 加上此行代码解决返回中文乱码问题 | |
builder.addBinaryBody("file", file);// 文件流 | |
for (Map.Entry<String, Object> e : param.entrySet()) { | |
builder.addTextBody(e.getKey(), e.getValue().toString());// 类似浏览器表单提交,对应 input 的 name 和 value | |
} | |
return builder; | |
} | |
} |
# 进阶
机器为 4C8G 的云服务器
如果请求量过大,如何保证接口的并发性以及代码的资源消耗,不能有 OOM 情况出现。(例如:1000 的并发转存)
如果保证并发并且不能有太大的服务器压力的话,可以用线程池。接口请求进来之后,将此任务异步到线程池中执行,线程池的参数就得根据机器的性能以及
业务的需求来设置了,阻塞队列设置成 1000 就可以。