java中如何能避免过长的switch-case分支语句?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中如何能避免过长的switch-case分支语句?相关的知识,希望对你有一定的参考价值。

要编写一个网络程序,socket连接。客户端传上来的都是一些状态码,比如100代表登录,101代表注册等等。这样的状态码大约有800多个

在服务器端,接收到不同的状态码,就要调用不同对象的不同方法,比如,登录就调用user.login(),注册就调用user.register(),等等.

这样,我使用switch-cash语句的话,这个类至少得2000多行。分支太多的话,效率会很低。

我考虑过使用配置文件加反射来实现。结果大量的反射处理同样把效率降得很低。

这种状况应该怎么处理呢?哪位大侠知道?
二楼的能不能详细一点,呵呵。想法不错,可是怎么实现呢,我读到末端,得到功能字符串,还是要使用反射执行啊?

java中使用if elseif 结构不会降低效率。
sun官方说明,java中的if是经过效率优化的。
反射的效率是if语句的1/10。(就是说反射是很慢的)
之前做电信项目的时候我就遇到过。

使用if语句几百个分支的效率是每秒几万次。效率非常高。
(当然if中是有逻辑的。)

如果你确实有800个的话。
有一个提高效率的方法。
就是把if分组
if(组一)

if()...

else if(组二)

if()...

每组对应一个类。
类中都实现do(int status)方法。
把状态的if else都放到每个类中的do方法中写。
这样代码清晰。而且由于分了组
每组100个左右,这样效率就更高了。
参考技术A 大约有800多个,就是分组,不要担心效率 参考技术B 我觉得的话应该构建一个树,有1个根结点,10个子节点(表示0-9),每个子结点再分10个子节点,再分..最末端的节点一共构成800多个,每个都存着你需要的功能
然后 读取状态码的时候从第1位读取,按照读出的数字(0-9),分支到下一个子节点,然后读取第二个数字,再分支,一直下去..

这样的话 一个4位的状态码只用执行4次switch,每个switch中判断10个case.而switch-case只写一次,剩下就是递归..不知这样效率如何.
参考技术C 貌似没啥好办法...按数字范围再分分块吧。

167Java利用可重入锁避免并发下出现错误数据,并且避免死锁以及等待锁的时间过长

注意

本文只讲解使用可重入锁解决问题的方法,其他方案放在文末,也不考虑 select for update 的方案

1.场景

我以医院的病房管理系统为例来说明可重入锁。

先放数据库的表结构:

-- 房间表
CREATE TABLE IF NOT EXISTS public.room
(
    id bigint NOT NULL,   -- 主键
    room_no character varying(10) COLLATE pg_catalog."default", -- 房间号
    remark character varying(50) COLLATE pg_catalog."default",  -- 备注
    sort_no integer,   -- 排序
    CONSTRAINT yqgl_room_pkey PRIMARY KEY (id)
)


-- 病床表
CREATE TABLE IF NOT EXISTS public.t_bed
(
    id bigint NOT NULL,         -- 主键
    room_id bigint NOT NULL,    -- 房间ID
    user_id bigint NOT NULL,    -- 病人ID
    bed_no character varying(50) COLLATE pg_catalog."default",  -- 床的编号
    update_time timestamp without time zone NOT NULL,           -- 更新时间
    CONSTRAINT yqgl_bed_pkey PRIMARY KEY (id)
)

病床表里的数据是管理员提前按照房间ID和病床编号录入的。对于无人占用的病床记录,字段user_id设置成0。

在医院里每个病房的床位是有限的,护士需要在系统中选择有空床位的病房,来分配给新入院的病人入住。当系统分配完房间后还需要一系列后续操作,比如打印单据交给病人,或者给病人发送提示短信。医院有多名护士多台电脑来同时操作系统分配房间。这个时候并发条件下如果没有锁就有可能出现以下问题:

  1. 护士A发现101病房空着,点击按钮分配给病人A。
  2. 于此同时护士B也发现101病房空着,点击按钮分配给病人B。
  3. 服务器同时接到了两个请求并分配了两个线程A、B来同时处理两个请求。
  4. 线程A和B同时开启数据库事务。
  5. 线程A查询数据库发现101房间还有一个空床位,病人A当前没有占用床位。
  6. 同时线程B查询数据库也发现101房间还有一个空床位,病人B当前没有占用床位。
  7. 线程A更新数据库,把最后一个床位ID和病人A的ID关联。并执行后续操作。
  8. 线程B更新数据库,把最后一个床位ID和病人B的ID关联。并执行后续操作。
  9. 线程A提交数据库事务,返回给护士A成功提示。
  10. 线程B提交数据库事务,返回给护士B成功提示。

显然护士A的操作被护士B覆盖了。但是系统给出了有误导性的提示。更为严重的是,系统的后续操作会带来更多麻烦,比如打印出了错误的住院单,给病人发送了错误的提示短信。

2.必须要搞清楚锁的范围

首先说明,锁的范围要覆盖数据库事务的范围,即先加锁、再开启事务、然后提交或者回滚事务、最后解锁。如果把锁放在事务中(即先开启事务、再加锁、解锁、最后提交或者回滚事务),是没有效果的。为了弄清楚原因,我们先看数据库的四种隔离机制:

  1. read uncommited(读未提交)
  2. read commited (读已提交)
  3. repeatable read (可重复读)
  4. Serializable (串行化)

由于性能原因,几乎没有人使用 Serializable。为了数据正确性也几乎没有人使用 read uncommited(读未提交)。常用的是 read commited 和 repeatale read 。下面我们分开讨论:

2.1 read commited

如果我们把加锁的代码放到事务中,因为对数据库的更改未提交,很可能导致两个线程一前一后读取了旧版本的数据并做了修改,然后同时提交事务后,导致其中一个的数据被覆盖。

2.1 repeatable read

虽然可重复读能够让事务中的数据不受其他事务的影响,但是在本例中并不能解决问题。下面我们来演示以下:

  1. 线程A开启事务、获取锁、查询数据库检查确定可以更新、更新数据库、解锁,在提交前操作系统调度线程。CPU切换到线程B。
  2. 线程B开启事务、获取锁、查询数据库检查确定可以更新、更新数据库、解锁。因为线程A没有提交数据库,所以这里数据库读取的是旧快照。
  3. 线程AB提交事务,数据被其中一方覆盖。

3.解决方案

存放锁对象的类 LockUtils

import java.util.concurrent.locks.ReentrantLock;

public class LockUtils 
    public static final ReentrantLock BED_LOCK = new ReentrantLock();

为了保证锁覆盖事务,我把锁放到 Controller 层:

@RestController
@RequestMapping("/isolate/userInfo")
public class RoomController 
   /**
     * 分配房间
     */
	@PutMapping("/distributeRoom")
	public R<Void> distributeRoom(@RequestBody Map<String, Object> param) 
		// 检查参数的代码,已忽略
		
 		R r = R.error();
        // 使用重入锁,避免同一个房间被分配给多个人。
        try 
        	// 这里是最多等待十秒钟就放弃,提示系统用户稍后重试。
            boolean flag = LockUtils.BED_LOCK.tryLock(10L * 1000L, TimeUnit.MILLISECONDS);
            if (flag) 
                r = roomService.distributeRoom(patientId, roomId);
             else 
                r = R.error("系统繁忙,请稍后重试。");
            
         catch (InterruptedException e) 
            e.printStackTrace();
         finally 
            // 确保锁被释放
            LockUtils.YQGL_USER_INFO_LOCK.unlock();
        
        return r;
	

业务类 RoomService,注意这里的方法声明了事务,确保锁的范围覆盖事务的范围。里面的业务使用伪代码编写。

@Service
public class RoomServiceImpl implements RoomService 
	@Override
    @Transactional(rollbackFor = Exception.class)
    public R<Void> distributeRoom(Long patientId, Long roomId) 
    	1. 查询数据库,判断房间有没有床位,判断病人当前有没有床位。
    	2. 如果房间没有床位或者病人已经占用了其他床位,终止执行,给出用户提示。
    	3. 如果房间有床位并且病人没有床位,就更新数据库,把床位分配给病人。
    	4. 执行其他相关操作。
	

以上是关于java中如何能避免过长的switch-case分支语句?的主要内容,如果未能解决你的问题,请参考以下文章

使用大型数据库时,如何避免 UITableView 中的自定义单元格加载时间过长

167Java利用可重入锁避免并发下出现错误数据,并且避免死锁以及等待锁的时间过长

167Java利用可重入锁避免并发下出现错误数据,并且避免死锁以及等待锁的时间过长

java使用jxl操作一百万数据到excel中,分sheet写入(一个sheet写五万)。如何避免内存溢出?

如果 Android 中的 HttpGet 操作持续时间过长,如何避免出现错误 10053 (WSAECONNABORTED)?

使用函数指针和多态代替冗长的if-else或者switch-case