Java进阶 1-2 枚举
目录
常量特定方法
职责链模式的枚举实现
状态机模式的枚举实现
多路分发
1、使用枚举类型实现分发
2、使用常量特定方法实现分发
3、使用EnumMap实现分发
4、使用二维数组实现分发
本笔记参考自: 《On Java 中文版》
常量特定方法
在Java中,我们可以通过为每个枚举实例编写不同的方法,来赋予它们不同的行为。一个方式是在枚举类型中定义抽象方法,并在枚举实例中实现它:
【例子:为枚举实例编写方法】
import java.text.DateFormat;
import java.util.Date;public enum ConstantSpecificMethod {DATE_TIME {@OverrideString getInfo() {return DateFormat.getDateInstance().format(new Date());}},CLASSPATH {@OverrideString getInfo() {// getenv()方法用于获取指定环境变量的值return System.getenv("CLASSPATH");}},VERSION {@OverrideString getInfo() { // 获取由输入指定的属性return System.getProperty("java.version");}};abstract String getInfo();public static void main(String[] args) {for (ConstantSpecificMethod csm : values())System.out.println(csm.getInfo());}
}
程序执行的结果是:
在这个例子中,我们通过关联的枚举实例来调用对应的方法,这通常被称为表驱动模式。
这里需要复习一点:在面向对象的编程中,不同的行为是和不同的类相关联的。而上述枚举的每个实例都拥有各自不同的行为,这就表明每个实例都相当于不同的类型(体现了多态)。它们的共同点在于它们的基类ConstantSpecificMethod。
但这并不意味着枚举实例能够等价于普通的类:
【例子:枚举实例和类之间的不同点】
enum LikeClasses {A {@Overridevoid behavior() {System.out.println("A");}},B {@Overridevoid behavior() {System.out.println("B");}},C {@Overridevoid behavior() {System.out.println("C");}};abstract void behavior();
}public class NotClasses {
// void f1(LikeClasses.A instance) {
// } // 不可行的操作
}
编译器并不允许f1()的操作:将枚举实例作为类的类型进行使用。为了解释这一点,可以对程序进行反编译(javap -c LikeClasses):
反编译告诉我们,每个枚举元素都是LikeClasses的一个static final实例。实例无法作为类型进行使用。
除此之外,不同于内部类,因为枚举实例是静态的,所以我们无法通过外部类LikeClasses直接访问其内部的枚举实例(具体而言,我们无法在非静态域中访问外部类的静态成员)。
(与匿名内部类相比,常量特定方法要显得更加简洁。)
除此之外,常量特定方法也支持重写:
【例子:重写常量特定方法】
public enum OverrideConstantSpecific {NUT, BOLT,WASHER {@Overridepublic void f() {System.out.println("重写后的f()方法");}};void f() {System.out.println("默认的f()方法");}public static void main(String[] args) {for (OverrideConstantSpecific ocs : values()) {System.out.print(ocs + ": ");ocs.f();}}
}
程序执行的结果是:
这些特性使得我们在通常情况下,可以将枚举作为类来使用。
职责链模式的枚举实现
||| 职责链模式:先创建一批用于解决目标问题的不同方法,并将它们链式排列。当一个请求到达时,它会顺着这条“链”向下走,直到遇到可以解决当前请求的方法。
下面的例子描述了一个邮局模型:使用常规方法处理信件,当前方法不可行时,尝试其他方法,直到无法处理该信件为止(称为“死信”)。
可以将每种处理方法视作一种策略,策略的列表组成了一条职责链。
【例子:邮局模型】
import onjava.Enums;import java.util.Iterator;class Mail {enum GeneralDelivery {YES, NO1, NO2, NO3, NO4, NO5} // 是否使用常规方式处理enum Scannability {UNSCANNABLE, YES1, YES2, YES3, YES4} // 是否可以扫描enum Readability {ILLEGIBLE, YES1, YES2, YES3, YES4} // 是否可读enum Address {INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6}enum ReturnAddress {MISSING, OK1, OK2, OK3, OK4, OK5}// 创建枚举变量GeneralDelivery generalDelivery;Scannability scannability;Readability readability;Address address;ReturnAddress returnAddress;static long counter = 0;long id = counter++;@Overridepublic String toString() {return "邮件" + id;}public String details() {return toString() +",一般交付:" + generalDelivery + ",地址是否可以扫描:" + scannability +"\n地址是否可读:" + readability + ",目标地址:" + address +"\n返还地址:" + returnAddress;}// 用于生成测试邮件:// (Enums类可见笔记 进阶1-1)public static Mail randomMail() {Mail m = new Mail();// 为枚举实例随机赋值:m.generalDelivery =Enums.random(GeneralDelivery.class);m.scannability =Enums.random(Scannability.class);m.readability =Enums.random(Readability.class);m.address =Enums.random(Address.class);m.returnAddress =Enums.random(ReturnAddress.class);return m;}// count无需修改,可以使用finalpublic static Iterable<Mail> generator(final int count) {return new Iterable<Mail>() {int n = count;@Overridepublic Iterator<Mail> iterator() {return new Iterator<Mail>() {@Overridepublic boolean hasNext() {return n-- > 0;}@Overridepublic Mail next() {return randomMail();}};}};}
}public class PostOffice {// 使用职责链模式:enum MailHandler {GENERAL_DELIVERY {@Overrideboolean handle(Mail m) {switch (m.generalDelivery) {case YES:System.out.println("对" + m + "使用常规方法进行处理");return true;default:return false;}}},MACHINE_SCAN {@Overrideboolean handle(Mail m) {switch (m.scannability) {case UNSCANNABLE:return false;default:switch (m.address) {case INCORRECT:return false;default:System.out.println("自动派送" + m);return true;}}}},VISUAL_INSPECTION {@Overrideboolean handle(Mail m) {switch (m.readability) {case ILLEGIBLE:return false;default:switch (m.address) {case INCORRECT:return false;default:System.out.println("常规派送" + m);return true;}}}},RETURN_TO_SENDER {@Overrideboolean handle(Mail m) {switch (m.returnAddress) {case MISSING:return false;default:System.out.println("将" + m + "返还给发送者");return true;}}};abstract boolean handle(Mail m);}static void handle(Mail m) {for (MailHandler handler : MailHandler.values())if (handler.handle(m))return;System.out.println(m + "是一封死信");}public static void main(String[] args) {for (Mail mail : Mail.generator(5)) {System.out.println(mail.details());handle(mail);System.out.println("============");}}
}
程序执行的结果是:
职责链模式在MailHandler枚举中得到应用,并且枚举的定义顺序决定了各个策略在邮件上的应用顺序。
状态机模式的枚举实现
||| 状态机的解释:
- 具有有限数量的特定状态。
- 每个状态都有输入。根据输入,可以实现状态之间的跳转,但也存在瞬态。
- 当任务执行完毕后,立即跳出所有状态。
用枚举表示状态机模式的一个优势是,枚举可以限制出现的状态集的大小:
【例子:自动售货机】
首先创建一个用于输入的枚举:
import java.util.Random;public enum Input {CENT(1), COIN(10), TEN_COIN(100),TOOTHBRUSH(150.0f), TOWEL(200.0f), SOAP(80.0f),ABORT_TRANSACTION {@Overridepublic float amount() {throw new RuntimeException("ABORT.amount()");}},STOP { // STOP必须是最后一个实例@Overridepublic float amount() {throw new RuntimeException("SHUT_DOWN.amount()");}};float value;Input(float value) {this.value = value;}Input() {}float amount() { // 单位为:毛return value;}static Random rand = new Random(47);public static Input randomSelection() {// 不包括STOP:return values()[rand.nextInt(values().length - 1)];}
}
amount()会返回物品对应的价格。由于枚举的限制,amount()会作用于所有的枚举实例。很显然,对最后两个实例调用它并不合适,因此这里需要重写amount()。
接下来实现自动售货机的状态机:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.function.Supplier;
import java.util.stream.Collectors;enum Category {MONEY(Input.CENT, Input.COIN, Input.TEN_COIN),ITEM_SELECTION(Input.TOOTHBRUSH, Input.TOWEL, Input.SOAP),QUIT_TRANSACTION(Input.ABORT_TRANSACTION),SHUT_DOWN(Input.STOP);private Input[] values;Category(Input... types) {values = types;}private static EnumMap<Input, Category> categories =new EnumMap<>(Input.class);static {for (Category c : Category.class.getEnumConstants())for (Input type : c.values)categories.put(type, c);}public static Category categorize(Input input) {return categories.get(input);}
}public class VendingMachine {private static State state = State.RESTING;private static float amount = 0;private static Input selection = null;enum StateDuration { // 用于标记enum中的瞬态TRANSIENT;}enum State {RESTING { // 休眠中@Overridevoid next(Input input) {switch (Category.categorize(input)) {case MONEY:amount += input.amount();state = ADDING_MONEY; // 重新赋值statebreak;case SHUT_DOWN:state = TERMINAL;default:}}},ADDING_MONEY { // 添加金钱,购买商品@Overridevoid next(Input input) {switch (Category.categorize(input)) {case MONEY:amount += input.amount();break;case ITEM_SELECTION:selection = input;if (amount < selection.amount())System.out.println("当前余额不足以购买" + selection);elsestate = DISPENSING; // 下个状态也可以是瞬态break;case QUIT_TRANSACTION:state = GIVING_CHANGE;break;case SHUT_DOWN:state = TERMINAL;default:}}},DISPENSING(StateDuration.TRANSIENT) { // 取出商品(注意:实例的初始化方式并不相同)@Overridevoid next() {System.out.println("这是你购买的物品:" + selection);amount -= selection.amount();state = GIVING_CHANGE;}},GIVING_CHANGE(StateDuration.TRANSIENT) { // 找钱@Overridevoid next() {if (amount > 0) {System.out.println("这是找零:" + amount);amount = 0;}state = RESTING;}},TERMINAL { // 关闭机器@Overridevoid output() {System.out.println("停止运行");}};// 判断当前状态是否为瞬态private boolean isTransient = false;State() {}State(StateDuration trans) {isTransient = true; // 说明当前处于瞬态}void next(Input input) {throw new RuntimeException("仅应该对非瞬态调用next(Input input)");}void next() {throw new RuntimeException("仅应该对StateDuration.TRANSIENT(瞬态)调用next()");}void output() {System.out.println(amount);}}static void run(Supplier<Input> gen) {while (state != State.TERMINAL) {state.next(gen.get());while (state.isTransient)state.next();state.output();}}public static void main(String[] args) {Supplier<Input> gen = new RandomInputSupplier();if (args.length == 1)gen = new FileInputSupplier(args[0]);run(gen);}
}class RandomInputSupplier implements Supplier<Input> {@Overridepublic Input get() {return Input.randomSelection();}
}// 从文件输入:
class FileInputSupplier implements Supplier<Input> {private Iterator<String> input;FileInputSupplier(String fileName) {try {input = Files.lines(Paths.get(fileName)) // 行级的Stream流.flatMap(s -> Arrays.stream(s.split(";"))) // 输入文件的信息是由分号分隔的.map(String::trim).collect(Collectors.toList()).iterator();} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic Input get() {if (!input.hasNext())return null;return Enum.valueOf(Input.class, input.next().trim());}
}
若不从文件导入数据,则售货机在开机后就会一直运行下去。为了完整演示整个状态机的流程,这里使用文件输入。测试文件如下:
CENT; TEN_COIN; TOOTHBRUSH;
COIN; TEN_COIN; TEN_COIN; SOAP;
TEN_COIN; TEN_COIN; CENT; COIN; TOWEL;
ABORT_TRANSACTION;
STOP;
对应该文件,程序执行的结果是:
在这种模式中,枚举在switch上发挥了它的功能。
在VendingMachine类中存在着两个瞬态:DISPENSING和GIVING_CHANGE。状态机会在各种状态之间移动,知道不再处于某个瞬态中为止(对这个例子而言,“取出商品”和“找零”的动作是瞬态,售货机不应该停留在这些状态中)。
然而,这种设计存在一个限制:State的枚举实例是静态的,这意味着这些实例只能访问其外部类(即VendingMachine)中的静态字段。这意味着只能存在一个VendingMachine实例。
多路分发
若一个程序中存在多个交互类型,这个程序有可能变得很混乱。举一个例子:
public abstract class Number {abstract Number plus(Number number);abstract Number multiply(Number number);//...
}
此处的Number类是某个系统中所有数值类型的基类,其中包括一些进行数值运算所需要的方法。
现在假设a和b分别属于Number的某个子类,并且都使用Number引用指示它们。若要处理语句a.plus(b),一个简单的想法是我们需要同时知道a、b的具体类型。遗憾的是,Java只能进行单路分发,这意味着我们只能对一个类型未知的对象进行操作。
多路分发并不是同时对多个类型未知的对象进行操作,而是进行了多次的方法调用。
使用多路分发,就必须对每个类型都执行虚拟调用。并且,若操作发生在不同的交互类型层次结构中,则还需要对每个层次结构都执行虚拟操作。
接下来通过一个“猜拳”的例子展示多路分发的使用:
【例子:“猜拳”游戏】
首先确定“猜拳”的三种结果:
public enum Outcome {WIN, // 赢LOSE, // 输DRAW // 平局
}
接下来实现多路分发的接口:
public interface Item {Outcome compete(Item it);Outcome eval(Paper p);Outcome eval(Scissors p);Outcome eval(Rock p);
}
接下来为“石头”、“剪刀”和“布”分别匹配它们的胜负情况:
然后,通过调用compete()方法,就可以简单地判断“猜拳”的胜负:
import java.util.Random;public class RoShamBo1 {static final int SIZE = 10;private static Random rand = new Random(47);public static Item newItem() {switch (rand.nextInt(3)) {default: // 必须有的defaultcase 0:return new Scissors();case 1:return new Paper();case 2:return new Rock();}}public static void match(Item a, Item b) {System.out.println(a + " vs. " + b + " " + a.compete(b));}public static void main(String[] args) {for (int i = 0; i < SIZE; i++)match(newItem(), newItem());}
}
程序执行的结果是:
在match()方法中,程序可以知道a的类型,进而调用对应的compete()方法。在compete()中调用eval()方法时,会向其中传入一个this引用,这个this引用用于保留原本a的类型。
如上所示,多路分发的构筑较为麻烦。但好处显而易见:我们在调用中保持住了语法的优雅。
因为枚举实例不是类型,因此我们无法通过重载eval()方法的方式实现目标。但还是有别的方法可以进行实现。
1、使用枚举类型实现分发
一种方法是在初始化枚举实例时进行操作。通过这种方式,我们最终可以得到一个类似于查询表的结构:
【例子:初始化枚举实现“猜拳”】
import static enums.Outcome.*;// 规定Competitor接口,通过接口来操作枚举:
public enum RoShamBo2 implements Competitor<RoShamBo2> {PAPER(DRAW, LOSE, WIN),SCISSORS(WIN, DRAW, LOSE),ROCK(LOSE, WIN, DRAW);private Outcome vPAPER, vSCISSORS, vROCK;// 针对“石头”、“剪刀”和“布”这三种情况,初始化后三个私有变量的值也会发生改变RoShamBo2(Outcome paper,Outcome scissors, Outcome rock) {this.vPAPER = paper;this.vSCISSORS = scissors;this.vROCK = rock;}@Overridepublic Outcome compete(RoShamBo2 competitor) {switch (competitor) {default:case ROCK: // 若对方出的是石头return vROCK; // 那么就返回当前对象遇到石头时会得出的结果case PAPER: // 布return vPAPER;case SCISSORS: // 剪刀return vSCISSORS;}}public static void main(String[] args) {RoShamBo.play(RoShamBo2.class, 10); // 独立出来的方法,用于操作猜拳}
}
程序执行的结果是:
书本在此处将原本用于操作枚举的代码分隔开来,以此增加了代码的复用率。以下是复用的代码(Competitor接口及RoShamBo类)的实现:
【例子:用于操作枚举的工具】
为了统一现有的枚举及之后会出现的枚举的操作,可以使用Competitor接口:
public interface Competitor<T extends Competitor<T>> {Outcome compete(T competitor);
}
我们规定,所有的“石头剪刀布”枚举都来自于这种竞争者(Competitor)关系,自限定类型(笔记18-4)可以很好地满足这一点。
与一般的泛型相比,自限定类型对继承关系有更加严格的要求。对于上述的compete()方法而言,它只会接受Competitor及其子类。
然后是生成随机结果的RoShamBo类:
import onjava.Enums;public class RoShamBo extends EnumClass {// 使用了自限定类型:public static <T extends Competitor<T>>void match(T a, T b) {System.out.println(a + " vs. " + b + " " + a.compete(b));}// 这种方式同时继承了一个基类和一个接口:public static <T extends Enum<T> & Competitor<T>>void play(Class<T> rsbClass, int size) {for (int i = 0; i < size; i++)match(Enums.random(rsbClass), Enums.random(rsbClass));}
}
这个类包含了一个用于接收的paly()方法和真正调用枚举操作的match()方法。play()方法规定,传入的数据必须是一个实现了Competitor接口的枚举。
需要注意一点:由于play()方法不需要返回值,换言之,在离开泛型的边界上并没有需要使用到类型参数T的地方,因此参数T在这里似乎是不必要的。但如果将Class<T>替换为Class<?>这种通配符形式,就会发生报错:
这是因为通配符【?】无法继承多个基类,因此这里只能使用类型参数T。
2、使用常量特定方法实现分发
遗憾的是,枚举本身并不能作为对象类型使用。因此我们没有办法通过传递实例来进行多路分发。在这里,一个好的办法是使用switch语句:
【例子:使用switch分发】
import static enums.Outcome.*;public enum RoShamBo3 implements Competitor<RoShamBo3> {PAPER {@Overridepublic Outcome compete(RoShamBo3 competitor) {switch (competitor) {default:case PAPER:return DRAW;case SCISSORS:return LOSE;case ROCK:return WIN;}}},SCISSORS {@Overridepublic Outcome compete(RoShamBo3 competitor) {switch (competitor) {default:case PAPER:return WIN;case SCISSORS:return DRAW;case ROCK:return LOSE;}}},ROCK {@Overridepublic Outcome compete(RoShamBo3 competitor) {switch (competitor) {default:case PAPER:return LOSE;case SCISSORS:return WIN;case ROCK:return DRAW;}}};// 此处的方法声明只是为了便于理解:@Overridepublic abstract Outcome compete(RoShamBo3 competitor);public static void main(String[] args) {RoShamBo.play(RoShamBo3.class, 10);}
}
程序执行的结果与上一个例子相同:
这种写法无疑是繁琐的。因此,我们可以尝试对其进行优化。用三目运算符来取代switch语句,压缩代码空间:
【例子:使用三目运算符进行优化】
public enum RoShamBo4 implements Competitor<RoShamBo4> {ROCK {@Overridepublic Outcome compete(RoShamBo4 competitor) {return compete(SCISSORS, competitor);}},SCISSORS {@Overridepublic Outcome compete(RoShamBo4 competitor) {return compete(PAPER, competitor);}},PAPER {@Overridepublic Outcome compete(RoShamBo4 competitor) {return compete(ROCK, competitor);}};// 重载的compete()方法// 第一个参数表示的是胜利条件Outcome compete(RoShamBo4 loser, RoShamBo4 competitor) {return ((competitor == this) ? Outcome.DRAW : // 通过this确定平局条件(competitor == loser) ? Outcome.WIN : // 通过传入参数确定胜利条件Outcome.LOSE); // 那么剩下的就是失败条件了}public static void main(String[] args) {RoShamBo.play(RoShamBo4.class, 10);}
}
由于输出结果是一致的,因此此处不再展示。
当然,更加简短的代码有可能带来更大的理解成本。在大型的系统开发过程中应该要注意这一点。
3、使用EnumMap实现分发
EnumMap是专门为enum设计的高效Map。使用它,我们可以更好地实现多路分发。具体而言,当我们需要进行两路分发时,我们可以使用嵌套的EnumMap:
【例子:使用EnumMap实现分发】
import java.util.EnumMap;import static enums.Outcome.*;public enum RoShamBo5 implements Competitor<RoShamBo5> {PAPER, SCISSORS, ROCK;// 使用Class进行初始化,规定Map的键值必须是RoShamBo5类型static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>>table = new EnumMap<>(RoShamBo5.class);static {// 使用for循环,为table每个键创建其对应的对象for (RoShamBo5 it : RoShamBo5.values())table.put(it, new EnumMap<>(RoShamBo5.class));// 为对象填充具体的信息initRow(PAPER, DRAW, LOSE, WIN);initRow(SCISSORS, WIN, DRAW, LOSE);initRow(ROCK, LOSE, WIN, DRAW);}static void initRow(RoShamBo5 it,Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {EnumMap<RoShamBo5, Outcome> row =RoShamBo5.table.get(it); // get()方法会返回指定键对应的值(一个EnumMap的对象)row.put(RoShamBo5.PAPER, vPAPER);row.put(RoShamBo5.SCISSORS, vSCISSORS);row.put(RoShamBo5.ROCK, vROCK);}@Overridepublic Outcome compete(RoShamBo5 competitor) {return table.get(this).get(competitor); // 两次get(),两次分发}public static void main(String[] args) {RoShamBo.play(RoShamBo5.class, 10);}
}
这里需要注意的是static子句。我们需要在RoShamBo5的其余部分初始化完毕之前,将所有的胜负信息填充到table里面。因此需要使用静态的初始化方式。
4、使用二维数组实现分发
在“石头剪刀布”中,每一个枚举实例都会对应一个固定的值(可以用ordinal()获取)。这使得使用二维数组实现两路分发成为可能:
【例子:使用二维数组实现分发】
import static enums.Outcome.*;public enum RoShamBo6 implements Competitor<RoShamBo6> {PAPER, SCISSORS, ROCK;// 胜负顺序需要根据枚举的定义顺序进行调整:private static Outcome[][] table = {{DRAW, LOSE, WIN}, // 布{WIN, DRAW, LOSE}, // 剪刀{LOSE, WIN, DRAW}, // 石头};@Overridepublic Outcome compete(RoShamBo6 competitor) {return table[this.ordinal()][this.ordinal()];}public static void main(String[] args) {RoShamBo.play(RoShamBo6.class, 10);}
}
这种写法有着极高的效率,以及简短易懂的代码。但与之相对,它的缺点也正是在于其使用了数组:一方面,在面对更加大量的数据时,使用数组或许会漏掉某些麻烦的情况;除此之外,这种写法更为死板——从数组中得到的依旧只是一个常量的结果。
针对第二个缺点,我们可以使用函数对象来进行一些变化,使得数组不再那么僵硬。并且,在处理特定问题时,“表驱动模式”可以发挥出强大的力量。
相关文章:
Java进阶 1-2 枚举
目录 常量特定方法 职责链模式的枚举实现 状态机模式的枚举实现 多路分发 1、使用枚举类型实现分发 2、使用常量特定方法实现分发 3、使用EnumMap实现分发 4、使用二维数组实现分发 本笔记参考自: 《On Java 中文版》 常量特定方法 在Java中,我们…...
一个人最大的内驱力是什么?
1、不因为孤独或外界压力而降低「生活标准“」的能力。 ”因为寂寞去约炮“、“因为家里催婚匆忙结婚“、”因为没谈过恋爱随便找个人交往。 “你的每一次选择都是在为自己想要的世界而投的票,往后余生是幸福还是悲剧,就是在这一次次 的将就与坚持死磕中…...
解决方法:公众号的API上传素材报错40005
公众号的API上传素材报错40005 Error uploading file : {"errcode":40005,"errmsg":"invalid file type hint: [YOkxGA0122w487] rid: 223442-323247e7bd5-5d75322d88"}上传错误原因分析: 之前成功的示例,文件名为"…...
音量控制软件sound control mac功能亮点
sound control mac可以帮助用户控制某个独立应用程序的音量,通过每应用音量,均衡器,平衡和音频路由独立控制每个应用的音频,还有整个系统的音量。 sound control mac功能亮点 每个应用程序的音量控制 独立控制应用的数量。 键盘音…...
Spring Boot 生产就绪中文文档-下
本文为官方文档直译版本。原文链接 由于篇幅较长,遂分两篇。上半部分中文文档 Spring Boot 生产就绪中文文档-下 度量标准入门受支持的监控系统AppOpticsAtlasDatadogDynatracev2 API自动配置手动配置 v1 API (旧版)与版本无关的设置 ElasticGangliaGraphiteHumioIn…...
DS|树结构及应用
题目一:DS树 -- 树的先根遍历(双亲转先序) 题目描述: 给出一棵树的双亲表示法结果,用一个二维数组表示,位置下标从0开始,如果双亲位置为-1则表示该结点为根结点 编写程序,输出该树…...
Java 读取超大excel文件
注意:此参考解决方案只是针对xlsx格式的excel文件! Maven <dependency><groupId>com.monitorjbl</groupId><artifactId>xlsx-streamer</artifactId><version>2.2.0</version> </dependency>读取方式1…...
K8S中的job和CronJob
Job 介绍 Kubernetes jobs主要是针对短时和批量的工作负载。它是为了结束而运行的,而不是像deployment、replicasets、replication controllers和DaemonSets等其他对象那样持续运行。 示例 apiVersion: batch/v1 kind: Job metadata:name: pispec:template:spec:r…...
中国文化文物和旅游统计年鉴,数据含pdf、excel等格式,文本形式呈现,可预览数据
基本信息. 数据名称: 中国旅游统计年鉴 数据格式: pdf、xls不定 数据时间: 2012-2020年 数据几何类型: 文本 数据坐标系: —— 数据来源:文化和旅游部、网络公开数据 原名为《中国旅游统计年鉴》2020年后更名为《中国文化文物和旅游统计年鉴》ÿ…...
Java版企业电子招标采购系统源码——鸿鹄电子招投标系统的技术特点
在数字化时代,采购管理也正经历着前所未有的变革。全过程数字化采购管理成为了企业追求高效、透明和规范的关键。该系统通过Spring Cloud、Spring Boot2、Mybatis等先进技术,打造了从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通过…...
go语言语法基础
文章目录 前言一、输入和输出常用的字符串格式化符号 二、注释三、Go常用基本语言数据类型数字类型布尔类型字符类型变量与常量数组和切片数组切片 map类型创建map增删改查特别提醒 指针 四、运算符五、条件判断语句if系列switch六、循环语句for循环标准写法死循环while循环do …...
eclipse 和java环境的安装教程
安装 Eclipse 和配置 Java 环境是一个多步骤的过程,涉及到安装 Java Development Kit (JDK) 和 Eclipse IDE。以下是基本步骤: 安装 Java Development Kit (JDK) 下载 JDK: 访问 Oracle 官方网站(Oracle JDK)或者选择…...
Win11系统的优化方法参考文档(彻底优化策略)
目录 一、个性化-应用-关闭防火墙等的设置 二、任务栏优化设置 三、Win11开始菜单更改为Win10经典菜单 四、将Micresoft Store 从固定任务栏取消 五、电源性能优化 六、解决卡顿 七、卸载系统自带软件 八、任务管理器开机启动项的禁用 九、调整为最佳性能 十…...
Leetcode13-解密消息(2325)
1、题目 给你字符串 key 和 message ,分别表示一个加密密钥和一段加密消息。解密 message 的步骤如下: 使用 key 中 26 个英文小写字母第一次出现的顺序作为替换表中的字母 顺序 。 将替换表与普通英文字母表对齐,形成对照表。 按照对照表 …...
二进制安装包安装Prometheus插件安装(mysql_exporter)
简介 mysql_exporter是用来收集MysQL或者Mariadb数据库相关指标的,mysql_exporter需要连接到数据库并有相关权限。既可以用二进制安装部署,也可以通过容器形式部署,但为了数据收集的准确性,推荐二进制安装。 一,下载安…...
原生微信小程序如何动态修改svg图片颜色及尺寸、宽高(封装svgIcon组件)
最终效果 前言 动态设置Svg图片颜色就是修改Svg源码的path中的fill属性, 通过wx.getFileSystemManager().readFile读取.xlsx文件 把文件转成base64 封装svg-icon组件 1、在项目的components下新建svg-icon文件夹,新增base64.js文件 class Base64 {cons…...
Python从入门到网络爬虫(面向对象详解)
前言 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本…...
NPDP产品经理含金量高吗?难考吗?
NPDP的中文翻译为产品经理国际资格认证。NPDP考试起源于美国,由美国产品开发与管理协会(PDMA)发起。NPDP认证是集理论、方法与实践为一体的全方位知识体系,为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。࿰…...
目标检测 YOLOv5 - 推理时的数据增强
目标检测 YOLOv5 - 推理时的数据增强 flyfish 版本 YOLOv5 6.2 参考地址 https://github.com/ultralytics/yolov5/issues/303在训练时可以使用数据增强,在推理阶段也可以使用数据增强 在测试使用数据增强有个名字叫做Test-Time Augmentation (TTA) 实际使用中使…...
篇二:springboot2.7 OAuth2 server使用jdbc存储RegisteredClient
上一篇 <<springboot 2.7 oauth server配置源码走读一>>中简单描述了oauth2 server的配置,其中使用了内存保存 RegisteredClient,本篇改用mysql存储。 db存储需要创建表,表结构应该是什么样的呢,从spring给我们封装好…...
卷积神经网络|导入图片
在学习卷积神经网络时,我们通常使用的就是公开的数据集,这里,我们不使用公开数据集,直接导入自己的图片数据,下面,就简单写个程序实现批量图片的导入。 import osfrom PIL import Imageimport numpy as np…...
关于unity的组件VerticalLayoutGroup刷新显示不正常的问题
先说明一下我是如何用到,有哪些处理的 用到这个组件基本上都是将列表进行排版操作的,竖着,或者横着,横着用HorizontalLayoutGroup 还有一个和这个组件搭配的组件叫ContentSizeFitter 先说我是怎么发现这个组件不好用的 //本地读取…...
wait 和 notify 这个为什么要在synchronized 代码块中?
一个工作七年的小伙伴,竟然不知道” wait”和“notify”为什么要在 Synchronized 代码块中 。 好吧,如果屏幕前的你也不知道,请在公屏上刷”不知道“。 对于这个问题,我们来看看普通人和高手的回答。 一、问题解析 1. wait 和 n…...
大白话说区块链和通证
1 区块链 简单地说,区块链其实就像是一个不可篡改的分布式数据库,该分布式数据库记录了一系列交易或事件。区块链运行在至少1个以上的节点上,每个节点都有自己的一个分布式数据库,也就是分布式账本。正常情况下,每个节…...
Jvm之垃圾收集器(个人见解仅供参考)
问:什么是垃圾收集算法中的分代收集理论? 答:分代收集理论是垃圾收集算法的一种思想,根据对象存活周期的不同将内存分为几块,一般将java堆分为新生代和老年代。这种理论使得我们可以根据各个年代的特点选择合适的垃圾收…...
Minitab 21软件安装包下载及安装教程
Minitab 21下载链接:https://docs.qq.com/doc/DUkNHZVhwTXhtTFla 1.选中下载好的安装包,鼠标右键解压到”Minitab 21“文件夹 2.选中”Setup.exe“,鼠标右击选择“以管理员身份运行” 3.点击“下一步” 4.点击“是” 5.点击“下一步” 6.勾选…...
Java版商城:Spring Cloud+SpringBoot b2b2c电子商务平台,多商家入驻、直播带货及免 费 小程序商城搭建
随着互联网的快速发展,越来越多的企业开始注重数字化转型,以提升自身的竞争力和运营效率。在这个背景下,鸿鹄云商SAAS云产品应运而生,为企业提供了一种简单、高效、安全的数字化解决方案。 鸿鹄云商SAAS云产品是一种基于云计算的…...
阿里云被拉入黑洞模式怎么办?该怎么换ip-速盾网络
被拉入黑洞模式(BGP黑洞路由)意味着所有进入目标IP的流量都会被丢弃,从而导致目标IP对外完全不可访问。这种情况通常发生在面对大规模DDoS攻击时,为了防止攻击流量对其他网络造成影响。如果你使用的是阿里云服务并遭受到这种攻击&…...
Android 13.0 recovery竖屏界面旋转为横屏
1.概述 在13.0系统项目定制化开发中,由于平板固定横屏显示,而如果recovery界面竖屏显示就觉得怪怪的,所以需要recovery页面横屏显示的功能, 所以今天就来解决这个问题 2.实现功能相关分析 Android的Recovery中,利用 bootable\recovery下的minui库作为基础,采用的是直接…...
异地环控设备如何远程维护?贝锐蒲公英解决远程互联难题
青岛某企业致力于孵化设备、养禽设备和养猪设备的研发、生产和服务,历经三十多年发展,目前已成长为行业主要的养殖装备及工程服务提供商,产品覆盖养殖产业链中绝大多数环节,涉及自动化设备、环控设备、整体解决方案等。 在实际应用…...
西安蓝海网站建设/百度网站怎么做
0401Z2教育领导与管理01 教育政策研究02 教育领导研究①101思想政治理论②201英语一③777教育学专业综合教育管理学本专业只招收本科毕业生045115小学教育①101思想政治理论②204英语二③333教育综合④811课程与教学论儿童教育心理学本专业只招收本科毕业生120403教育经济与管理…...
自己怎么做dj 视频网站/市场调研报告万能模板
fork的作用是根据一个现有的进程复制出一个新进程,原来的进程称为父进程(Parent Process),新进程称为子进程(Child Process)。系统中同时运行着很多进程,这些进程都是从最初只有一个进程开始一个…...
单页网站制作/广告推广精准引流
简述Python的五大数据类型的作用、定义方式、使用方法: 数字类型 整型(int) 作用:表示人的年龄、各种号码、级别 定义方式:int定义,例如:ageint(18) 使用方法:加减乘除、逻辑判断&am…...
网站定制开发是什么意思/网络推广有几种方法
配置了 log4j 在xml 中,但控制台输出为空的解决办法 1、 在Mybatis 的核心配置文件内添加: <setting name"logImpl" value"LOG4J"/> </settings>2、在当前工程的pom 文件内添加 <dependencies><dependency>…...
网站客服怎么做的/今日世界杯比分预测最新
前言 很多次小伙伴问到学习方法,我也很想写这样的一篇文章来跟大家讨论下关于学习方法这件事情。 其实学习方法这个事情,我没啥发言权,因为我自己本身都是没啥方法可言的,就瞎折腾那种,但是大家想看这样的一篇文章&a…...
怎么做彩票网站代理/百度知道灰色词代发收录
本节书摘来自异步社区《Adobe Illustrator CS5中文版经典教程》一书中的第0课0.13节使用“外观”面板和效果,作者【美】Adobe公司,更多章节内容可以访问云栖社区“异步社区”公众号查看。 0.13 使用“外观”面板和效果Adobe Illustrator CS5中文版经典教程“外观”面…...