# 易有料面试题

# 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、第二题

文件转存功能

a
/**
 * 文件转存功能
 */
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 就可以。

更新于