十六、代码校验(2)
本章概要
- 前置条件
- 断言(Assertions)
- Java 断言语法
- Guava 断言
- 使用断言进行契约式设计
- 检查指令
- 前置条件
- 后置条件
- 不变性
- 放松 DbC 检查或非常严格的 DbC
- DbC + 单元测试
前置条件
前置条件的概念来自于契约式设计(Design By Contract, DbC), 利用断言机制实现。我们从 Java 的断言机制开始来介绍 DBC,最后使用谷歌的 Guava 库作为前置条件。
断言(Assertions)
断言通过验证在程序执行期间满足某些条件,从而增加了程序的健壮性。举例,假设在一个对象中有一个数值字段表示日历上的月份。这个数字总是介于 1-12 之间。断言可以检查这个数字,如果超出了该范围,则报告错误。如果在方法的内部,则可以使用断言检查参数的有效性。这些是确保程序正确的重要测试,但是它们不能在编译时被检查,并且它们不属于单元测试的范围。
Java 断言语法
你可以通过其它程序设计架构来模拟断言的效果,因此,在 Java 中包含断言的意义在于它们易于编写。断言语句有两种形式 :
assert boolean-expression;
assert boolean-expression: information-expression;
两者似乎告诉我们 “我断言这个布尔表达式会产生 true”, 否则,将抛出 AssertionError 异常。
AssertionError 是 Throwable 的派生类,因此不需要异常说明。
不幸的是,第一种断言形式的异常不会生成包含布尔表达式的任何信息(与大多数其他语言的断言机制相反)。
下面是第一种形式的例子:
// validating/Assert1.java// Non-informative style of assert
// Must run using -ea flag:
// {java -ea Assert1}
// {ThrowsException}
public class Assert1 {public static void main(String[] args) {assert false;}
}/* Output:
___[ Error Output ]___
Exception in thread "main" java.lang.AssertionError
at Assert1.main(Assert1.java:9)
*/
如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 -ea 标志, 它也可以表示为: -enableassertion, 这将运行程序并执行任何断言语句。
输出中并没有包含多少有用的信息。另一方面,如果你使用 information-expression , 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 information-expression 通常是一串针对程序员的文本:
// validating/Assert2.java
// Assert with an information-expression
// {java Assert2 -ea}
// {ThrowsException}public class Assert2 {public static void main(String[] args) {assert false:"Here's a message saying what happened";}
}
/* Output:
___[ Error Output ]___
Exception in thread "main" java.lang.AssertionError:
Here's a message saying what happened
at Assert2.main(Assert2.java:8)
*/
information-expression 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它包含失败的断言。
你还可以基于类名或包名打开或关闭断言;也就是说,你可以对整个包启用或禁用断言。实现这一点的详细信息在 JDK 的断言文档中。此特性对于使用断言的大型项目来说很有用当你想打开或关闭某些断言时。但是,日志记录(Logging)或者调试(Debugging),可能是捕获这类信息的更好工具。
你还可以通过编程的方式通过链接到类加载器对象(ClassLoader)来控制断言。类加载器中有几种方法允许动态启用和禁用断言,其中 setDefaultAssertionStatus () ,它为之后加载的所有类设置断言状态。因此,你可以像下面这样悄悄地开启断言:
// validating/LoaderAssertions.java
// Using the class loader to enable assertions
// {ThrowsException}
public class LoaderAssertions {
public static void main(String[] args) {ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);new Loaded().go();}
}class Loaded {public void go() {assert false: "Loaded.go()";}
}
这消除了在运行程序时在命令行上使用 -ea 标志的需要,使用 -ea 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数,这么做是有意义的。然而,决定在程序运行时启用断言可以使用下面的 static 块来实现这一点,该语句位于系统的主类中:
static {boolean assertionsEnabled = false;// Note intentional side effect of assignment:assert assertionsEnabled = true;if(!assertionsEnabled)throw new RuntimeException("Assertions disabled");
}
如果启用断言,然后执行 assert 语句,assertionsEnabled 变为 true 。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,assert 语句不执行,assertionsEnabled 保持false,将导致异常。
Guava断言
因为启用 Java 本地断言很麻烦,Guava 团队添加一个始终启用的用来替换断言的 Verify 类。他们建议静态导入 Verify 方法:
import com.google.common.base.*;import static com.google.common.base.Verify.*;public class GuavaAssertions {public static void main(String[] args) {verify(2 + 2 == 4);try {verify(1 + 2 == 4);} catch (VerifyException e) {System.out.println(e);}try {verify(1 + 2 == 4, "Bad math");} catch (VerifyException e) {System.out.println(e.getMessage());}try {verify(1 + 2 == 4, "Bad math: %s", "not 4");} catch (VerifyException e) {System.out.println(e.getMessage());}String s = "";s = verifyNotNull(s);s = null;try {verifyNotNull(s);} catch (VerifyException e) {System.out.println(e.getMessage());}try {verifyNotNull(s, "Shouldn't be null: %s", "arg s");} catch (VerifyException e) {System.out.println(e.getMessage());}}
}
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version>
</dependency>
这里有两个方法,使用变量 verify() 和 verifyNotNull() 来支持有用的错误消息。注意,verifyNotNull() 内置的错误消息通常就足够了,而 verify() 太一般,没有有用的默认错误消息。
使用断言进行契约式设计
_契约式设计(DbC)_是 Eiffel 语言的发明者 Bertrand Meyer 提出的一个概念,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 DBC(Eiffel 语言也是如此),但是它们创建了一种非正式的 DbC 编程风格。DbC 假定服务供应者与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应者和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。
Meyer 认为:
1.应该明确指定行为,就好像它是一个契约一样。
2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。
不管你是否同意,第一条总是对的,在大多数情况下,DbC 确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类 DbC 约束的表达式;如果无法指定约束,则你可能对要构建的内容了解得不够。
检查指令
详细研究 DbC 之前,思考最简单使用断言的办法,Meyer 称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是复杂反应的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。
检查指令是对你的代码进行补充,当你可以测试并阐明对象或程序的状态时,应该使用它。
前置条件
前置条件确保客户端(调用此方法的代码)履行其部分契约。这意味着在方法调用开始时几乎总是会检查参数(在你用那个方法做任何操作之前)以此保证它们的调用在方法中是合适的。因为你永远无法知道客户端会传递给你什么,前置条件是确保检查的一个好做法。
后置条件
后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在 return 语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。
不变性
不变性保证了必须在方法调用之间维护的对象的状态。但是,它并不会阻止方法在执行过程中暂时偏离这些保证,它只是在说对象的状态信息应该总是遵守状态规则:
1. 在进入该方法时。
2. 在离开方法之前。
此外,不变性是构造后对于对象状态的保证。
根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为 invariant() ,它在构造之后以及每个方法的开始和结束时调用。方法以如下方式调用:
assert invariant();
这样,如果出于性能原因禁用断言,就不会产生开销。
放松 DbC 检查或非严格的 DbC
尽管 Meyer 强调了前置条件、后置条件和不变性的价值以及在开发过程中使用它们的重要性,他承认在一个产品中包含所有 DbC 代码并不总是实际的。你可以基于对特定位置的代码的信任程度放松 DbC 检查。以下是放松检查的顺序,最安全到最不安全:
1. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。
2. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是当对自己的代码有信心时。
3. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 invariant() 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
4. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,某些情况下对性能要求很高,通过分析得到前置条件造成了这个瓶颈,而且你有某种合理的保证客户端不会违反前置条件(比如自己编写客户端的情况下),那么禁用前置条件检查是可接受的。
你不应该直接删除检查的代码,而只需要禁用检查(添加注释)。这样如果发现错误,就可以轻松地恢复检查以快速发现问题。
DbC + 单元测试
下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,将绕回到开头。
我们可以对这个队列做一些契约定义:
1. 前置条件(用于put()):不允许将空元素添加到队列中。
2. 前置条件(用于put()):将元素放入完整队列是非法的。
3. 前置条件(用于get()):试图从空队列中获取元素是非法的。
4. 后置条件用于get()):不能从数组中生成空元素。
5. 不变性:包含对象的区域不能包含任何空元素。
6. 不变性:不包含对象的区域必须只有空值。
下面是实现这些规则的一种方式,为每个 DbC 元素类型使用显式的方法调用。
首先,我们创建一个专用的 Exception:
// validating/CircularQueueException.java
public class CircularQueueException extends RuntimeException {public CircularQueueException(String why) {super(why);}
}
它用来报告 CircularQueue 中出现的错误:
import java.util.*;public class CircularQueue {private Object[] data;private int in = 0, // Next available storage spaceout = 0; // Next gettable object// Has it wrapped around the circular queue?private boolean wrapped = false;public CircularQueue(int size) {data = new Object[size];// Must be true after construction:assert invariant();}public boolean empty() {return !wrapped && in == out;}public boolean full() {return wrapped && in == out;}public boolean isWrapped() {return wrapped;}public void put(Object item) {precondition(item != null, "put() null item");precondition(!full(),"put() into full CircularQueue");assert invariant();data[in++] = item;if (in >= data.length) {in = 0;wrapped = true;}assert invariant();}public Object get() {precondition(!empty(),"get() from empty CircularQueue");assert invariant();Object returnVal = data[out];data[out] = null;out++;if (out >= data.length) {out = 0;wrapped = false;}assert postcondition(returnVal != null,"Null item in CircularQueue");assert invariant();return returnVal;}// Design-by-contract support methods:private static void precondition(boolean cond, String msg) {if (!cond) {throw new CircularQueueException(msg);}}private static boolean postcondition(boolean cond, String msg) {if (!cond) {throw new CircularQueueException(msg);}return true;}private boolean invariant() {// Guarantee that no null values are in the// region of 'data' that holds objects:for (int i = out; i != in; i = (i + 1) % data.length) {if (data[i] == null) {throw new CircularQueueException("null in CircularQueue");}}// Guarantee that only null values are outside the// region of 'data' that holds objects:if (full()) {return true;}for (int i = in; i != out; i = (i + 1) % data.length) {if (data[i] != null) {throw new CircularQueueException("non-null outside of CircularQueue range: " + dump());}}return true;}public String dump() {return "in = " + in +", out = " + out +", full() = " + full() +", empty() = " + empty() +", CircularQueue = " + Arrays.asList(data);}
}
in 计数器指示数组中下一个入队对象所在的位置。out 计数器指示下一个出队对象来自何处。wrapped 的flag表示入队和出队指针顺序是否变换, 为false 表示in在out之前,为true则顺序相反。当in和 out 重合时,队列为空(如果wrapped为 false )或满(如果 wrapped 为 true )。
put() 和 get() 方法调用 precondition() ,postcondition(), 和 invariant(),这些都是在类中定义的私有方法。前置precondition() 和 postcondition() 是用来阐明代码的辅助方法。
注意,precondition() 返回 void , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 precondition() 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。
postcondition() 和 invariant() 都返回一个布尔值,因此可以在 assert 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。invariant() 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 Meyer 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 invariant() 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。
dump() 帮助方法返回一个包含所有数据的字符串,而不是直接打印数据。这允许我们用这部分信息做更多事。
现在我们可以为类创建 JUnit 测试:
// validating/tests/CircularQueueTest.java
package validating;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class CircularQueueTest {private CircularQueue queue = new CircularQueue(10);private int i = 0;@BeforeEachpublic void initialize() {while(i < 5) // Pre-load with some dataqueue.put(Integer.toString(i++));}// Support methods:private void showFullness() {assertTrue(queue.full());assertFalse(queue.empty());System.out.println(queue.dump());}private void showEmptiness() {assertFalse(queue.full());assertTrue(queue.empty());System.out.println(queue.dump());}@Testpublic void full() {System.out.println("testFull");System.out.println(queue.dump());System.out.println(queue.get());System.out.println(queue.get());while(!queue.full())queue.put(Integer.toString(i++));String msg = "";try {queue.put("");} catch(CircularQueueException e) {msg = e.getMessage();∂System.out.println(msg);}assertEquals(msg, "put() into full CircularQueue");showFullness();}@Testpublic void empty() {System.out.println("testEmpty");while(!queue.empty())System.out.println(queue.get());String msg = "";try {queue.get();} catch(CircularQueueException e) {msg = e.getMessage();System.out.println(msg);}assertEquals(msg, "get() from empty CircularQueue");showEmptiness();}@Testpublic void nullPut() {System.out.println("testNullPut");String msg = "";try {queue.put(null);} catch(CircularQueueException e) {msg = e.getMessage();System.out.println(msg);}assertEquals(msg, "put() null item");}@Testpublic void circularity() {System.out.println("testCircularity");while(!queue.full())queue.put(Integer.toString(i++));showFullness();assertTrue(queue.isWrapped());while(!queue.empty())System.out.println(queue.get());showEmptiness();while(!queue.full())queue.put(Integer.toString(i++));showFullness();while(!queue.empty())System.out.println(queue.get());showEmptiness();}
}
initialize() 添加了一些数据,因此每个测试的 CircularQueue 都是部分满的。showFullness() 和 showempty() 表明 CircularQueue 是满的还是空的,这四种测试方法中的每一种都确保了 CircularQueue 功能在不同的地方正确运行。
通过将 Dbc 和单元测试结合起来,你不仅可以同时使用这两种方法,还可以有一个迁移路径—你可以将一些 Dbc 测试迁移到单元测试中,而不是简单地禁用它们,这样你仍然有一定程度的测试。
使用Guava前置条件
在非严格的 DbC 中,前置条件是 DbC 中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为 Java 在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。
谷歌的 Guava 库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里你可以看到它们的简单用法。库的设计人员建议静态导入前置条件:
import java.util.function.*;import static com.google.common.base.Preconditions.*;public class GuavaPreconditions {static void test(Consumer<String> c, String s) {try {System.out.println(s);c.accept(s);System.out.println("Success");} catch (Exception e) {String type = e.getClass().getSimpleName();String msg = e.getMessage();System.out.println(type +(msg == null ? "" : ": " + msg));}}public static void main(String[] args) {test(s -> s = checkNotNull(s), "X");test(s -> s = checkNotNull(s), null);test(s -> s = checkNotNull(s, "s was null"), null);test(s -> s = checkNotNull(s, "s was null, %s %s", "arg2", "arg3"), null);test(s -> checkArgument(s == "Fozzie"), "Fozzie");test(s -> checkArgument(s == "Fozzie"), "X");test(s -> checkArgument(s == "Fozzie"), null);test(s -> checkArgument(s == "Fozzie", "Bear Left!"), null);test(s -> checkArgument(s == "Fozzie", "Bear Left! %s Right!", "Frog"),null);test(s -> checkState(s.length() > 6), "Mortimer");test(s -> checkState(s.length() > 6), "Mort");test(s -> checkState(s.length() > 6), null);test(s ->checkElementIndex(6, s.length()), "Robert");test(s ->checkElementIndex(6, s.length()), "Bob");test(s ->checkElementIndex(6, s.length()), null);test(s ->checkPositionIndex(6, s.length()), "Robert");test(s ->checkPositionIndex(6, s.length()), "Bob");test(s ->checkPositionIndex(6, s.length()), null);test(s -> checkPositionIndexes(0, 6, s.length()), "Hieronymus");test(s -> checkPositionIndexes(0, 10, s.length()), "Hieronymus");test(s -> checkPositionIndexes(0, 11, s.length()), "Hieronymus");test(s -> checkPositionIndexes(-1, 6, s.length()), "Hieronymus");test(s -> checkPositionIndexes(7, 6, s.length()), "Hieronymus");test(s -> checkPositionIndexes(0, 6, s.length()), null);}
}
虽然 Guava 的前置条件适用于所有类型,但我这里只演示 字符串(String) 类型。test() 方法需要一个Consumer,因此我们可以传递一个 lambda 表达式作为第一个参数,传递给 lambda 表达式的字符串作为第二个参数。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给 lambda 表达式。try 块中的第二个 println() 仅在 lambda 表达式成功时才显示; 否则 catch 块将捕获并显示错误信息。注意 test() 方法消除了多少重复的代码。
每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 %s (字符串类型)替换标记。在上面的例子中,演示了checkNotNull() 和 checkArgument() 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 checkNotNull() 的返回参数, 所以你可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 Null 值的对象构造:
import static com.google.common.base.Preconditions.*;public class NonNullConstruction {private Integer n;private String s;NonNullConstruction(Integer n, String s) {this.n = checkNotNull(n);this.s = checkNotNull(s);}public static void main(String[] args) {NonNullConstruction nnc =new NonNullConstruction(3, "Trousers");}
}
checkArgument() 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 IllegalArgumentException,checkState() 用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 IllegalStateException 。
最后三个方法在失败时抛出 IndexOutOfBoundsException。checkElementIndex() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。checkPositionIndex() 确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。 checkPositionIndexes() 检查 [first_arg, second_arg] 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。
所有的 Guava 前置条件对于基本类型和对象都有必要的重载。
相关文章:

十六、代码校验(2)
本章概要 前置条件 断言(Assertions)Java 断言语法Guava 断言使用断言进行契约式设计检查指令前置条件后置条件不变性放松 DbC 检查或非常严格的 DbCDbC 单元测试 前置条件 前置条件的概念来自于契约式设计(Design By Contract, DbC), 利用断言机制…...

安卓 kotlin-supportFragmentManager报红
如果你继承baseActivity 请查看 是不是继承 AppCompatActivity...

linux中安装RocketMQ以及dashboard
前提: 需要安装jdk8 上传下面的文件到服务器中 新建目录 mkdir rocketmq 将下载后的压缩包上传到阿里云服务器或者虚拟机中去,并解压 unzip rocketmq-all-4.9.2-bin-release.zip 配置环境变量 vim /etc/profile 配置内容: export NAM…...
Android kotlin内联函数(inline)的详解与原理
一、介绍 在kotlin中,有一种函数叫内联函数,这种函数标识符是inline,但是好多人对这个函数的理解只停留在八股文中,内容函数的用法和普通函数没有区别,但是在编译原理上是有,对程序的性能有一定的影响。 二…...
林沛满---一个面试建议
在应聘一个技术职位之前,做好充分的准备无疑能大大提高成功率。这里所说的准备并不是指押题,因为有经验的面试官往往准备了海量的题库,押中的概率太低。比如我有位同事的题库里有上百道题,内容涵盖了编程、操作系统、网络、存储……...
CMake教程-第 5 步:安装和测试
CMake教程-第 5 步:安装和测试 1 CMake教程介绍2 学习步骤Step 1: A Basic Starting PointStep 2: Adding a LibraryStep 3: Adding Usage Requirements for a LibraryStep 4: Adding Generator ExpressionsStep 5: Installing and TestingStep 6: Adding Support f…...

移动应用-Android开发基础\核心知识点
Android开发基础 知识点 1 介绍了解2 系统体系架构3 四大应用组件4 移动操作系统优缺点5 开发工具6 配置工具7 下载相关资源8JDK下载安装流程9配置好SDK和JDK环境10 第一个Hello word11 AS开发前常用设置12模拟器使用运行13 真机调试14 AndroidUI基础布局15 加载展示XML布局16…...
Java读取并转换字符串中的浮点数
在写Android接收蓝牙数据的时候,由于传过来的蓝牙数据转换后都为字符串格式,但是需要从其中提取出来浮点数,所以通过查阅资料写出了从字符串中提取并转换为浮点数的方法,特记录下来以供参考。 目录 原始数据内容 提取字符串中的…...
SQL: 索引原理与创建索引的规范
SQL 索引是一种数据结构,用于加速数据库查询操作。它通过在表的列上创建索引,提供了一种快速查找数据的方法,减少了数据库的扫描和比较操作,从而提高了查询性能。索引根据其实现方式可以分为多种类型,如 B-树索引、哈希…...

基于STM32_DS18B20单总线传感器驱动
基于STM32_DS18B20单总线传感器驱动 文章目录 基于STM32_DS18B20单总线传感器驱动前言一、BS18B20?二、原理1.复位与检验2.基本命令3.唯一ROM识别码4.温度转换 三、驱动代码四、注意事项 前言 本文以一款典型的单总线传感器及其驱动——DS18B20为例,简单…...

目标识别项目实战:基于Yolov7-LPRNet的动态车牌目标识别算法模型(三)
前言 目标识别如今以及迭代了这么多年,普遍受大家认可和欢迎的目标识别框架就是YOLO了。按照官方描述,YOLOv8 是一个 SOTA 模型,它建立在以前 YOLO 版本的成功基础上,并引入了新的功能和改进,以进一步提升性能和灵活性…...
springboot线程池创建与使用
/*** author: zcs* Title: TaskPoolConfig* Description: 线程池配置* date: 2023/10/11 17:52*/ Component public class TaskPoolConfig {Bean(name "threadPoolTaskExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor taskExecutor new ThreadP…...

UDP和TCP特点(部分)对比:
传输层的两个主要协议:TCP 和 UDP UDP和TCP特点(部分)对比: UDP:无连接, 不可靠传输, 面向数据报, 全双工。 TCP:有连接, 可靠传输, 面向字节流…...

PostMan环境变量、全局变量、动态参数使用
一、环境准备 postmanmoco [{"description": "登录认证","request": {"uri": "/login","method": "post","forms": {"user": "admin","password": "a123…...

服务器数据恢复-服务器硬盘指示灯黄灯闪烁的数据恢复案例
服务器数据恢复环境: 服务器面板上的硬盘指示灯显示黄色是一种警告,提示指示灯对应的服务器硬盘已经被服务器识别出存在故障,硬盘即将下线。如果出现这种情况,建议服务器管理员/运维人员及时用完好的硬盘替换显示黄色指示灯对应的…...
ts 分发
在 TypeScript 中,"分发"(distributive)是指在条件类型中的联合类型上自动进行类型推断的机制。当使用条件类型操作联合类型时,TypeScript 会自动将联合类型中的每个成员都应用该条件类型。 下面是一个示例:…...
SQL中的group by使用注意事项
在 SQL 中,GROUP BY 语句用于将查询结果按照指定的列进行分组,并对每个分组计算聚合函数(如 SUM、AVG、COUNT 等)的值。一般情况下,如果查询中包含聚合函数,那么就需要使用 GROUP BY 语句将查询结果按照指定…...

矩阵剪辑系统源码----pc/手机端双开发
剪辑系统,剪辑矩阵系统,剪辑矩阵系统主要是针对抖音、快手、bili平台的一个工具,今天就来给大家交流一下这 个产品的主要功能以及构成。剪辑矩阵系统,矩阵剪辑系统源码-这产品主要功能就是一个视频剪辑功能,这个视频剪…...

0基础学习VR全景平台篇 第106篇:认识调色软件Lightroom
上课!全体起立~ 大家好,欢迎观看蛙色官方系列全景摄影课程! 正式开讲之前需要先引出一个概念:到底什么是调色? 比如说上面这张照片,你可能会具体的指出照片中的元素有天空、山脉、草地等…… 如果我们跳出我们的固…...

React如何优化减少组件间的重新Render
目前写了不少React的项目,发现React有些特点更灵活和注重细节,很多东西需要有一定的内功才能掌握好;比如在项目中常常遇到的组件重复渲染,有时候组件重复渲染如果内容是纯文本,不打印日志就不容易发现重复渲染了&#…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...