引言

普通的工程师堆砌代码,优秀的工程师优雅代码,卓越的工程师简化代码。如何写出优雅整洁易懂的代码是一门学问,也是软件工程实践里重要的一环。--来自网络

背景

软件质量,不但依赖于架构及项目管理,更与代码质量紧密相关。简洁高效的代码不但易于阅读,更能避免潜在BUG与风险,提高代码质量。近期,一位Oracle程序员在Hacker News上吐槽自己的工作,引起了热议。

这个工程师的核心痛点是,Oracle经历长期的产品线迭代,代码异常庞大、逻辑复杂,整个代码中充斥着神秘的宏命令。每新增一个特性或者修复BUG,该工程师都需要大量的调研,小心谨慎的进行着日常的工作。而Oracle每次的版本发布都经历数百万次的测试,脑补一下,如噩梦一般。那么我们应该如何编写简洁高效的代码呢?其实业内有过很多相关书籍,比如经典的书籍有《代码整洁之道》、《编写可读代码的艺术》、《重构:改善既有代码的设计》,可用于修炼内功。以及我们有严格的代码规范以及方便的静态代码扫描工具,可用于加强研发代码质量能力。

简洁之术

其实代码规范和静态代码扫描工具能够帮助我们完成很多代码简洁的工作。诸如:注释、命名、方法、异常、单元测试等多个方面。但却无法总结了一些代码简洁最佳实践,其实Java是面向对象语音,而面向对象的特征是封装、继承、多态,巧妙的运用这三大特性、理解Java的一些关键字特性、语音特性、阅读JDK源码,就可以写出相对简洁的代码了。

简化逻辑


// 修改前
if(list.size()>0) {
return true;
} else {
return false;
} // 修改后
return list.size()>0;
  • if/else 语法:if语句包含一个布尔表达式,当if语句的布尔表达式值为false时,else语句块会被执行;
  • return 关键字:返回一个任意类型的值;
  • list.size()>0 表达式:list.size()方法本身是一个返回int类型数值的函数,而与>0组成了一个布尔表达式;

省略无意义赋值


// 修改前
public List<Map<String, Object>> queryList(Map<String, Object> params) {
List<Map<String, Object>> list = null;
try {
list = mapper.queryList(params);
} catch (Throwable e) {
throw new RuntimeException("ERROR", e);
}
return list;
} // 修改后
public List<Map<String, Object>> queryList(Map<String, Object> params) {
try {
return mapper.queryList(params);
} catch (Throwable e) {
throw new RuntimeException("ERROR", e);
}
}
  • 局部变量list的数据类型与该方法的返回值类型一致,而多余的变量也将会增加JVM垃圾回收的消耗;
  • 局部变量list只是负责接收了mapper.queryList(params)的返回值,而并没有其他逻辑处理;
  • 此代码存在于service层和mapper层之间,可以在框架层面进一步抽象,利用注解、java8 default方法等进一步改进;

最小化判断


// 修改前
if (0 == retCode) {
sendMessage("A001", "Process Success", outResult);
} else {
sendMessage("A001", "Process Failure", outResult);
}
// 修改后 1 String message = (0 == retCode ? "Process Success" : "Process Failure");
sendMessage("A001", message, outResult); // 修改后 2
sendMessage("A001", messageFromRetCode(retCode), outResult);
  • 代码中if else的存在只是因为sendMessage函数的第二个参数会有两种情况(成功/失败),尽量让判断最小化;

set方法治理


// 修改前
String uuid = UUIDUtils.getUUID();
String date = DateTimeUtils.getCurrDt();
String time = DateTimeUtils.getCurrTm();
Order order = new Order();
order.setSrUsrId(map.get("srcUsrId"));
// 省略几十个set
...
order.setTmsDate(date);
order.setTmsCte(time);
order.setUuid(uuid); list.add(order); // 修改后
list.add(buildOrder(map)); public Order buildOrder(Map<String,String> map){ Order order = new Order();
order.setSrUsrId(map.get("srcUsrId"));
// 省略几十个set
...
String date = DateTimeUtils.getCurrDt();
order.setTmsDate(date);
order.setTmsCte(DateTimeUtils.getCurrTm());
String uuid = UUIDUtils.getUUID();
order.setUuid(uuid);
return order;
}
  • 大坨的set方法很影响代码可读性,可封装成特定方法或者使用lombok工具简化代码;
  • 局部变量就近声明,增加可读性,局部变量声明和使用地方距离遥远,会导致的读者频繁滑动;
  • 可不声明变量尽量不要声明多余的变量,冗余代码;(如date、time两段代码);

巧用JAVA8特性-函数式编程简化代码

JAVA8特性“函数式编程”,使用Lambdas我们能做到什么?

  • 遍历集合(List、Map等)、Sum、Max、Min、Avg、Sort、Distinct等等
  • 函数接口
  • 谓词(Predicate)使用
  • 实现Map和Reduce
  • 实现事件处理/简化多线程

内、外部循环


// 修改前
public static void test1() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); for (int number : numbers) {
System.out.println(number);
}
} // 修改后1
// 使用lambda表达式以及函数操作(functional operation)
public static void test2(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.forEach((Integer value)-> System.out.println(value));
} // 修改后2
//在Java8中使用双冒号操作符(double colon operator)
public static void test3(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.forEach(System.out::println);
}

上述代码是传统方式的遍历一个List的写法,简单来说主要有3个不足:

  • 只能顺序处理list中的数据(process one by one)
  • 不能充分利用多核cpu
  • 不利于编译器优化(jit)

而使用函数式编程能规避上面的三个问题:

  • 不一定需要顺序处理List中的元素,顺序可以不确定
  • 可以并行处理,充分利用多核CPU的优势
  • 有利于JIT编译器对代码进行优化
  • 代码看起来更简洁,完全交给编译器内部循环

default方法

在Java8中,接口中的方法可以被实现,用关键字 default 作为修饰符来标识,接口中被实现的方法叫做 default 方法。使用default方法,当接口发生改变的时候,实现类不需要做改动,所有的子类都会继承 default 方法。


public class Test1 {
public static void main(String[] args) {
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100)); // 100.0
System.out.println(formula.sqrt(16)); // 4.0
}
} interface Formula {
double calculate(int a); default double sqrt(int a) {
return Math.sqrt(a);
}
}

当一个接口扩展另外一个包含默认方法的接口的时候,有以下3种处理方式。

  • 完全无视默认方法(直接继承上级接口的默认方法)
  • 重新申明默认方法为抽象方法(无实现,具体子类必需再次实现该方法)
  • 重新实现默认方法(重写了默认方法的实现,依然是一个默认方法)

日期处理

Java8中新增了LocalDate和LocalTime接口,为什么要搞一套全新的处理日期和时间的API?因为旧的java.util.Date实在是太难用了。

  • java.util.Date月份从0开始,一月是0,十二月是11,变态吧!java.time.LocalDate月份和星期都改成了enum,就不可能再用错了。
  • java.util.Date和SimpleDateFormatter都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
  • java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,如果你只想用java.util.Date存储日期,或者只存储时间,那么,只有你知道哪些部分的数据是有用的,哪些部分的数据是不能用的。在新的Java8中,日期和时间被明确划分为LocalDate和LocalTime,LocalDate无法包含时间,LocalTime无法包含日期。

当然,LocalDateTime才能同时包含日期和时间。

新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。

  • 1、Clock时钟。Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代System.currentTimeMillis(),来获取当前的微秒数。某一个特定的时间点也可以使用Instant类(为Final类)来表示,Instant类也可以用来创建老的java.util.Date对象。

Clock c = Clock.systemDefaultZone();
System.out.println(System.currentTimeMillis());
System.out.println(c.millis());
Date date = Date.from(c.instant());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date));
// instant精确到纳秒,比原来的date的毫秒要更精确
// 获取当前时间
Instant in = Instant.now();
System.out.println(in);
// 将现在的时间增加3小时2分,将产生新的实例
Instant in1 = in.plus(Duration.ofHours(3).plusMinutes(2));
System.out.println(in1);
System.out.println(in1 == in);
// 关于计算的例子
in.minus(5, ChronoUnit.DAYS);// 计算5天前
in.minus(Duration.ofDays(5));// 计算5天前 // 计算两个Instant之间的分钟数
long diffAsMinutes1 = ChronoUnit.MINUTES.between(in, in1); // 方法2
System.out.println(diffAsMinutes1);
// instant是可比较的,有isAfter和isBefore
System.out.println(in.isAfter(in1));
System.out.println(in.isBefore(in1));
  • 2、LocalDate和LocalTime、LocalDateTime(均为Final类,不带时区)的一系列计算。LocalDateTime和Instant两者很像都是不带时区的日期和时间,Instant中是不带时区的即时时间点。比如:两个人都是2018年4月14日出生的,一个出生在北京,一个出生在纽约;看上去他们是一起出生的(LocalDateTime的语义),其实他们是有时间差的(Instant的语义)

// 取当前日期
LocalDate today = LocalDate.now();
System.out.println(today); // 获得2005年的第86天 (27-Mar-2005)
LocalDate localDate = LocalDate.ofYearDay(2005, 86);
System.out.println(localDate); // 根据年月日取日期 2013年8月10日
localDate = LocalDate.of(2013, Month.AUGUST, 10);
localDate = LocalDate.of(2013, 8, 10); // 根据字符串取
LocalDate.parse("2014-02-28"); // 严格按照ISO yyyy-MM-dd验证,02写成2都不行
LocalDate.parse("2014-02-29"); // 无效日期无法通过:DateTimeParseException: Invalid date // 取本月第1天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 取本月第2天:
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 取本月最后一天,再也不用计算是28,29,30还是31:
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 取下一天:
LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 变成了2015-01-01 // 取2015年1月第一个周一,这个计算用Calendar要死掉很多脑细胞:
LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01")
.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
// LocalTime
LocalTime now = LocalTime.now(); // 带纳秒
LocalTime now1 = LocalTime.now().withNano(0); // 清除纳秒
System.out.println(now);
System.out.println(now1);
LocalTime localTime = LocalTime.of(22, 33);
System.out.println(localTime); // 返回一天中的第4503秒
localTime = LocalTime.ofSecondOfDay(4503);
System.out.println(localTime);
LocalDateTime localDateTime0 = LocalDateTime.now();
System.out.println(localDateTime0); // 当前时间加上25小时3分钟
LocalDateTime inTheFuture = localDateTime0.plusHours(25).plusMinutes(3);
System.out.println(inTheFuture); // 同样也可以用在localTime和localDate中
System.out.println(localDateTime0.toLocalTime().plusHours(25).plusMinutes(3));
System.out.println(localDateTime0.toLocalDate().plusMonths(2));

Streams与集合

Stream是对集合的包装,通常和lambda一起使用。使用lambdas可以支持许多操作。如 map,filter,limit,sorted,count,min,max,sum,collect等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据。遇到像getFirst()这样的方法就会结束链式语法,通过下面一系列例子介绍:比如我有个Person类,就是一个简单的pojo,
针对这个对象,我们可能有这样一系列的运算需求。


class Person{
private String name, job, gender;
private int salary, age; // 省略若干get/set方法及构造方法
...
}
  • 先进行数据初始化,为后面运算演示提供基础

List<Person> persons = new ArrayList<Person>() {
private static final long serialVersionUID = 1L;
{
add(new Person("张三", "Java", "female", 25, 1000));
add(new Person("李四", "Java", "male", 29, 1200));
add(new Person("王五", "测试", "female", 25, 1400));
add(new Person("赵六", "Java", "male", 31, 1800));
add(new Person("张三三", "设计", "male", 33, 1900));
add(new Person("李四四", "需求", "female", 30, 2000));
add(new Person("王五五", "Java", "female", 29, 2100));
add(new Person("赵六六", "Java", "male", 43, 2800));
}
};
  • 1.使用foreach输出上述列表

Consumer<Person> print = e -> System.out.println(e.toString());
persons.forEach(print);
  • 2.将所有员工工资涨10%(使用foreach)

Consumer<Person> raise = e -> e.setSalary(e.getSalary()/100*10+e.getSalary());
persons.forEach(raise);
persons.forEach(print);
  • 3.显示工资低于1500的员工(使用stream().filter())

persons.stream().filter((p) -> (p.getSalary()< 1500)).forEach(print);
  • 4.显示工资大于2000 job=java 年龄>29 的女生

Predicate<Person> salaryPredicate = e -> e.getSalary() > 2000;
Predicate<Person> jobPredicate = e -> "Java".equals(e.getJob());
Predicate<Person> agePredicate = e -> e.getAge() >= 29;
Predicate<Person> genderPredicate = e -> "female".equals(e.getGender());
persons.stream().filter(salaryPredicate)
.filter(jobPredicate)
.filter(agePredicate)
.filter(genderPredicate)
.forEach(print);
  • 5.限制结果条数limit

persons.stream().filter(genderPredicate).limit(2).forEach(print);
  • 6.按照年龄排序

persons.stream().sorted((p1,p2)-> (p1.getAge() - p2.getAge()))
//.sorted((p1,p2)->(p1.getName().compareTo(p2.getName())))
.forEach(print);
  • 7.找出工资最高max(),年龄最小的min()

System.out.println(persons.stream().min((p1,p2)->(p1.getSalary()-p2.getSalary())).get().toString());
System.out.println(persons.stream().max((p1,p2)->(p1.getSalary()-p2.getSalary())).get().toString());
  • 8.计算所有人的工资parallel()并行的计算

System.out.println("所有人的工资总和:"+ persons.stream().parallel().mapToInt(p - > p.getSalary()).sum());
  • 9.将人员姓名存放到TreeSet\set\String中

String str = persons.stream().map(Person::getName).collect(Collectors.joining(";"));
System.out.println(str);
TreeSet<String> ts = persons.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
System.out.println("ts.toString():"+ts.toString());
Set<String> set = persons.stream().map(Person::getName).collect(Collectors.toSet());
System.out.println("set.toString():"+set.toString());
  • 10.统计结果summaryStatistics()

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers
.stream()
.mapToInt((x) -> x)
.summaryStatistics(); System.out.println("List中最大的数字 : " + stats.getMax());
System.out.println("List中最小的数字 : " + stats.getMin());
System.out.println("所有数字的总和 : " + stats.getSum());
System.out.println("所有数字的平均值 : " + stats.getAverage());
  • 11.去除重复元素,创建新数组

List<Integer> numbers1 = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers1.stream().distinct().collect(Collectors.toList());
System.out.println(distinct);

传递行为,而不仅仅是传值


//sumAll算法很简单,完成的是将List中所有元素相加。
public static int sumAll(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
total += number;
}
return total;
}

sumAll算法很简单,完成的是将List中所有元素相加。某一天如果我们需要增加一个对List中所有偶数求和的方法sumAllEven,那么就产生了sumAll2,如下:


public static int sumAll2(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
if (number % 2 == 0) {
total += number;
}
}
return total;
}

又有一天,我们需要增加第三个方法:对List中所有大于3的元素求和,那是不是继续加下面的方法呢?sumAll3


public static int sumAll3(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
if (number > 3) {
total += number;
}
}
return total;
}

观察这三个方法我们发现,有很多重复内容,唯一不同的是方法中的if条件不一样(第一个可以看成if(true)),如果让我们优化,可能想到的第一种重构就是策略模式吧,代码如下:


public interface Strategy {
public boolean test(int num);
} public class SumAllStrategy implements Strategy {
@Override
public boolean test(int num) {
return true;
}
} public class SumAllEvenStrategy implements Strategy {
@Override
public boolean test(int num) {
return num % 2 == 0;
}
} public class SumAllGTThreeStrategy implements Strategy {
@Override
public boolean test(int num) {
return num > 3;
}
}
public class BodyClass {
private Strategy stragegy = null;
private final static Strategy DEFAULT_STRATEGY = new SumAllStrategy(); public BodyClass() {
this(null);
} public BodyClass(Strategy arg) {
if (arg != null) {
this.stragegy = arg;
} else {
this.stragegy = DEFAULT_STRATEGY;
}
} public int sumAll(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
if (stragegy.test(number)) {
total += number;
}
}
return total;
}
} //调用
BodyClass bc = new BodyClass();
bc.sumAll(numbers);

这无疑使用设计模式的方式优化了冗余代码,但是可能要额外增加几个类,以后扩展也要新增,下面看看使用lambda如何实现,声明方法:第一个参数还是我们之前传递的List数组,第二个看起来可能有点陌生,通过查看jdk可以知道,这个类是一个谓词(布尔值的函数)


public static int sumAllByPredicate(List<Integer> numbers, Predicate<Integer> p) {
int total = 0;
for (int number : numbers) {
if (p.test(number)) {
total += number;
}
}
return total;
}
//调用:
sumAllByPredicate(numbers, n -> true);
sumAllByPredicate(numbers, n -> n % 2 == 0);
sumAllByPredicate(numbers, n -> n > 3);

代码是不是比上面简洁了很多?语义也很明确,重要的是不管以后怎么变,都可以一行代码就修改了。。。万金油啊。

其他

JAVA8 还推出了很多特性,来简化代码。比如String.join函数、Objects类、Base64编码类。

字符串拼接

String joined = String.join("/", "usr","local","bin");
String joided1="usr/"+"local/"+"bin/";
System.out.println(joined); String ids = String.join(", ", ZoneId.getAvailableZoneIds());
System.out.println(ids);
Objects类

String aa = null; Objects.requireNonNull(aa," aa must be not null"); Object a = null;
Object b = new Object();
if(a.equals(b)){ }
if(Objects.equals(a, b)){
}
Base64编码

Base64.Encoder encoder = Base64.getEncoder();
Base64.Decoder decoder = Base64.getDecoder();
String str = encoder.encodeToString("你好".getBytes(StandardCharsets.UTF_8));
System.out.println(str);
System.out.println(new String(decoder.decode(str),StandardCharsets.UTF_8));

总结

好的代码需要不停的打磨,作为一个优秀的工程师,我们应该严格遵守,每次提交的代码要比迁出的时候更好。经常有人说,作为工程师一定要有团队精神,但这种精神并不是说说而已的,需要实际的行动来体现的。设计模式、JDK的新特性都是我们可以借助的经验,编码完成后思考一下,还可不可以在简化、优化,不要成为一个“作恶”的工程师。

作者简介

马铁利,随行付架构部负责人 & TGO鲲鹏会北京分会会员,10年全栈工程师,擅长微服务分布式架构设计。主要负责随行付架构部日常管理;参与构建微服务平台周边基础设施及中间件;负责随行付对外开源等事宜。

原文链接:https://my.oschina.net/matieli/blog/2992447

JAVA基础之代码简洁之道的更多相关文章

  1. JavaScript 代码简洁之道

    摘要: 可以说是<Clean Code>的JS代码示例了,值得参考. 原文:JavaScript 代码简洁之道 作者:缪宇 Fundebug经授权转载,版权归原作者所有. 测试代码质量的唯 ...

  2. 《Clean Code》 代码简洁之道

    作者介绍 原文作者: Robert C. Martin, Object Mentor公司总裁,面向对象设计.模式.UML.敏捷方法学和极限编程领域的资深顾问,是<敏捷软件开发:原则.模式.与实践 ...

  3. JAVA基础(代码)练习题61~90

    JAVA基础 61.设计一个方法打印数组{'a','r','g','s','e','r'}中下标为1和3的的元素 package Homework_90; /** * 设计一个方法打印数组{'a',' ...

  4. Java面试题系列 ----- Java基础面试题(91道)

    更多详情点击查看,点这里!这里!!这里!!! 文末获取所有面试PDF文档! Java概述 1. 何为编程 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程. 为了 ...

  5. Java基础(basis)-----代码块详解

    1.静态代码块 格式: static{ 内容..... } 随着类的加载而加载,而且只被加载一次 多个静态代码块之间按照顺序结构执行 优先其他各种代码块以及构造函数先执行 静态的代码块中只能执行静态的 ...

  6. Java基础学习-代码块

    /*代码块: * 用{}修饰的代码 * 1.局部代码块:控制变量,存在方法中,控制变量的生命周期(作用域) * 2.构造代码块:提取构造方法中的共性,每次创建对象,都会执行,并且在构造方法执行之前执行 ...

  7. JS代码简洁之道--函数

    函数的参数越少越好 有一个准则是:如果你的函数参数超过两个,就应该改为对象传入. 这样做是合理的,因为当函数参数超过两个时,参数顺序开始变得难以记忆,而且容易出现一种很尴尬的情况:比如我只需要传入第三 ...

  8. java基础知识代码-------枚举类型

    package com.mon10.day22; /** * 类说明 :枚举类型,案例二 * * @author 作者 : chenyanlong * @version 创建时间:2017年10月22 ...

  9. PHP 代码简洁之道 ( PHP Clean Code)

    https://laravel-china.org/topics/7774/the-conciseness-of-the-php-code-php-clean-code

随机推荐

  1. 洛谷p1052过河 路径压缩+dp

    洛谷 P1052 过河 思路部分可以看这篇博客 我将在这里对其进行一些解释与补充 首先我们先看题 乍一看 这不是模板题吗 然后开开心心的敲了一个简单dp上去 #include<iostream& ...

  2. antd-vue中table行高亮效果实现

    [方式一]:通过设置customRow达到目的,点击时遍历所有行设置为正常颜色,把当前行设置为特殊颜色(高亮色) HTML: <a-table ref="table" siz ...

  3. 小白也能弄懂的卷积神经网络(Convolutional Neural Networks )

    本系列主要是讲解卷积神经网络 - Convolutional Neural Networks 的系列知识,本系列主要帮助大家入门,我相信这是所有入门深度学习的初学者都必须学习的知识,这里会用更加直接和 ...

  4. 计算Pi

    程序: package pai; import java.math.BigDecimal; public class ComplexPi { BigDecimal result; public Com ...

  5. linux 增加新用户无法使用sudo命令解决办法

    昨天一不小心把自己的系统搞崩了,也没有快照,没法进行还原操作,所以只能重装系统解决了,装完系统以后一切正常,当我新增了一个用户,使用sudo命令切换到root用户时,发现怎么都切换不过去,经过百度发现 ...

  6. vueJs 安装

    1.下载nodeJs 可前往 https://www.cnblogs.com/takeyblogs/p/13600124.html 这里下载 2.由于 npm 安装速度慢,本教程使用了淘宝的镜像及其命 ...

  7. Java使用ArrayList实现“群主发红包”功能

    群主发普通红包,某群有多名成员,群主给成员发普通红包,普通红包的规则: 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取: 成员领取红包后,保存到成员余额中. 请根据描述,完成案例中所有类 ...

  8. Linux:用tar解压文件出现错误Not found in archive

    问题:用tar解压文件出现错误Not found in archive 解决办法:加上-C参数  tar -zxvf ZenTaoPMS.8.1.3.zbox_64.gz -C /usr 因为压缩文件 ...

  9. 内置函数:循环调用函数map和filter

    1.map:循环调用函数,前面一定一定要加list,要不然不会被调用 map的格式:list(map(函数名,循环体)) #这里的函数只能写函数名,不要加() list(map(os.mkdir,[' ...

  10. MySQL安装错误Couldn't find MySQL server

    Starting MySQL ERROR! Couldn't find MySQL server (/usr/local/mysql/bin/mysqld_safe) 昨天rpm安装MySQL5.7后 ...