[Java读书笔记] Effective Java(Third Edition) 第 6 章 枚举和注解
Java支持两种引用类型的特殊用途的系列:一种称为枚举类型(enum type)的类和一种称为注解类型(annotation type)的接口。
第34条:用enum代替int常量
枚举是其合法值由一组固定的常量组成的一种类型,例如一年中的季节,太阳系中的行星。
在将枚举类型添加到该语言之前,表示枚举类型的常见模式是声明一组名为int的常量,每个类型的成员都有一个常量。
int枚举模式的技术有许多缺点。不具有类型安全性,也没有描述性可言。
Java提供了一种避免int和String枚举模式的所有缺点的替代方法,并提供了许多额外的好处。它没有提供类型安全的方式,也没有提供任何表达力。
例如:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
Java枚举类型背后的基本思想很简单:它们是通过公共静态final属性为每个枚举常量导出一个实例的类。 由于没有可访问的构造方法,枚举类型实际上是final类。 由于客户既不能创建枚举类型的实例也不能继承它,除了声明的枚举常量外,不能有任何实例。 换句话说,枚举类型是实例控制的(第1条)。 它们是单例Singleton(第3条)的泛型化,基本上是单元素的枚举。
除了纠正int枚举的缺陷之外,枚举类型还允许添加任意方法和属性并实现任意接口。 它们提供了所有Object方法的高级实现(第3章),它们实现了Comparable(第 14条)和Serializable接口(第12章),并针对枚举类型的可任意改变性设计了序列化方式。
枚举中添加方法或域能将数据域常量联系起来。太阳系8大行星例子,每颗行星有质量和半径,通过这两个属性可以计算出表面重力。
// Enum type with data and behavior
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7); private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11; // Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
} public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; } public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
编写一个丰富的枚举类型比如Planet很容易。 要将数据与枚举常量相关联,请声明实例属性并编写一个构造方法,构造方法带有数据并将数据保存在属性中。
虽然Planet枚举很简单,但它的功能非常强大。 这是一个简短的程序,它将一个物体在地球上的重量(任何单位),打印一个漂亮的表格,显示该物体在所有八个行星上的重量(以相同单位):
public class WeightTable {
public static void main(String[] args) {
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Weight on %s is %f%n",
p, p.surfaceWeight(mass));
}
}
有时候需要不同的行为与每个常量关联起来,比如计算器四大基本操作,提供一个方法来执行每个常量所表示的算术运算。
// Enum type with constant-specific method implementations
public enum Operation {
PLUS {public double apply(double x, double y){return x + y;}},
MINUS {public double apply(double x, double y){return x - y;}},
TIMES {public double apply(double x, double y){return x * y;}},
DIVIDE{public double apply(double x, double y){return x / y;}}; public abstract double apply(double x, double y);
}
特定于常量的方法实现可以与特定于常量的数据结合使用。 例如,以下是Operation的一个版本,它重写toString方法以返回通常与该操作关联的符号:
// Enum type with constant-specific class bodies and data
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
}; private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
显示的toString实现可以很容易地打印算术表达式:
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n",
x, op, y, op.apply(x, y));
}
以2和4作为命令行参数运行此程序会生成以下输出:
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为。
例如,假设Operation枚举不在你的控制之下,你希望它有一个实例方法来返回每个相反的操作。
// Switch on an enum to simulate a missing method
public static Operation inverse(Operation op) {
switch(op) {
case PLUS: return Operation.MINUS;
case MINUS: return Operation.PLUS;
case TIMES: return Operation.DIVIDE;
case DIVIDE: return Operation.TIMES;
default: throw new AssertionError("Unknown op: " + op);
}
}
什么时候应该使用枚举?每当需要一组固定常量,并且在编译时就知道其成员的时候,就应该使用枚举。例如行星、一周的天数等等。也包括在编译时就知道其所有可能值得其他集合,比如菜单的选项、操作代码已经命令行标识等。
总之,与int相比,枚举更具可读性,更安全,更强大。
第35 条:用实例域代替序数
许多枚举通常与单个int值关联。所有枚举都有一个ordinal方法,它返回每个枚举常量类型的数值位置。你可能想从序数中派生一个关联的int值:
// Abuse of ordinal to derive an associated value - DON'T DO THIS
public enum Ensemble {
SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET; public int numberOfMusicians() { return ordinal() + 1; }
}
如果常量被重新排序,numberOfMusicians方法将会中断。 如果你想添加一个与你已经使用的int值相关的第二个枚举常量,则没有那么好运了。
永远不要从根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中:
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10), TRIPLE_QUARTET(12); private final int numberOfMusicians;
Ensemble(int size) { this.numberOfMusicians = size; }
public int numberOfMusicians() { return numberOfMusicians; }
}
第36条:用EnumSet代替位域
如果一个枚举类型的元素主要用于集合中,一般就使用int枚举模式(第 34条),例如将2的不同倍数赋值给每个常量:
// Bit field enumeration constants - OBSOLETE!
public class Text {
public static final int STYLE_BOLD = 1 << 0; //
public static final int STYLE_ITALIC = 1 << 1; //
public static final int STYLE_UNDERLINE = 1 << 2; //
public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8 // Parameter is bitwise OR of zero or more STYLE_ constants
public void applyStyles(int styles) { ... }
}
这种表示法让你使用OR位运算将几个常量合并到一个集合中,称为位域(bit field):
text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
但是位域有int枚举常量等的所有缺点,甚至更多。翻译位域比int枚举困难,在编写时要预测最多需要多少位,还要选择对应类型(int或long),一旦确定,在不修改API的情况下,将不能超出其位宽度(如32位或64位)。
下面是前一个范例改成用枚举代替位域之后的代码:
// EnumSet - a modern replacement for bit fields
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } // Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}
下面将EnumSet实例传递给applyStyle方法,如下:
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
总之,因为枚举类型要用在集合中,所有没有理由用位域来表示它。EnumSet类集位域的简洁和性能优势及第34条所述枚举类型的所有优点。
第37条:用EnumMap代替序数索引
有时可能会看到使用ordinal方法(条目 35)来索引到数组或列表的代码。例如下面表示一种香草:
class Plant {
enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL }
final String name;
final LifeCycle lifeCycle; Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
} @Override public String toString() {
return name;
}
}
现在假设有一个香草的数组,表示一座花园中的植物,想要列出这些由生命周期组织的植物(一年生,多年生,或双年生)。需要构建三个集合,每种类型一个,并遍历整个花园,将每种香草放到相应的集合中。有些人会将这些集合放到一个按照类型的序数进行索引的数组中来实现这一点:
// Using ordinal() to index into an array - DON'T DO THIS!
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length]; for (int i = 0; i < plantsByLifeCycle.length; i++)
plantsByLifeCycle[i] = new HashSet<>(); for (Plant p : garden)
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p); // Print the results
for (int i = 0; i < plantsByLifeCycle.length; i++) {
System.out.printf("%s: %s%n",
Plant.LifeCycle.values()[i], plantsByLifeCycle[i]);
}
这种方法可行,但有许多问题。因为数组不能与泛型兼容(第28条),程序需要进行未受检的转换,并且不能正确无误地进行编译。因为数组不知道它的索引代表什么,你必须手工标注这些索引的输出。
更好的办法是使用map,有一种快速map专门用于枚举键,称为java.util.EnumMap。
修改后的程序:
// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class); for (Plant.LifeCycle lc : Plant.LifeCycle.values())
plantsByLifeCycle.put(lc, new HashSet<>()); for (Plant p : garden)
plantsByLifeCycle.get(p.lifeCycle).add(p); System.out.println(plantsByLifeCycle);
这段程序更简短,更清晰,更安全,运行速度与原始版本相当。 没有不安全的转换; 无需手动标记输出,因为map键是知道如何将自己转换为可打印字符串的枚举; 并且不可能在计算数组索引时出错。 EnumMap与序数索引数组的速度相当,其原因是EnumMap内部使用了这样一个数组,但它对程序员的隐藏了这个实现细节,将Map的丰富性和类型安全性与数组的速度相结合。
总之,最好不要用序数来索引数组,而要使用EnumMap。 如果你所代表的关系是多维的,请使用EnumMap <...,EnumMap <... >>。
第38条:用接口模拟可扩展的枚举
大多数情况下,枚举的可扩展性是一个糟糕的主意。对于可伸缩的枚举类型,有一种具有说服力,即操作码(operation code)。如第34条的Operation类型,表示计算器。有时候要尽可能地让API的用户提供它们自己的操作,这样可以有效地扩展API所提供的操作集。
// Emulated extensible enum using an interface
public interface Operation {
double apply(double x, double y);
} public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol; BasicOperation(String symbol) {
this.symbol = symbol;
} @Override public String toString() {
return symbol;
}
}
假设想要定义前面所示的操作类型的扩展,包括指数运算和余数运算。 你所要做的就是编写一个实现Operation接口的枚举类型:
// Emulated extension enum
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
public double apply(double x, double y) {
return x % y;
}
}; private final String symbol; ExtendedOperation(String symbol) {
this.symbol = symbol;
}
@Override public String toString() {
return symbol;
}
}
只要API编写为接口类型(Operation),而不是实现(BasicOperation),现在就可以在任何可以使用基本操作的地方使用新操作。
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(ExtendedOperation.class, x, y);
} private static <T extends Enum<T> & Operation> void test(
Class<T> opEnumType, double x, double y) {
for (Operation op : opEnumType.getEnumConstants())
System.out.printf("%f %s %f = %f%n",
x, op, y, op.apply(x, y));
}
扩展过的操作类型的类的字面文字(ExtendedOperation.class)从main方法里传递给了test方法,用来描述被扩展操作的集合。这个类的字面文字充当有限制的类型令牌(第33条)。opEnumType参数中复杂的声明(<T extends Enum<T> & Operation> Class<T>)确保了Class对象既是枚举又是Operation的子类,这正是遍历元素和执行每个元素相关联的操作时所需要的。
第二种方式是传递一个Collection<? extends Operation>,这是一个限定通配符类型(第31条),而不是传递了一个类对象:
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
test(Arrays.asList(ExtendedOperation.values()), x, y);
}
private static void test(Collection<? extends Operation> opSet,
double x, double y) {
for (Operation op : opSet)
System.out.printf("%f %s %f = %f%n",
x, op, y, op.apply(x, y));
}
上面的两个程序在运行命令行输入参数4和2时生成以下输出:
4.000000 ^ 2.000000 = 16.000000
4.000000 % 2.000000 = 0.000000
总之,虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型来对它进行模拟。允许客户端谢自己的枚举来实现接口。
第 39 条:注解优于命名模式
一般使用命名模式(naming parttern)表明有些程序元素需要通过某种工具或者框架进行特殊处理。
如Junit4之前,用户必须以test作为测试方法的开头。
这种方法有几个缺点:1.文字拼写错误会导致失败,且没有提示。 2. 无法确保它们只用于相应的程序元素上。3. 它们没有提供将参数值与程序元素关联起来的好办法。
Junit4解决了以上问题。以下是名为Test的这种注解类型的定义:
// Marker annotation type declaration
import java.lang.annotation.*; /**
* Indicates that the annotated method is a test method.
* Use only on parameterless static methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
Test注解类型的声明本身使用Retention和Target注解进行标记。 注解类型声明上的这种注解称为元注解。 @Retention(RetentionPolicy.RUNTIME)元注解指示Test注解应该在运行时保留。 没有它,测试工具就不会看到Test注解。@Target.get(ElementType.METHOD)元注解表明Test注解只对方法声明合法:它不能应用于类声明,属性声明或其他程序元素。
注解不会改变注解代码的语义,但可以通过诸如这个简单的测试运行器等工具对其进行特殊处理:
// Program to process marker annotations
import java.lang.reflect.*; public class RunTests {
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
m.invoke(null);
passed++;
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
System.out.println(m + " failed: " + exc);
} catch (Exception exc) {
System.out.println("Invalid @Test: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
}
测试运行器工具在命令行上接受完全限定的类名,并通过调用Method.invoke来反射地运行所有类标记有Test注解的方法。 isAnnotationPresent方法告诉工具要运行哪些方法。 如果测试方法引发异常,则反射机制将其封装在InvocationTargetException中。 该工具捕获此异常并打印包含由test方法抛出的原始异常的故障报告,该方法是使用getCause方法从InvocationTargetException中提取的。
让我们添加对仅在抛出特定异常时才成功的测试的支持。 我们需要为此添加一个新的注解类型:
// Annotation type with a parameter
import java.lang.annotation.*;
/**
* Indicates that the annotated method is a test method that
* must throw the designated exception to succeed.
*/ @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
此注解的参数类型是Class<? extends Throwable>。 在英文中,它表示“某个扩展Throwable的类的Class对象”,它允许注解的用户指定任何异常(或错误)类型。 这个用法是一个限定类型标记的例子(第 33条)。
修改测试运行器工具来处理新的注解:
if (m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (InvocationTargetException wrappedEx) {
Throwable exc = wrappedEx.getCause();
Class<? extends Throwable> excType =
m.getAnnotation(ExceptionTest.class).value(); if (excType.isInstance(exc)) {
passed++;
} else {
System.out.printf(
"Test %s failed: expected %s, got %s%n",
m, excType.getName(), exc); }
} catch (Exception exc) {
System.out.println("Invalid @Test: " + m);
}
}
总之,除了工具铁匠(toolsmiths,即平台框架程序员)之外,不必自定义注解。应该尽量使用Java平台所提供的预定义注解类型。还要考虑IDE或静态分析工具所提供的任何注解。
第40 条:坚持使用Override注解
Override它表示被注解的方法声明覆盖了超类型中的一个方法声明。如果使用这个注解,将避免大量非法错误。例如:
// Can you spot the bug?
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
} public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();
for (int i = 0; i < 10; i++)
for (char ch = 'a'; ch <= 'z'; ch++)
s.add(new Bigram(ch, ch));
System.out.println(s.size());
}
}
主程序反复添加26个双字母组合到集合中,每个双字母组合由两个相同的小写字母组成。 然后它会打印集合的大小。 你可能希望程序打印26,因为集合不能包含重复项。 如果你尝试运行程序,你会发现它打印的不是26,而是260。哪里出错了?
Bigram的创作者原本想要覆盖equals方法(第10条),同时还记得覆盖hashCode(第11条)。遗憾的是程序并没有覆盖(overrride)equals方法,而是重载(overload)(第52条)了equals方法。
要重写Object.equals,必须定义一个equals方法,其参数的类型为Object,但Bigram的equals方法的参数不是Object类型的,因此Bigram继承Object的equals方法。
幸运的是编译器可以帮你发现这个错误,要用@Override标注Bigram.equals。
Override注解可以用在方法声明中,覆盖来做接口以及类的声明。由于缺省方法(default)的出现,在接口方法的具体实现上使用Override,可以确保签名正确。如果知道接口没有缺省方法,可以选择省略接口方法的具体实现上的Override注解,以减少混乱。
总之,如果在你想要的每个方法声明中使用Override注解来覆盖超类声明,编译器就可以替你防止大量错误。
第41 条:用标记接口定义类型
标记接口(marker interface)是不包含方法声明的接口,只是指明一个类实现了具有某种属性的接口。例如,考虑Serializable接口(第12条)。 通过实现这个接口,一个类表明它的实例可以写入ObjectOutputStream(或“序列化”)。
标记接口定义的类型是由被标记类的实例实现的,标记注解则没有定义这样的类型。标记接口类型的存在,允许你在编译时就能捕捉到在使用标记注解的情况下要到运行时才能捕捉到的错误。
标记接口胜过标记注解的另一个优点是,他们可以被更加精确地进行锁定。
标记注解胜过标记接口的最大优点是,他们是更大的注解机制的一部分。
什么时候使用标记注解,什么时候使用标记接口?
如果标记适用于除类或接口以外的任何程序元素,则必须使用注解,因为只能使用类和接口来实现或扩展接口。如果标记仅适用于类和接口,那么问自己问题:“可能我想编写一个或多个只接受具有此标记的对象的方法呢?”如果是这样,则应该优先使用标记接口而不是注解。
总之,标记接口和标记注释都有其用处。 如果你想定义一个没有任何关联的新方法的类型,一个标记接口是一种可行的方法。 如果要标记除类和接口以外的程序元素,或者将标记符合到已经大量使用注解类型的框架中,那么标记注解是正确的选择。 如果发现自己正在编写目标为ElementType.TYPE的标记注解类型,请花点时间确定它是否应该是注释类型,是不是标记接口是否更合适。
[Java读书笔记] Effective Java(Third Edition) 第 6 章 枚举和注解的更多相关文章
- [Java读书笔记] Effective Java(Third Edition) 第 7 章 Lambda和Stream
在Java 8中,添加了函数式接口(functional interface),Lambda表达式和方法引用(method reference),使得创建函数对象(function object)变得 ...
- [Java读书笔记] Effective Java(Third Edition) 第 3 章 对于所有对象都通用的方法
第 10 条:覆盖equals时请遵守通用约定 在不覆盖equals方法下,类的每个实例都只与它自身相等. 类的每个实例本质上都是唯一的. 类不需要提供一个”逻辑相等(logical equality ...
- [Java读书笔记] Effective Java(Third Edition) 第2章 创建和销毁对象
第 1 条:用静态工厂方法代替构造器 对于类而言,获取一个实例的方法,传统是提供一个共有的构造器. 类可以提供一个公有静态工厂方法(static factory method), 它只是一个返回类 ...
- [Java读书笔记] Effective Java(Third Edition) 第 5 章 泛型
第 26 条:请不要使用原生态类型 声明中具有一个或多个类型参数的类或者接口,就是泛型(generic). 例如List接口只有单个类型参数E, 表示列表的元素类型.这个接口全称List<E&g ...
- [Java读书笔记] Effective Java(Third Edition) 第 4 章 类和接口
第 15 条: 使类和成员的可访问性最小化 软件设计基本原则:信息隐藏和封装. 信息隐藏可以有效解耦,使组件可以独立地开发.测试.优化.使用和修改. 经验法则:尽可能地使每个类或者成员不被外界访问 ...
- 【读书笔记 - Effective Java】05. 避免创建不必要的对象
1. 如果对象是不可变的(immutable),它就始终可以被重用. (1) 特别是String类型的对象. String str1 = new String("str"); // ...
- 【java读书笔记】——java开篇宏观把控 + HelloWorld
学完java有一段时间了,一直没有做对应的总结,总认为有一种缺憾.从这篇博客開始,将自己平时的学习笔记进行总结归纳,分享给大家. 这篇博客主要简单的介绍一下java的基础知识,基本的目的是扫盲.原来仅 ...
- 【读书笔记 - Effective Java】02. 遇到多个构造器参数时要考虑用构建器
类有多个可选参数的解决方案: 1. 重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读. 2. JavaBeans模式,调用一个无参构造器来创造对象,然后调用sett ...
- 【读书笔记 - Effective Java】04. 通过私有构造器强化不可实例化的能力
工具类(utility class)不希望被实例化,比如只包含静态方法和静态域的类.为了这个目的,需要让这个类包含一个私有构造器. // 私有构造器示例 public class UtilityCla ...
随机推荐
- Java基于Redis的分布式锁
分布式锁,其实最终还是要保证锁(数据)的一致性,说到数据一致性,基于ZK,ETCD数据一致性中间件做分数是锁,才是王道.但是Redis也能满足最基本的需求. 参考: https://www.cnblo ...
- Linux学习笔记(九)shell基础:echo、命令别名和常用快捷键
一.echo在屏幕上打印内容 echo [选项] [输出内容] -e 支持转义字符控制的字符转换 输出带颜色的文本 二.第一个脚本 编写脚本 注意: #!/bin/bash 此行不是注释,必须有 #! ...
- Jupyter notebook部署引导
一.简介方面很多博客写得比较好,主要转发几篇: 1.对Jupyter notebook 的整体进行介绍: https://www.itcodemonkey.com/article/6025.html ...
- redis——持久化策略
4.2.2 持久 化方式(1 ) RDB 方式1. 什么是 RDB 方式?Redis Database(RDB),就是在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接再读到内存 ...
- SPFA的优化
[为什么要优化] 关于SPFA,他死了(懂的都懂) 进入正题... 一般来说,我们有三种优化方法. SLF优化: SLF优化,即 Small Label First 策略,使用 双端队列 进行优 ...
- vue3.0+typeScript项目
https://segmentfault.com/a/1190000018720570#articleHeader15 https://segmentfault.com/a/1190000016423 ...
- spring-JDBC配置,使用,一些报错,mybatis原理,优化
一. 配置spring的jdbc的pom.xml遇到报错 missing artifactXXXXX. 修改dependency的版本如下 <dependency> <groupId ...
- (四)AppScan用外部设备(ios,安卓)录制app脚本进行安全测试
一.打开AppScan,选择外部设备/客户机,点击下 二.记录代理设置,可以手动输入需要的端口号,也可以自动选择. 手机配置代理: 1.连接wifi 2.找到该wifi--高级设置--配置代理: 三. ...
- 关于EMF中从schema到ecore转变中的默认处理问题
以前的工作,建模基本都是通过ecore tool直接画ecore的模型图来完成,最近要从schema创建ecore文件,本来以为是非常简单的一件事情,使用向导创建genmodel,然后从xsd文件导入 ...
- HDU 6619 Horse 斜率优化dp
http://acm.hdu.edu.cn/showproblem.php?pid=6619 #include<bits/stdc++.h> #define fi first #defin ...