当前位置: 首页 > news >正文

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枚举中得到应用,并且枚举的定义顺序决定了各个策略在邮件上的应用顺序。


状态机模式的枚举实现

||| 状态机的解释:

  1. 具有有限数量的特定状态。
  2. 每个状态都有输入。根据输入,可以实现状态之间的跳转,但也存在瞬态
  3. 当任务执行完毕后,立即跳出所有状态。

        用枚举表示状态机模式的一个优势是,枚举可以限制出现的状态集的大小:

【例子:自动售货机】

        首先创建一个用于输入的枚举:

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类中存在着两个瞬态:DISPENSINGGIVING_CHANGE。状态机会在各种状态之间移动,知道不再处于某个瞬态中为止(对这个例子而言,“取出商品”和“找零”的动作是瞬态,售货机不应该停留在这些状态中)。

    然而,这种设计存在一个限制:State的枚举实例是静态的,这意味着这些实例只能访问其外部类(即VendingMachine)中的静态字段。这意味着只能存在一个VendingMachine实例。

多路分发

        若一个程序中存在多个交互类型,这个程序有可能变得很混乱。举一个例子:

public abstract class Number {abstract Number plus(Number number);abstract Number multiply(Number number);//...
}

此处的Number类是某个系统中所有数值类型的基类,其中包括一些进行数值运算所需要的方法。

        现在假设ab分别属于Number的某个子类,并且都使用Number引用指示它们。若要处理语句a.plus(b),一个简单的想法是我们需要同时知道ab的具体类型。遗憾的是,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、使用二维数组实现分发 本笔记参考自&#xff1a; 《On Java 中文版》 常量特定方法 在Java中&#xff0c;我们…...

一个人最大的内驱力是什么?

1、不因为孤独或外界压力而降低「生活标准“」的能力。 ”因为寂寞去约炮“、“因为家里催婚匆忙结婚“、”因为没谈过恋爱随便找个人交往。 “你的每一次选择都是在为自己想要的世界而投的票&#xff0c;往后余生是幸福还是悲剧&#xff0c;就是在这一次次 的将就与坚持死磕中…...

解决方法:公众号的API上传素材报错40005

公众号的API上传素材报错40005 Error uploading file : {"errcode":40005,"errmsg":"invalid file type hint: [YOkxGA0122w487] rid: 223442-323247e7bd5-5d75322d88"}上传错误原因分析&#xff1a; 之前成功的示例&#xff0c;文件名为"…...

音量控制软件sound control mac功能亮点

sound control mac可以帮助用户控制某个独立应用程序的音量&#xff0c;通过每应用音量&#xff0c;均衡器&#xff0c;平衡和音频路由独立控制每个应用的音频&#xff0c;还有整个系统的音量。 sound control mac功能亮点 每个应用程序的音量控制 独立控制应用的数量。 键盘音…...

Spring Boot 生产就绪中文文档-下

本文为官方文档直译版本。原文链接 由于篇幅较长&#xff0c;遂分两篇。上半部分中文文档 Spring Boot 生产就绪中文文档-下 度量标准入门受支持的监控系统AppOpticsAtlasDatadogDynatracev2 API自动配置手动配置 v1 API (旧版)与版本无关的设置 ElasticGangliaGraphiteHumioIn…...

DS|树结构及应用

题目一&#xff1a;DS树 -- 树的先根遍历&#xff08;双亲转先序&#xff09; 题目描述&#xff1a; 给出一棵树的双亲表示法结果&#xff0c;用一个二维数组表示&#xff0c;位置下标从0开始&#xff0c;如果双亲位置为-1则表示该结点为根结点 编写程序&#xff0c;输出该树…...

Java 读取超大excel文件

注意&#xff1a;此参考解决方案只是针对xlsx格式的excel文件&#xff01; Maven <dependency><groupId>com.monitorjbl</groupId><artifactId>xlsx-streamer</artifactId><version>2.2.0</version> </dependency>读取方式1…...

K8S中的job和CronJob

Job 介绍 Kubernetes jobs主要是针对短时和批量的工作负载。它是为了结束而运行的&#xff0c;而不是像deployment、replicasets、replication controllers和DaemonSets等其他对象那样持续运行。 示例 apiVersion: batch/v1 kind: Job metadata:name: pispec:template:spec:r…...

中国文化文物和旅游统计年鉴,数据含pdf、excel等格式,文本形式呈现,可预览数据

基本信息. 数据名称: 中国旅游统计年鉴 数据格式: pdf、xls不定 数据时间: 2012-2020年 数据几何类型: 文本 数据坐标系: —— 数据来源&#xff1a;文化和旅游部、网络公开数据 原名为《中国旅游统计年鉴》2020年后更名为《中国文化文物和旅游统计年鉴》&#xff…...

Java版企业电子招标采购系统源码——鸿鹄电子招投标系统的技术特点

在数字化时代&#xff0c;采购管理也正经历着前所未有的变革。全过程数字化采购管理成为了企业追求高效、透明和规范的关键。该系统通过Spring Cloud、Spring Boot2、Mybatis等先进技术&#xff0c;打造了从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通过…...

go语言语法基础

文章目录 前言一、输入和输出常用的字符串格式化符号 二、注释三、Go常用基本语言数据类型数字类型布尔类型字符类型变量与常量数组和切片数组切片 map类型创建map增删改查特别提醒 指针 四、运算符五、条件判断语句if系列switch六、循环语句for循环标准写法死循环while循环do …...

eclipse 和java环境的安装教程

安装 Eclipse 和配置 Java 环境是一个多步骤的过程&#xff0c;涉及到安装 Java Development Kit (JDK) 和 Eclipse IDE。以下是基本步骤&#xff1a; 安装 Java Development Kit (JDK) 下载 JDK&#xff1a; 访问 Oracle 官方网站&#xff08;Oracle JDK&#xff09;或者选择…...

Win11系统的优化方法参考文档(彻底优化策略)

目录 一、个性化-应用-关闭防火墙等的设置 二、任务栏优化设置 三、Win11开始菜单更改为Win10经典菜单 四、将Micresoft Store 从固定任务栏取消 五、电源性能优化 六、解决卡顿 七、卸载系统自带软件 八、任务管理器开机启动项的禁用 九、调整为最佳性能 十…...

Leetcode13-解密消息(2325)

1、题目 给你字符串 key 和 message &#xff0c;分别表示一个加密密钥和一段加密消息。解密 message 的步骤如下&#xff1a; 使用 key 中 26 个英文小写字母第一次出现的顺序作为替换表中的字母 顺序 。 将替换表与普通英文字母表对齐&#xff0c;形成对照表。 按照对照表 …...

二进制安装包安装Prometheus插件安装(mysql_exporter)

简介 mysql_exporter是用来收集MysQL或者Mariadb数据库相关指标的&#xff0c;mysql_exporter需要连接到数据库并有相关权限。既可以用二进制安装部署&#xff0c;也可以通过容器形式部署&#xff0c;但为了数据收集的准确性&#xff0c;推荐二进制安装。 一&#xff0c;下载安…...

原生微信小程序如何动态修改svg图片颜色及尺寸、宽高(封装svgIcon组件)

最终效果 前言 动态设置Svg图片颜色就是修改Svg源码的path中的fill属性&#xff0c; 通过wx.getFileSystemManager().readFile读取.xlsx文件 把文件转成base64 封装svg-icon组件 1、在项目的components下新建svg-icon文件夹&#xff0c;新增base64.js文件 class Base64 {cons…...

Python从入门到网络爬虫(面向对象详解)

前言 Python从设计之初就已经是一门面向对象的语言&#xff0c;正因为如此&#xff0c;在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。如果你以前没有接触过面向对象的编程语言&#xff0c;那你可能需要先了解一些面向对象语言的一些基本…...

NPDP产品经理含金量高吗?难考吗?

NPDP的中文翻译为产品经理国际资格认证。NPDP考试起源于美国&#xff0c;由美国产品开发与管理协会&#xff08;PDMA&#xff09;发起。NPDP认证是集理论、方法与实践为一体的全方位知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。&#xff0…...

目标检测 YOLOv5 - 推理时的数据增强

目标检测 YOLOv5 - 推理时的数据增强 flyfish 版本 YOLOv5 6.2 参考地址 https://github.com/ultralytics/yolov5/issues/303在训练时可以使用数据增强&#xff0c;在推理阶段也可以使用数据增强 在测试使用数据增强有个名字叫做Test-Time Augmentation (TTA) 实际使用中使…...

篇二:springboot2.7 OAuth2 server使用jdbc存储RegisteredClient

上一篇 <<springboot 2.7 oauth server配置源码走读一>>中简单描述了oauth2 server的配置&#xff0c;其中使用了内存保存 RegisteredClient&#xff0c;本篇改用mysql存储。 db存储需要创建表&#xff0c;表结构应该是什么样的呢&#xff0c;从spring给我们封装好…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

node.js的初步学习

那什么是node.js呢&#xff1f; 和JavaScript又是什么关系呢&#xff1f; node.js 提供了 JavaScript的运行环境。当JavaScript作为后端开发语言来说&#xff0c; 需要在node.js的环境上进行当JavaScript作为前端开发语言来说&#xff0c;需要在浏览器的环境上进行 Node.js 可…...

结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案

以下是一个结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案,包含完整数学推导、PyTorch/TensorFlow双框架实现代码及对比实验分析。 基于PINN的反应扩散方程稀疏数据预测与大规模数据泛化能力研究 1. 问题定义与数学模型 1.1 反应扩散方程 考虑标…...

Caliper 配置文件解析:config.yaml 和 fisco-bcos.json 附加在caliper中执行不同的合约方法

Caliper 配置文件解析:config.yaml 和 fisco-bcos.json Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO…...