异步重试模式

当您有一段经常失败且必须重试的代码时,此Java 7/8库提供了丰富且不引人注目的API,并提供了针对此问题的快速且可扩展的解决方案:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
RetryExecutor executor = new AsyncRetryExecutor(scheduler).retryOn(SocketException.class).withExponentialBackoff(500, 2).     //500ms times 2 after each retrywithMaxDelay(10_000).               //10 secondswithUniformJitter().                //add between +/- 100 ms randomlywithMaxRetries(20);

现在,您可以运行任意代码块,并且库将为您重试该代码块,以防它抛出SocketException

final CompletableFuture<Socket> future = executor.getWithRetry(() ->new Socket("localhost", 8080)
);future.thenAccept(socket ->System.out.println("Connected! " + socket)
);

请仔细看! getWithRetry()不会阻止。 它立即返回CompletableFuture并异步调用给定的函数。 您可以一次收听该Future甚至是多个Future ,并同时进行其他工作。 因此,这段代码的作用是:尝试连接到localhost:8080 ,如果由于SocketException而失败,它将在500毫秒后重试(带有一些随机抖动),每次重试后的延迟加倍,但不超过10秒。

等效但更简洁的语法:

executor.getWithRetry(() -> new Socket("localhost", 8080)).thenAccept(socket -> System.out.println("Connected! " + socket));

这是您可能期望的示例输出:

TRACE | Retry 0 failed after 3ms, scheduled next retry in 508ms (Sun Jul 21 21:01:12 CEST 2013)
java.net.ConnectException: Connection refusedat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]//...TRACE | Retry 1 failed after 0ms, scheduled next retry in 934ms (Sun Jul 21 21:01:13 CEST 2013)
java.net.ConnectException: Connection refusedat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]//...TRACE | Retry 2 failed after 0ms, scheduled next retry in 1919ms (Sun Jul 21 21:01:15 CEST 2013)
java.net.ConnectException: Connection refusedat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]//...TRACE | Successful after 2 retries, took 0ms and returned: Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]Connected! Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]

想象一下,您连接到两个不同的系统,一个系统速度 ,第二个系统不可靠并且经常失败:

CompletableFuture<String> stringFuture = executor.getWithRetry(ctx -> unreliable());
CompletableFuture<Integer> intFuture = executor.getWithRetry(ctx -> slow());stringFuture.thenAcceptBoth(intFuture, (String s, Integer i) -> {//both done after some retries
});

当缓慢且不可靠的系统最终都无任何故障地答复时,异步异步执行thenAcceptBoth()回调。 类似地(使用CompletableFuture.acceptEither() ),您可以同时异步调用两个或多个不可靠的服务器,并在重试几次后第一个成功时会收到通知。

我对此不够强调–重试是异步执行的,并且有效地使用了线程池,而不是盲目入睡。

基本原理

通常我们被迫重试给定的代码段,因为它失败了,我们必须再次尝试,通常会稍有延迟以节省CPU。 这项要求非常普遍,并且在Spring Batch中通过RetryTemplate类提供重试支持的现成通用实现很少RetryTemplate 。 但是几乎没有其他类似的方法( [1] , [2] )。 所有这些尝试(我敢打赌,你们中的许多人自己都实现了类似的工具!)遇到了相同的问题-它们正在阻塞,从而浪费了大量资源,并且扩展性不好。

本身并不坏因为它使编程模型更加简单-库负责重试,而您只需要等待比平常更长的返回值即可。 但是,这不仅会造成泄漏的抽象(由于重试和延迟,通常非常快的方法通常会突然变慢),而且还会浪费宝贵的线程,因为这种工具将在重试之间花费大部分时间。 因此
创建了Async-Retry实用程序,该实用程序针对Java 8 (现有Java 7 backport )并解决了上述问题。

主要的抽象是RetryExecutor ,它提供了简单的API:

public interface RetryExecutor {CompletableFuture<Void> doWithRetry(RetryRunnable action);<V> CompletableFuture<V> getWithRetry(Callable<V> task);<V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);<V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task);
}

不必担心RetryRunnableRetryCallable –为方便起见,它们允许使用已检查的异常,并且在大多数情况下,我们还是会使用lambda表达式。

请注意,它返回CompletableFuture 。 我们不再假装调用错误方法很快。 如果库遇到异常,它将使用预先配置的退避延迟重试我们的代码块。 调用时间将从几毫秒飞涨到几秒钟。 CompletableFuture清楚地表明了这一点。 而且,它不是一个愚蠢的java.util.concurrent.Future我们都知道– Java 8中的CompletableFuture非常强大 ,最重要的是–默认情况下是非阻塞的。

如果您毕竟需要阻止结果,只需在Future对象上调用.get()

基本API

该API非常简单。 您提供了一个代码块,该库将多次运行它,直到它正常返回为止,而不是引发异常。 它也可能在重试之间设置可配置的延迟:

RetryExecutor executor = //...executor.getWithRetry(() -> new Socket("localhost", 8080));

一旦成功连接到localhost:8080将解析返回的CompletableFuture<Socket> localhost:8080 。 (可选)我们可以使用RetryContext来获取额外的上下文,例如当前正在执行的重试:

executor.getWithRetry(ctx -> new Socket("localhost", 8080 + ctx.getRetryCount())).thenAccept(System.out::println);

该代码比看起来更聪明。 在第一次执行时, ctx.getRetryCount()返回0 ,因此我们尝试连接到localhost:8080 。 如果失败,则下一个重试将尝试localhost:80818080 + 1 ),依此类推。 而且,如果您意识到所有这些操作都是异步发生的,则可以扫描多台计算机的端口,并收到有关每个主机上第一个响应端口的通知:

Arrays.asList("host-one", "host-two", "host-three").stream().forEach(host ->executor.getWithRetry(ctx -> new Socket(host, 8080 + ctx.getRetryCount())).thenAccept(System.out::println));

对于每个主机, RetryExecutor将尝试连接到端口8080,并尝试使用更高的端口。

getFutureWithRetry()需要特别注意。 我想重试已经返回CompletableFuture<V> :例如异步HTTP调用的结果:

private CompletableFuture<String> asyncHttp(URL url) { /*...*/}//...final CompletableFuture<CompletableFuture<String>> response = executor.getWithRetry(ctx -> asyncHttp(new URL("http://example.com")));

asyncHttp()传递给getWithRetry()将产生CompletableFuture<CompletableFuture<V>> 。 与它一起工作不仅很尴尬,而且还很麻烦。 该库将仅调用asyncHttp()并仅在失败时重试,但在返回时不重试
CompletableFuture<String>失败。 解决方案很简单:

final CompletableFuture<String> response =executor.getFutureWithRetry(ctx ->asyncHttp(new URL("http://example.com")));

在这种情况下, RetryExecutor将理解从asyncHttp()返回的内容实际上只是一个Future并且将(异步)等待结果或失败。 该库功能更强大,因此让我们深入了解:

配置选项

通常,您可以配置两个重要因素: RetryPolicy ,用于控制是否应进行下一次重试;以及Backoff (可以有选择地增加后续重试之间的延迟)。

默认情况下, RetryExecutor在每个Throwable上无限地重复用户任务,并在RetryExecutor重试之间增加1秒的延迟。

创建

RetryExecutor默认实现是AsyncRetryExecutor ,您可以直接创建:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();RetryExecutor executor = new AsyncRetryExecutor(scheduler);//...scheduler.shutdownNow();

唯一需要的依赖关系是JDK的标准ScheduledExecutorService 。 在许多情况下,一个线程就足够了,但是如果您要同时处理数百个或更多任务的重试,请考虑增加池大小。

请注意, AsyncRetryExecutor不会关闭ScheduledExecutorService 。 这是一个有意识的设计决策,将在后面进行解释。

AsyncRetryExecutor几乎没有其他构造函数,但是在大多数情况下,更改类的行为对于with*()方法链接的调用最为方便。 您将看到大量以此方式编写的示例。 稍后,我们将仅使用executor引用而不定义它。 假设它是RetryExecutor类型。

重试政策

例外情况

默认情况下,从用户任务抛出的每个Throwable (特殊AbortRetryException除外)都会导致重试。 显然,这是可配置的。 例如,在JPA中,您可能想重试由于OptimisticLockException而失败的事务-但其他所有异常都应立即失败:

executor.retryOn(OptimisticLockException.class).withNoDelay().getWithRetry(ctx -> dao.optimistic());

其中dao.optimistic()可能会引发OptimisticLockException 。 在这种情况下,您可能不希望重试之间有任何延迟,以后再说。 如果您不喜欢在每个Throwable上重试的默认设置,只需使用retryOn()来限制它:

executor.retryOn(Exception.class)

当然,也可能需要相反的操作–中止重试并在抛出某些异常的情况下立即失败而不是重试。 就这么简单:

executor.abortOn(NullPointerException.class).abortOn(IllegalArgumentException.class).getWithRetry(ctx -> dao.optimistic());

显然,您不想重试NullPointerExceptionIllegalArgumentException因为它们指示编程错误,而不是瞬时失败。 最后,您可以结合使用重试和中止策略。 如果出现任何retryOn()异常(或子类),则用户代码将重试,除非它应该abortOn()指定的异常。 例如,我们想重试每个IOExceptionSQLException但是如果遇到FileNotFoundExceptionjava.sql.DataTruncation则中止(顺序无关):

executor.retryOn(IOException.class).abortIf(FileNotFoundException.class).retryOn(SQLException.class).abortIf(DataTruncation.class).getWithRetry(ctx -> dao.load(42));

如果这还不够,您可以提供将在每次失败时调用的自定义谓词:

executor.abortIf(throwable ->throwable instanceof SQLException &&throwable.getMessage().contains("ORA-00911"));

最大重试次数

中断重试“循环”的另一种方法(请记住此过程是异步的,没有阻塞循环 )是通过指定最大重试次数:

executor.withMaxRetries(5)

在极少数情况下,您可能希望禁用重试,并且几乎不利用异步执行。 在这种情况下,请尝试:

executor.dontRetry()

重试之间的延迟(退避)

有时需要在失败后立即重试(请参阅OptimisticLockException示例),但是在大多数情况下,这是一个坏主意。 如果您无法连接到外部系统,请稍等片刻,然后再尝试下一次尝试。 您可以节省CPU,带宽和其他服务器的资源。 但是有很多选择要考虑:

  • 我们应该以固定的间隔重试还是增加每次失败后的延迟 ?
  • 轮候时间是否应该有上限和下限?
  • 我们是否应该添加随机“抖动”来延迟时间以及时分散许多任务的重试?

该库回答了所有这些问题。

重试间隔固定

默认情况下,每次重试之前都有1秒的等待时间。 因此,如果初始尝试失败,则将在1秒后执行第一次重试。 当然,我们可以将默认值更改为200毫秒:

executor.withFixedBackoff(200)

如果我们已经在此处,则默认情况下,执行用户任务后将应用退避。 如果用户任务本身消耗一些时间,则重试的频率将降低。 例如,重试延迟为RetryExecutor毫秒,用户任务失败所需的平均时间约为50毫秒, RetryExecutor将每秒重试约4次(50毫秒+ RetryExecutor毫秒)。 但是,如果要将重试频率保持在更可预测的水平,则可以使用fixedRate标志:

executor.withFixedBackoff(200).withFixedRate()

这类似于ScheduledExecutorService “固定速率”与“固定延迟”方法。 顺便说一句,不要期望RetryExecutor非常精确,这是最好的,但是在很大程度上取决于前面提到的ScheduledExecutorService准确性。

重试之间的间隔呈指数增长

它可能是一个活跃的研究主题,但总的来说,您可能希望随着时间的推移扩展重试延迟,假设如果用户任务多次失败,我们应该减少尝试次数。 例如,假设我们从100ms延迟开始,直到进行第​​一次重试为止,但是如果该尝试也失败了,我们应该再等待两次(200ms)。 再过400毫秒,800毫秒……您就会明白:

executor.withExponentialBackoff(100, 2)

这是一个指数函数,可以快速增长。 因此,将最大退避时间设置在某个合理的水平(例如10秒)非常有用:

executor.withExponentialBackoff(100, 2).withMaxDelay(10_000)      //10 seconds

随机抖动

在严重停机期间经常观察到的一种现象是系统趋于同步。 想象一下一个繁忙的系统突然停止响应。 成百上千的请求失败并被重试。 这取决于您的退避,但是默认情况下,所有这些请求都会在一秒钟后精确重试,从而在某个时间点产生大量流量。 最后,此类故障会传播到其他系统,这些系统又会进行同步。

为避免此问题,随着时间的推移扩展重试,使负载平坦化是很有用的。 一个简单的解决方案是添加随机抖动来延迟时间,以便并非所有请求都计划在完全相同的时间重试。 您可以在均匀抖动(随机值从-100ms到100ms)之间进行选择:

executor.withUniformJitter(100)     //ms

…和成比例的抖动,将延迟时间乘以随机因子,默认情况下为0.9到1.1(10%):

executor.withProportionalJitter(0.1)        //10%

您还可以对延迟时间设置严格的下限,以避免安排较短的重试时间:

executor.withMinDelay(50)   //ms

实施细节

该库是在考虑Java 8的情况下构建的,以利用lambda和新的CompletableFuture抽象(但是存在具有Guava依赖项的Java 7 port )。 它在下面使用ScheduledExecutorService来运行任务并计划将来的重试-这样可以最大程度地利用线程。

但是真正有趣的是整个库是完全不变的,根本没有单个可变字段。 起初这可能是违反直觉的,例如以以下简单代码示例为例:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();AsyncRetryExecutor first = new AsyncRetryExecutor(scheduler).retryOn(Exception.class).withExponentialBackoff(500, 2);AsyncRetryExecutor second = first.abortOn(FileNotFoundException.class);AsyncRetryExecutor third = second.withMaxRetries(10);

似乎所有with*()方法或retryOn() / abortOn()方法retryOn()现有的执行程序变异。 但是事实并非如此,每次配置更改都会创建一个新实例 ,而旧实例则保持不变。 因此,例如,当first执行者将重试FileNotFoundExceptionsecondthird执行者则不会。 但是它们都共享同一个scheduler 。 这就是AsyncRetryExecutor不关闭ScheduledExecutorService (甚至没有任何close()方法)的原因。 由于我们不知道有多少个AsyncRetryExecutor副本指向同一调度程序,因此我们甚至不尝试管理其生命周期。 但是,这通常不是问题(请参见下面的Spring集成 )。

您可能想知道,为什么这么笨拙的设计决定? 有以下三个原因:

  • 在编写并发代码时,不变性可以大大降低多线程错误的风险。 例如, RetryContext保存重试次数。 但是,我们无需更改变量,而只需创建具有递增但final计数器的新实例(副本)即可。 没有比赛条件或可见性。
  • 如果给您现有的RetryExecutor几乎完全是您想要的,但是您需要进行一些细微调整,则只需调用executor.with...()并获取一个新副本。 您不必担心使用同一执行程序的其他地方(请参阅: Spring集成以获取更多示例)
  • 如今,函数式编程和不变的数据结构非常流行

注意: AsyncRetryExecutor 标记为final ,您可以通过将其子类化并添加可变状态来打破不变性。 请不要这样做,子类只允许更改行为。

依存关系

该库需要Java 8和SLF4J进行记录。 Java 7端口还取决于Guava 。

Spring整合

如果您即将在Spring中使用RetryExecutor ,请放心,但是配置API可能对您不起作用。 Spring通过大量的设置来促进(或用于促进)可变服务的约定。 在XML中,您可以定义bean并在其上调用setter(通过<property name="..."/> )。 该约定假定存在变异设置器。 但是我发现这种方法在某些情况下容易出错并且违反直觉。

假设我们全局定义了org.springframework.transaction.support.TransactionTemplate bean并将其注入到多个位置。 大。 现在有一个请求,它的超时要求略有不同:

@Autowired
private TransactionTemplate template;

后来在同一个班级:

final int oldTimeout = template.getTimeout();
template.setTimeout(10_000);
//do the work
template.setTimeout(oldTimeout);

此代码在许多级别上都是错误的! 首先,如果发生故障,我们将永远不会恢复oldTimeout 。 好了, finally救了。 但还要注意我们如何更改全局共享的TransactionTemplate实例。 谁知道不知道更改配置的其他几个bean和线程将要使用它?

即使您确实想全局更改事务超时,也足够公平,但是这样做仍然是错误的方法。 private timeout字段不是volatile ,因此对其进行的更改可能对其他线程可见或不可见。 真是一团糟! 同样的问题在许多其他类(如JmsTemplate

你知道我要去哪里吗? 只需创建一个不变的服务类,并在需要时通过创建副本来安全地对其进行调整。 现在,使用此类服务​​同样简单:

@Configuration
class Beans {@Beanpublic RetryExecutor retryExecutor() {return new AsyncRetryExecutor(scheduler()).retryOn(SocketException.class).withExponentialBackoff(500, 2);}@Bean(destroyMethod = "shutdownNow")public ScheduledExecutorService scheduler() {return Executors.newSingleThreadScheduledExecutor();}}

嘿! 进入21世纪,我们在Spring不再需要XML。 Bootstrap也很简单:

final ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);
final RetryExecutor executor = context.getBean(RetryExecutor.class);
//...
context.close();

如您所见,将现代的,不可变的服务与Spring集成非常简单。 顺便说一句,如果您在设计自己的服务时没有准备好进行如此大的更改,请至少考虑使用构造函数注入 。

到期

该库包含大量的单元测试。 但是,尚未在任何生产代码中使用它,并且该API可能会更改。 当然,我们鼓励您提交错误,功能请求和提取请求 。 它是在考虑到Java 8的情况下开发的,但是Java 7 backport存在一些更详细的API和强制性Guava依赖关系( ListenableFuture而不是
Java 8的CompletableFuture )。

GitHub上的完整源代码。

参考:来自Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz的异步重试模式 。

翻译自: https://www.javacodegeeks.com/2013/08/asynchronous-retry-pattern.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367634.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端 ---jQuery的补充

15-jQuery补充 jquery内容补充 jquery除了咱们上面讲解的常用知识点之外&#xff0c;还有jquery 插件、jqueryUI知识点 jqueryUI 官网&#xff1a; https://jqueryui.com/ jqueryUI 中文网&#xff1a; http://www.jqueryui.org.cn/ jquery插件内容包含 官网demo&#xff1a; h…

[软件工程]自我介绍----一个小菜鸡的自我介绍(C++版 手动偷笑~)

#include “iostream” using namespace std; class Lee : protected MyMother,protected MyFather { private:string ChineseName("李怡龙");string EnglishName("Lee");int age 20;string university("青海大学计算机系");string level("…

用Java编写Hadoop MapReduce任务

尽管Hadoop框架本身是使用Java创建的&#xff0c;但MapReduce作业可以用许多不同的语言编写。 在本文中&#xff0c;我将展示如何像其他Java项目一样&#xff0c;基于Maven项目在Java中创建MapReduce作业。 准备示例输入 让我们从一个虚构的商业案例开始。 在这种情况下&#…

java 大二学期总结报告_大二学生自我总结「」

大二学生导师工作总结转眼间&#xff0c;我们的大二学年就这样结束了&#xff0c;在迎接新的一学期前我们来写一份自我总结吧。下面是小编搜集整理的大二学生自我总结&#xff0c;欢迎阅读。更多资讯尽在自我总结栏目!大二学生自我总结回顾大学二年,通过良师的教导和自身的刻苦…

windows下揪出java程序占用cpu很高的线程

背景 天天搞java&#xff0c;这些监控也都知道&#xff0c;用过&#xff0c;但也没往细里追究。因为也没碰见这种问题&#xff0c;这次还是静下来走一遍流程吧。与网上基本一致&#xff0c;不过我区分了下linux和windows的不一样。我感觉基本是程序写成死循环了或者大对象分配多…

jquery -input事件

input输入框的change事件&#xff0c;要在input失去焦点的时候才会触发 $(input[namemyInput]).change(function() { ... }); 在输入框内容变化的时候不会触发change&#xff0c;当鼠标在其他地方点一下才会触发用下面的方法会生效&#xff0c;input [html] view plain copy$(&…

Bootstrap中的下拉列表

下拉列表&#xff08;select&#xff09;注意&#xff0c;很多原生选择菜单单 - 即在 Safari 和 Chrome 中 - 的圆角是无法通过修改 border-radius 属性来改变的。复制<select class"form-control"><option>1</option><option>2</option&…

Spring集成:轻量级集成方法

当今的应用程序希望能够访问企业环境中的所有业务&#xff0c;而无需考虑与绝望的系统无缝集成的应用程序技术。 可以通过使用中间件技术对各种系统进行布线来实现这种集成。 集成平台使应用程序可以相互共享信息的环境&#xff0c;从而使体系结构具有高度的互操作性。 Spring…

动态REM

什么是rem&#xff1f; rem是相对于根元素html字体大小来计算的&#xff0c;即( 1rem html字体大小 ) rem和em区别&#xff1f; rem:&#xff08;root em&#xff0c;根em&#xff09;根元素的fort-size的大小计算em&#xff1a;相对长度单位&#xff0c;相对于当前对象内文本…

java教学楼的属性_java设计一个父类建筑物building,由它派生出教学楼类classroom,然后采用一些数据进行测试....

public class Building {public String bname;//建筑物名称public int floors;//代表总层数public double area;//代表总面积public Building(){}public Building(String bname, int floors, double area) {this.bname bname;this.floors floors;this.area area;}}public cl…

Bootstrap中的列表的使用

列表无序列表排列顺序无关紧要的一列元素。Lorem ipsum dolor sit ametConsectetur adipiscing elitInteger molestie lorem at massaFacilisis in pretium nisl aliquetNulla volutpat aliquam velitPhasellus iaculis nequePurus sodales ultriciesVestibulum laoreet portti…

Vue项目中使用HighChart

记&#xff1a;初次在Vue项目中使用 HighChart 的时候要走的坑 感谢这位哥们的思路 传送门 Vue-cli项目使用 npm install highcharts --save 让我们看看 highcharts 的使用方法&#xff0c;传送门 Highcharts.chart(targetTag, option) 重启项目&#xff0c;建立chart.vue文件 …

form字体和颜色java安卓开发_Android 修改App中默认TextView的字体和颜色

一、别人怎么做来源http://stackoverflow.com/questions/3078081/setting-global-styles-for-views-in-androidActually, you can set a default style for TextViews (and most other built-in widgets) without needing to do a custom java class or setting the style indi…

使用Dozer框架进行Bean操作

介绍 如您所知&#xff0c;您可以在任何操作系统中将文件或文件夹从源位置复制到目标位置。 您是否考虑过复制基本上是POJO的java对象&#xff1f; 在许多情况下&#xff0c;您需要将源bean的内容复制到目标bean。 我不关心对象的拷贝构造函数&#xff0c;浅拷贝或深拷贝或克隆…

js如何把ajax获取的值返回到上层函数里?

我现在有个系统在用户点击浏览时&#xff0c;系统会以ajax的方式从后台获取查看的链接&#xff0c;并以window.open的方式打开&#xff0c;但因为现在多数的浏览器都会拦截window.open打开的地址&#xff0c;而window.location.href的方式又无法在浏览器新窗口打开&#xff0c;…

接口IDisposable的用法

C#的每一个类型都代表一种资源&#xff0c;而资源又分为两类&#xff1a; 托管资源 由CLR管理分配和释放的资源&#xff0c;即从CLR里new出来的对象。非托管资源 不受CLR管理的对象&#xff0c;如Windows内核对象&#xff0c;或者文件、数据库连接、套接字、COM对象等。如果类…

图形处理:betweeness中心性– neo4j的密码与graphstream

上周&#xff0c; 我写了关于中间性中心性算法以及使用graphstream 理解它的尝试 &#xff0c;在阅读源代码时&#xff0c;我意识到我可以使用neo4j的所有最短路径算法将某些东西放在一起。 概括地说&#xff0c;中间性中心度算法用于确定图中节点的负载和重要性。 在与Jen讨…

java tongpaiyu danliantiao_java版的汉字转拼音程序

[文件] ChiToLetter.javaimport java.io.UnsupportedEncodingException;import java.util.HashSet;import java.util.Iterator;import java.util.Set;import java.util.Vector;//实现汉字向拼音的转化//-----------------------------------------------------设计人:牛文平// …

小程序之Tab切换

小程序越来越火了&#xff0c;作为一名&#xff0c;额 有理想的攻城狮&#xff0c;当然要紧跟互联网时代的步伐啦&#xff0c;于是我赶紧抽时间学习了一下小程序的开发&#xff0c;顺便把经验分享给大家。 对于申请账号以及安装开发工具等&#xff0c;大家可以看官网&#xff…

configparser logging

configparser模块 # 该模块适用于配置文件的格式与windows ini文件类似&#xff0c;可以包含一个或多个节&#xff08;section&#xff09;&#xff0c;每个节可以有多个参数&#xff08;键值&#xff09;。 import configparser config configparser.ConfigParser() c…