Fork me on GitHub

ARTS04

Algorithms-Review-Tip-Share

Posted by Kaelzhang on September 9, 2018

Algorithm

322.零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

说明: 你可以认为每种硬币的数量是无限的。

解决方案:

public class Solution {
    public static int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            dp[i] = 0x7fff_ffee;
        }

        for ( int coin: coins ) {
            for(int i = coin; i <= amount; i++){
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }

        return dp[amount] == 0x7fff_ffee? -1: dp[amount];

    }
}

采用动态规划算法,步骤解释如下:

  1. 构建数量为amount的数组,数组的每个元素代表最小个数
  2. 将每个数组初始值设置为一个大数0xffff_ffee(第一个除外,dp[0]为0)
  3. 开始按照币种遍历
  4. 每个币种起始值为币值本身,进行步长为1的遍历
  5. 对遍历到的每个数组求dp[i]与dp[i-coin]+1的最小值,dp[i]可能为初始值或者其他币种所计算得到的值,dp[i-coin]+1是从i-coin到i的最小值。
  6. 如果dp[amount]最后为初始值则代表没有任何一种硬币组合能组成总金额,即-1。其余则返回计算后的值。

Review

Java API设计检查单,本文从包、类以及方法上个层面来阐述了java的api设计规范

术语 说明
(做) 动词 表示所需的设计
倾向 表示几种设计方案中的最佳选择
考虑 表示一个可能的设计改进
避免 表示一个设计缺陷
不要 表示一个设计错误

1. 包设计检查单

1.1. 通用

1.1.1. 倾向将API和实现放入单独的包中 1.1.2. 倾向将API放入高级包并将其实现到较低级别的包中 1.1.3. 考虑将大型API分解为多个包 1.1.4. 考虑将API和实现包放入单独的Java归档中 1.1.5. 避免(最小化)API中实现类的内部依赖性 1.1.6. 避免不必要的API碎片 1.1.7. 不要将公共实现类放在API包中 1.1.8. 不要在调用者和实现类之间创建依赖关系 1.1.9. 不要将不相关的API放在同一个包中 1.1.10. 不要将API和SPI放在同一个包中 1.1.11. 不要移动或重命名已发布的公共API的包

1.2. 命名

1.2.1. 使用公司的官方根命名空间来开始包名称 1.2.2. 在包名称的第二级使用稳定的产品或产品系列名称 1.2.3. 使用API的名称作为包名称的最后部分 1.2.4. 考虑通过在包名称中包含“internal”来标记仅实现包 1.2.5. 避免复合名称 1.2.6. 避免在包内使用相同名称的包和类 1.2.7. 避免在包名中使用“api” 1.2.8. 不要使用市场,项目,组织单位或地理位置名称 1.2.9. 不要在包名称中使用大写字符

1.3. 文档

1.3.1. 为每个包提供包概述(package.html) 1.3.2. 遵循标准的Javadoc规范 1.3.3. 首先简要介绍API的一句话摘要 1.3.4. 提供足够的详细信息以帮助确定是否以及如何使用API 1.3.5. 指示API的入口点(main类或方法) 1.3.6. 包括主要的,最基本的用例的示例代码 1.3.7. 包含指向开发人员指南的链接 1.3.8. 包括指向Cookbook的链接 1.3.9. 指明相关的API 1.3.10. 包括API版本号 1.3.11. 使用@deprecated标记指示已弃用的API版本 1.3.12. 考虑包含版权声明 1.3.13. 避免冗长的包概述 1.3.14. 不要将实现包包含在已发布的Javadoc中

2. 类型设计检查表

2.1. 通用

2.1.1. 确保每种类型都有一个明确定义的目的 2.1.2. 确保类型代表领域概念,而不是设计抽象 2.1.3. 限制类型的数量 2.1.4. 限制类型的大小 2.1.5. 在设计相关类型时遵循一致的设计模式 2.1.6. 支持多种公共类型的多个(私有)实现 2.1.7. 倾向类继承的接口,以表达行为中的简单通用性 2.1.8. 倾向接口上的抽象类,用于将API与实现分离[解释] 2.1.9. 倾向枚举类型的常量 2.1.10. 考虑泛型类型 2.1.11. 考虑在泛型类型参数上放置约束 2.1.12. 考虑使用接口来实现与多重继承类似的效果 2.1.13. 避免客户端扩展设计 2.1.14. 避免深度继承层次结构 2.1.15. 不要使用公开嵌套类型 2.1.16. 不要声明公开或受保护的字段 2.1.17. 不要将继承实现暴露给客户端

2.2. 命名

2.2.1. 使用名词或名词短语 2.2.2. 使用PascalCasing 2.2.3. 仅限首字母缩略词的第一个字母 2.2.4. 根据类型使用准确的名称 2.2.5. 为最常用的类型保留最短,最难忘的名称 2.2.6. 使用“Exception”一词结束作为所有异常的名称 2.2.7. 使用单数名词(Color,而非Colors)来命名枚举类型 2.2.8. 考虑更长的名字 2.2.9. 考虑使用基类的名称结尾的派生类的名称 2.2.10. 考虑使用单词“Abstract”开始抽象类的名称 2.2.11. 避免使用缩写 2.2.12. 避免使用通用名词 2.2.13. 避免同义词 2.2.14. 避免使用相关API中使用的类型名称 2.2.15. 不要仅使用不同的名称 2.2.16. 不要使用前缀 2.2.17. 不要将接口名称加上“I”前缀 2.2.18. 不要使用Java核心包中使用的类型名称

2.3. 类

2.3.1. 最小化对实现的依赖 2.3.2. 首先列出公开方法 2.3.3. 声明实现方法是私有的 2.3.4. 定义至少一个扩展公开抽象类的公开具体类 2.3.5. 为基本使用方案提供足够的默认值 2.3.6. 设计具有强不变量的类 2.3.7. 将无状态,访问器和更改器方法组合在一起 2.3.8. 保持更改器方法的数量最少 2.3.9. 考虑提供默认的无参数构造函数 2.3.10. 考虑重写equals(), hashCode()和toString() 2.3.11. 考虑实现Comparable 2.3.12. 考虑实现Serializable 2.3.13. 考虑让类可重入 2.3.14. 考虑将该类声明为final 2.3.15. 考虑通过不提供公开构造函数来防止类实例化 2.3.16. 考虑使用自定义类型将强大的前置条件强制为类不变量 2.3.17. 考虑设计不可变类 2.3.18. 避免静态类 2.3.19. 避免使用Cloneable 2.3.20. 不要将实例成员添加到静态类 2.3.21. 不要为客户端不应扩展的公开抽象类定义公开构造函数 2.3.22. 不需要大量初始化

2.4. 接口

2.4.1. 为每个公开接口提供至少一个实现类 2.4.2. 为每个公共接口提供至少一种消费方法 2.4.3. 不要将方法添加到已发布的公开Java接口 2.4.4. 不要使用标记接口 2.4.5. 不要将公共接口用作常量字段的容器

2.5. 枚举

2.5.1. 考虑为枚举类型指定零值(“无”或“未指定”等) 2.5.2. 避免使用只有一个值的枚举类型 2.5.3. 不要将枚举类型用于开放式值集合 2.5.4. 不要保留枚举值以备将来使用 2.5.5. 不要向已发布的枚举添加新值

2.6. 异常

2.6.1. 确保正确序列化自定义异常 2.6.2. 考虑为每种错误类型定义不同的异常类 2.6.3. 考虑为程序化访问提供额外信息 2.6.4. 避免深度异常层次结构 2.6.5. 不要从Exception和RuntimeException以外派生自定义异常 2.6.6. 不要直接从Throwable派生自定义异常 2.6.7. 不要在错误消息中包含敏感信息

2.7. 文档

2.7.1. 提供每种类型的类型概述 2.7.2. 遵循标准的Javadoc规范 2.7.3. 从类型的简短的,一句话摘要开始 2.7.4. 提供足够的详细信息以帮助确定是否以及如何使用该类型 2.7.5. 解释如何实例化类型 2.7.6. 提供代码示例以说明该类型的主要用例 2.7.7. 在开发者指南中包含指向相关部分的链接 2.7.8. 包括指南中相关部分的链接 2.7.9. 指出相关类型 2.7.10. 使用@deprecated标记指示不推荐使用的类型 2.7.11. 文档化类不变量 2.7.12. 避免冗长的类型概述 2.7.13. 不要为私有字段和方法生成Javadoc

3. 方法设计检查单

3.1. 通用

3.1.1. 确保每个方法只做一件事 3.1.2. 确保相关方法具有相同的粒度级别 3.1.3. 确保组合方法调用不需要样板代码 3.1.4. 使所有方法调用原子化 3.1.5. 设计受保护的方法与公开方法一样谨慎 3.1.6. 限制mutator方法的数量 3.1.7. 设计具有强不变量的mutator 3.1.8. 倾向在一组重载方法上使用泛型方法 3.1.9. 考虑通用方法 3.1.10. 考虑方法对,其中一方的效果被另一方反转 3.1.11. 避免“helper”方法 3.1.12. 避免长时间运行的方法 3.1.13. 避免强制调用者为基本场景编写循环 3.1.14. 避免使用“option”参数改变行为 3.1.15. 避免使用非重入方法 3.1.16. 不要删除已发布的方法 3.1.17. 如果不提供替换,请不要弃用已发布的方法 3.1.18. 请勿更改已发布方法的签名 3.1.19. 不要更改已发布方法的可观察行为 3.1.20. 不要强化已发布的API方法的前提条件 3.1.21. 不要弱化已发布的API方法的后置条件 3.1.22. 不要向已发布的接口添加新方法 3.1.23. 不要向已发布的API添加新的重载

3.2. 命名

3.2.1. 用强大的,富有表现力的动词开头 3.2.2. 使用小驼峰 3.2.3. 为JavaBeans方法访问保留“get”,“set”和“is” 3.2.4. 使用调用者熟悉的词汇 3.2.5. 保持接近英语口语 3.2.6. 避免缩略语 3.2.7. 避免使用通用动词 3.2.8. 避免同义词 3.2.9. 不要使用下划线 3.2.10. 不要依赖参数名称或类型来阐明方法的含义

3.3. 参数

3.3.1. 选择最精确的参数类型 3.3.2. 在相关方法调用中保持null参数值的含义一致 3.3.3. 在相关方法中使用一致的参数名称,类型和排序 3.3.4. 将输出参数放在参数列表中的输入参数之后 3.3.5. 为经常使用的默认参数值提供带有较短参数列表的重载方法 3.3.6. 对不相关类型使用相同语义的操作使用重载方法 3.3.7. 支持具体类的接口作为参数 3.3.8. 支持数组上的集合作为参数并返回值 3.3.9. 在原始(无类型)集合上支持泛型集合 3.3.10. 支持布尔类型或整数类型的枚举类型 3.3.11. 倾向于将单个对象参数放在集合或数组参数之前 3.3.12. 倾向于将自定义类型参数放在标准Java类型参数之前 3.3.13. 倾向于将对象参数置于值参数之前 3.3.14. 倾向于具体类的接口作为返回类型 3.3.15. 倾向于空集合而非空返回值 3.3.16. 倾向于返回值,这些值是相关方法的有效输入 3.3.17. 考虑制作可变参数的防御性副本 3.3.18. 考虑在内部存储弱对象引用 3.3.19. 避免可变长度参数列表 3.3.20. 避免使用长参数列表(超过3个) 3.3.21. 避免将相同类型的参数放在一起 3.3.22. 避免输出或输入方法参数 3.3.23. 避免方法重载(overloading) 3.3.24. 避免暴露实现细节的参数类型 3.3.25. 避免布尔参数 3.3.26. 避免返回null 3.3.27. 避免在核心Java API之外的无关API中定义的返回类型 3.3.28. 避免返回对可变内部对象的引用 3.3.29. 不要使用整数参数来传递预定义的常量值 3.3.30. 请勿保留参数以备将来使用 3.3.31. 请勿在重载方法中更改参数命名或排序

3.4. 错误处理

3.4.1. 仅在特殊情况下抛出异常 3.4.2. 仅针对可恢复的错误抛出已检查的异常 3.4.3. 抛出运行时异常以指示API使用错误 3.4.4. 在适当的抽象级别抛出异常 3.4.5. 执行运行时预处理检查 3.4.6. 抛出NullPointerException以指示禁止的空参数值 3.4.7. 抛出IllegalArgumentException以指示除null之外的不正确的参数值 3.4.8. 抛出IllegalStateException以指示在错误的上下文中进行的方法调用 3.4.9. 在错误消息中指出哪个参数违反了哪个前提条件 3.4.10. 确保失败的方法调用没有副作用 3.4.11. 为回调方法中的禁止API调用提供运行时检查 3.4.12. 倾向于自定义异常的标准Java异常 3.4.13. 倾向于可预测错误条件的异常查询方法

3.5. 重写

3.5.1. 使用@Override标注 3.5.2. 保持或削弱先决条件 3.5.3. 保持或加强后置条件 3.5.4. 保持或加强不变量 3.5.5. 不要抛出新类型的运行时异常 3.5.6. 不要更改方法的类型(无状态,访问器或更改器)

3.6. 构造函数

3.6.1. 最大限度地减少构造函数中完成的工作 3.6.2. 将所有属性的值设置为合理的默认值 3.6.3. 仅将构造函数参数用作设置属性的快捷方式 3.6.4. 验证构造函数参数 3.6.5. 命名构造函数参数应与对应的属性相同 3.6.6. 提供多个构造函数时,请遵循方法重载的准则 3.6.7. 倾向于静态工厂方法的构造函数 3.6.8. 考虑一个无参数的默认构造函数 3.6.9. 如果不总是需要新实例,请考虑使用静态工厂方法 3.6.10. 如果需要在运行时确定对象的精确类型,请考虑使用静态工厂方法 3.6.11. 如果需要访问外部资源,请考虑使用静态工厂方法 3.6.12. 在面对许多构造函数参数时,请考虑构建器 3.6.13. 考虑私有构造函数以防止直接类实例化 3.6.14. 避免创建不必要的对象 3.6.15. 避免finalizers 3.6.16. 不要从默认(无参数)构造函数中抛出异常 3.6.17. 不要将带有参数的构造函数添加到没有显式构造函数的类中

3.7. 设置器和查询器

3.7.1. 使用“get”作为返回非布尔属性的方法的名称 3.7.2. 使用“is”,“can”或类似方法作为返回布尔属性的方法的名称 3.7.3. 使用“set”作为更新本地属性的方法名称 3.7.4. 验证setter方法的参数 3.7.5. 尽量减少在getter和setter中完成的工作 3.7.6. 考虑从getter返回不可变集合 3.7.7. 考虑实现集合接口而不是集合类型的公共属性 3.7.8. 考虑只读属性 3.7.9. 在设置可变类型的属性时,请考虑制作防御性副本 3.7.10. 在返回可变类型的属性时,请考虑制作防御性副本 3.7.11. 避免从getter返回数组 3.7.12. 避免使用本地知识无法完成的验证 3.7.13. 不要从getter中抛出异常 3.7.14. 不要设计仅限set的属性(使用public setter 但无 public getter) 3.7.15. 设置时不要依赖其他属性

3.8. 回调

3.8.1. 设计具有最强的前提条件 3.8.2. 设计具有最弱的后置条件 3.8.3. 考虑将对引发回调对象的引用作为回调方法的第一个参数传递 3.8.4. 避免使用返回值进行回调

3.9. 文档

3.9.1. 为每个方法提供Javadoc注释 3.9.2. 遵循标准的Javadoc规范 3.9.3. 从方法的简短的,一句话摘要开始 3.9.4. 说明相关方法 3.9.5. 使用@deprecated标记指示已弃用的方法 3.9.6. 指示任何已弃用方法的替换 3.9.7. 避免冗长的注释 3.9.8. 文档化通用的行为模式 3.9.9. 文档化空参数值的精确含义(如果允许) 3.9.10. 文档化方法的类型(无状态,访问者或更改者) 3.9.11. 文档化方法前提条件 3.9.12. 文档化所实现算法的性能特征 3.9.13. 文档化远程方法调用 3.9.14. 文档化方法访问进程外资源 3.9.15. 文档化在回调方法中允许的API调用 3.9.16. 考虑单元测试来说明方法的行为

Tip

有时远程ssh启动占用端口服务一段时间后,需要重启该服务,网上常见介绍的方法是:

[root@remote ~]# netstat -tlnp|grep 5601
[root@remote ~]# netstat -tlnp|grep 5601
tcp   0 0 0.0.0.0:5601   0.0.0.0:*   LISTEN  12331/python2
[root@remote ~]# kill -9 11134

但是该方式在我的应用场景下没有启到应有作用:

[root@remote ~]# netstat -tlnp|grep 5601
[root@remote ~]# netstat -tlnp|grep 5601
tcp   0 0 0.0.0.0:5601   0.0.0.0:*   LISTEN  11134/python2

可以看到端口还是被占用但是PID发生了改变。经过尝试通过lsof命令(可能需要先安装该命令)可以解决该问题:

[root@remote ~]# lsof -i:5601
COMMAND    PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
gunicorn 11134 root    5u  IPv4 28974582      0t0  TCP *:esmagent (LISTEN)
gunicorn 24930 root    5u  IPv4 28974582      0t0  TCP *:esmagent (LISTEN)
[root@remote ~]# kill 24930
[root@remote ~]# kill 11134
-bash: kill: (11134) - 没有那个进程
[root@remote ~]# lsof -i:5601
[root@remote ~]# 

可以看出需要进程24930才是“真凶”,关闭其后11134也就自动关闭了。

Share

声明式事务与编程式事务

编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。

区分项 编程式事务 声明式事务
开发方式 侵入式 非侵入式
控制粒度 代码块级 方法级
修改点 代码 配置
实现基础 TransactionTemplate AOP

重写(Overriding)与重载(Overloading)

这两个词比较容易混淆,需要严格区分。

区分项 重写 重载      
层次 不同类间 同一类中      
应用场景 类继承 不同参数的相同方法      
访问 完全相同 可以不同      
参数 完全相同 参数不同      
返回值 完全相同 可以不同      
异常 可减少或删除 异常 可减少或删除 异常 可减少或删除,不能抛出其他或更广泛的异常

参考文献

  1. Java API设计检查单
  2. Sring事务管理的两种方式

  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!