CompletableFuture异步

创建CompletableFuture异步

异步就是使用的线程,创建的时候使用 new 一个 Runnable,默认底层使用ForkJoinPool线程池。
CompletableFuture会从全局ForkJoinPool.commonPool()线程池获取来执行这些任务,当然, 你也可以创建一个线程池,并将其传递给runAsync() 和supplyAsync()方法,以使它们在从你指定的线程池获得的线程中执行任务。
CompletableFuture API中的所有方法都有两种变体,一种是接受传入的Executor参数作为指定的线程池,而另一个则使用默认的线程池(ForkJoinPool.commonPool())

为快速创建、链接依赖和结合多个Future提供了大量的便利方法。
提供了适用于各种开发场景的回调函数,它还提供了非常全面的异常处理支持。
它无疑衔接和亲和lJava 8 提供的Lambda表达式和Stream - API。
真正意义上的异步编程,把异步编程和函数式编程、响应式编程多种高阶编程思维集于一身,设计上更优雅。

runAsync 无返回值

如果你要异步运行某些耗时的后台任务,并且不想从任务中返回任何内容,则可以使用CompletableFuture.runAsync()方法。它接受一个Runnable接口实现类对象,方法返回CompletableFuture对象

static CompletableFuture<Void> runAsync(Runnable runnable)
// 使用Runnable匿名内部类 创建
    CompletableFuture.runAsync(new Runnable() {
      @Override
      public void run() {
        // 具体逻辑
      }
    });
// Lambda表达式 创建
    CompletableFuture.runAsync(()-> {
        // 具体逻辑
    });
// runAsync()重载
static CompletableFuture<Void> runAsync(Runnable runnable)
static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)


//创建包含固定4个线程的线程池
  ExecutorService executor = Executors.newFixedThreadPool(4);
        
 CompletableFuture.runAsync(()  -> {
         逻辑
        },executor);

supplyAsync 有返回值

CompletableFuture.runAsync()开启不带返回结果异步任务。但是,如果你想从后台的异步任务中返回一个结果怎么办?此时,CompletalbeFuture.supplyAsync()是你的最好的选择了

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
          		  具體邏輯
                return 返回結果;
            }
        });
// Lambda表达式 创建
    CompletableFuture<U> completableFuture=CompletableFuture.suplyAsync(()-> {
        // 具体逻辑
         return 返回結果;
    });
如果想要获取上述代码中future中的结果,可以调用
completableFuture.get()方法,get()将阻塞,直到future完成,还会存在异常
也可以使用join 获取结果  使用方法 completableFuture.join(); 这种方式无异常

總結:使用时,我们发现,get() 抛出检查时异常,需要程序必须处理;而join() 方法抛出运行时异常,程序可以不处理。所以, join()更适合用在流式编程中
// spplyAsync()重载
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)


//创建包含固定4个线程的线程池
  ExecutorService executor = Executors.newFixedThreadPool(4);
        
 CompletableFuture.runAsync(()  -> {
        // 具体逻辑
         return 返回結果;
     },executor);

异步->回调

thenApply

消费之前的异步调用的返回结果,并且消费完 返回结果
使用thenApply()方法可以处理和转换CompletableFuture的结果,它以Function<T, U>作为参数。 Function<T, U>是一个函数式接口,表示一个转换操作,它接受类型T的参数并产生类型R的结果

CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
 public static void main(String[] args) throws ExecutionException, InterruptedException {

        //需求:异步读取filter_wors.txt文件中的内容,读取完成后,把内容转换成数组(敏感词数组),异步任务返回敏感词数组
        CommonUtils.printTheadLog("main start");

        CompletableFuture<String> readFileFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                CommonUtils.printTheadLog("开始读取filter_wors.txt文件");
                String readFile = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt");
                return readFile;
            }
        });
        //回调函数 content只上一步异步返回的结果
        CompletableFuture<String[]> splitWordFuture = readFileFuture.thenApply(content -> {
            CommonUtils.printTheadLog("开始读取敏感词组");
            String[] split = content.split(",");
            return split;
        });


//链式

/**CompletableFuture<String[]> filterWordsFuture = CompletableFuture.supplyAsync(() -> {
    String filterWordContent = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt");
    return filterWordContent;
}).thenApply(content -> {
    String[] filterWords = content.split(",");
    return filterWords;
});
content 就是异步处理结果
*/



        CommonUtils.printTheadLog("main lock");
        String[] strings = splitWordFuture.get();
        String splitWord = Arrays.toString(strings);
        CommonUtils.printTheadLog("splitWord ="+splitWord);
     }

thenAccept

如果不想从回调函数返回结果,而只想在Future完成后运行一些代码,则可以使用thenAccpet(),这些方法是一个Consumer<? super T>,它可以对异步任务的执行结果进行消费使用,方法返回CompletableFuture

该方法通常用作回调链中的最后一个回调 进行消费异步中的结果

CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public static void main(String[] args) throws InterruptedException {

        CommonUtils.printTheadLog("main start");

        //链式操作
        CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                CommonUtils.printTheadLog("读取filter_words.txt文件");
                String readFile = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt");
                return readFile;            }
        }).thenApply( content -> {
            CommonUtils.printTheadLog("把文件内容转换成敏感词数组");
            String[] split = content.split(",");
            return split;
        }).thenAccept( fildWord ->{
             CommonUtils.printTheadLog(Arrays.toString(fildWord));
        });

        //让异步任务执行完毕
        Thread.sleep(4000);

        CommonUtils.printTheadLog("main stop");
    }



也可以使用链式编程+lambda方式
 CompletableFuture.supplyAsync(()->{
//逻辑
return split;

}).thenApply( content -> {
//消费逻辑
});

thenRun

前面我们已经知道,通过thenApply(Function<T,R>)对链式操作中的上一个异步任务的结果进行转换,返回一个新的结果; 通过thenAccpet(Consumer)对链式操作中的上一个异步任务的结果进行消费,不返回新结果;

如果我们只是想从CompletableFuture的链式操作得到一个完成的通知,甚至都不使用上一个链式操作的结果,那么CompletableFuture.thenRun()会是你最佳的选择,它需要一个Runnable并返回CompletableFuture

CompletableFuture<Void> thenRun(Runnable action);
 public static void main(String[] args) {

        //需求:仅仅想知道 filter_words.txt 的文件是否读取完成
        CommonUtils.printTheadLog("main start");

        CompletableFuture.supplyAsync(() -> {
            CommonUtils.printTheadLog("开始读取filter_words.txt");
            String readFile = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt");
            return readFile;
        }).thenRun(() -> {
            CommonUtils.printTheadLog("读取filter_words.txt文件完成");
        });

        CommonUtils.printTheadLog("main continue");
        CommonUtils.sleepSecond(4);
        CommonUtils.printTheadLog("main end");
    }

回调的方式的变异体

CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) 
// 回调方的异步变体(异步回调)
CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) 
// thenAccept和其异步回调
CompletableFuture<Void> thenAccept(Consumer<? super T> action)
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action Executor executor)
// thenRun和其异步回调
CompletableFuture<Void> thenRun(Runnable action)
CompletableFuture<Void> thenRunAsync(Runnable action)
CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor)

/**变异体就是 多了一个参数  那个参数是自己控制使用哪个线程池去运行这个异步回调的方法的
跟创建异步的方法的变异体一样的
*/

异步任务之编排

编排2个依赖关系的异步任务 thenCompose()

CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)

//变异体
CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn)CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor)

//错误的示范 为了证明要使用 thenCompose()方法

public static CompletableFuture<String> readFileFuture(String fileName) {
    return CompletableFuture.supplyAsync(()->{
        String filterWordsContent = CommonUtils.readFile(fileName);
        return filterWordsContent;
    });
}

public static CompletableFuture<String[]> splitFuture(String content) {
    return CompletableFuture.supplyAsync(()->{
        String[] filterWords = content.split(",");
        return filterWords;
    });
}


public static void main(String[] args) throws ExecutionException, InterruptedException {
        //编排2个依赖关系的异步任务thenCompose()

        //使用thenApply
        CompletableFuture<CompletableFuture<String[]>> future = readFileFuture("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt").thenApply(content -> {
            return splitFuture(content);
        });

        CompletableFuture<String[]> completableFuture = future.get();

        System.out.println("completableFuture = " + completableFuture);

        String[] content = completableFuture.get();

        System.out.println("content = " + content);
    }
/**在上面的案例中,thenApply(Function<T,R>)中Function回调会对上
一步异步结果转换后得到一个简单值,但现在这种情况下,
如果结果是嵌套的CompletableFuture,所以这是不符合预期的
*/
//正确方式 CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
public static void main(String[] args) throws ExecutionException, InterruptedException {
        //异步读取filter_words.txt文件中的内容,读取完成后,转换成敏感词数组,主线程获取结果打印输出这个数组
        //thnCompose
        CommonUtils.printTheadLog("main start");
        CompletableFuture<String[]> future = CompletableFuture.supplyAsync(() -> {
            CommonUtils.printTheadLog("开始读取filter_words.txt");
            String readFile = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt");
            return readFile;
        }).thenCompose(content -> CompletableFuture.supplyAsync(() -> {
            CommonUtils.printTheadLog("开始转换成敏感词数组");
            String[] split = content.split(",");
            return split;
        }));
        CommonUtils.printTheadLog("main continue");
        String[] strings = future.get();

        CommonUtils.printTheadLog("strings ="+strings);
        CommonUtils.printTheadLog("main stop");
    }



编排2个非依赖关系的异步任务 thenCombine()

CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)

T:是第一个任务的结果
U:是第二个任务的结果
V:经BiFunction应用转换后的结果


//变异体
CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor)





//代码演示
 public static void main(String[] args) throws ExecutionException, InterruptedException {

        //需求:替换新闻稿(news.txt)中敏感词汇,把敏感词汇替换成*,敏感词存储在filter_.words.txt中

        CommonUtils.printTheadLog("main start");

        //读取敏感词并转为敏感词数组
        CompletableFuture<String[]> future1 = CompletableFuture.supplyAsync(() -> {
            CommonUtils.printTheadLog("开始读取filter_words.txt");
            String readFile = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\filter_words.txt");
            CommonUtils.printTheadLog("开始转为敏感词数组");
            String[] words = readFile.split(",");
            return words;
        });

        //读取新闻稿(news.txt)
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            String newsContent = CommonUtils.readFile("E:\\project\\competable-future\\src\\main\\java\\news.txt");
            return newsContent;
        });

        //替换敏感词
        CompletableFuture<String> future3 = future1.thenCombine(future2, (words, newsContent) -> {
            for (String word : words) {
                if (newsContent.indexOf(word) > 0) {
                    newsContent  = newsContent.replace(word, "**");
                }
            }
            return newsContent;
        });

        CommonUtils.printTheadLog("main continue");
        String content = future3.get();

        CommonUtils.printTheadLog("content="+content);
        CommonUtils.printTheadLog("main stop");

    }
//注意:当两个Future都完成时,才将两个异步任务的结果传递给thenCombine的回调函数进一步处理!!!

合并多个异步 allOf / anyOf

可以使用以下方法来组合任意数量的CompletableFuture

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

//变异体


//代码演示  allof
//有多个需要独立并运行的Future,并在所有这些Future都完成后执行一些操作
public static CompletableFuture<String> readFileFuture(String fileName){
        return CompletableFuture.supplyAsync(()->{
            String content = CommonUtils.readFile(fileName);
            return content;
        });
    }

    public static void main(String[] args) {
        //需求:统计news1.txt、new2.txt、new3.txt文件中包合CompLetableFuture.关键字的文件的个数

        // step 1: 创建List集合存储文件名
        List<String> files = Arrays.asList("ps://blog.csdn.net/siaok/article/details/135541414?spm=1001.2014.3001.5501",
                "ps://blog.csdn.net/siaok/article/details/136014595?spm=1001.2014.3001.5501",
                "E:\\project\\competable-future\\src\\main\\resources\\news3.txt");

        // step 2: 根据文件名调用readFileFuture创建多个CompletableFuture,并存入List集合中
        List<CompletableFuture<String>> readFileFutureList = files.stream().map(file -> {
           return readFileFuture(file);
       }).collect(Collectors.toList());
        
        // step 3: 把List集合转换成数组待用,以便传入allOf方法中
        int len = readFileFutureList.size();
        CompletableFuture[] readFileArray = readFileFutureList.toArray(new CompletableFuture[len]);

        // step 4: 使用allOf方法合并多个异步任务
        CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(readFileArray);

        // step 5: 当多个异步任务都完成后,使用回调操作文件结果,统计符合条件的文件个数
        CompletableFuture<Long> countFuture = allOfFuture.thenApply(v -> {
            return readFileFutureList.stream().
                    map(future -> future.join()).
                    filter(content -> content.contains("CompletableFuture")).
                    count();
        });

        // step 6: 主线程打印输出文件个数
        Long count = countFuture.join();

        System.out.println("count = " + count);

    }

//代码演示  anyof
//当给定的多个异步任务中的有任意Future一个完成时,需要执行一些操作,就可以使用它
//anyOf()返回一个新的CompletableFuture, 新的CompletableFuture的结果和cfs已完成的那个异步任务结果相同

public static void main(String[] args) throws ExecutionException, InterruptedException {

        //anyOf()
        CompletableFuture<String> future1 =CompletableFuture.supplyAsync(()->{
            CommonUtils.sleepSecond(2);
            return "Future1的结果";
        });

        CompletableFuture<String> future2 =CompletableFuture.supplyAsync(()->{
            CommonUtils.sleepSecond(1);
            return "Future2的结果";
        });

        CompletableFuture<String> future3 =CompletableFuture.supplyAsync(()->{
            CommonUtils.sleepSecond(3);
            return "Future3的结果";
        });

        CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);

        Object ret = anyOfFuture.get();
        System.out.println("ret = " + ret);
    }
//结果是 ref=Future2的结果  
// Future2  是最先完成的   为什么返回值是Object, 因为你无法知道在多个异步调用中 哪个先完成,而且每个异步调用可能返回类型也不一样,只有使用object最保险。

异步任务之异常处理

如果在 supplyAsync 任务中出现异常,后续的 thenApply 和 thenAccept 回调都不会执行, CompletableFuture 将传入异常处理
如果在第一个thenApply任务中出现异常,第二个 thenApply 和最后的 thenAccept 回调不会被执行,CompletableFuture 将转入异常处理,依次类推

public static void main(String[] args) {

        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
//            int r = 1 / 0;
            return "result1";
        }).thenApply(result -> {
            CommonUtils.printTheadLog(result);
            String str = null;
            int length = str.length();
            return result + " result2";
        }).thenApply(result -> {
            return result + " result3";
        }).thenAccept(result -> {
            CommonUtils.printTheadLog(result);
        });
    }
//结果  result

exceptionally()

exceptionally 用于处理回调链上的异常, 回调链上出现的任何异常,回调链不继续向下执行,都在exceptionally中处理异常
因为 exceptionally 只处理一次异常,所以常常用在回调链的末端

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) 
//变异体
CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
CompletableFuture<T> exceptionallyAsync(Function<Throwable, ? extends T> fn) // jdk17+
CompletableFuture<T> exceptionallyAsync(Function<Throwable, ? extends T> fn, Executor executor)

//演示
 public static void main(String[] args) {

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
//            int r = 1 / 0;
            return "result1";
        }).thenApply(result -> {
            CommonUtils.printTheadLog(result);
            String str = null;
            int length = str.length();
            return result + " result2";
        }).thenApply(result -> {
            return result + " result3";
        }).exceptionally(ex ->{
            String message = ex.getMessage();
            System.out.println("message = " + message);
            return "Unknown";
        });

    }

//结果-   会将message打印出 / by zero



handle()

CompletableFuture API 还提供了一种更通用的方法handle() 表示从异常中恢复 handle() 常常被用来恢复回调链中的一次特定的异常,回调链恢复后可进一步向下传递。

CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
//变异体
CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)
CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)



 public static void main(String[] args) throws ExecutionException, InterruptedException {

        // handle()
        CommonUtils.printTheadLog("main start");
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            int r = 1 / 0;
            return "result1";
        }).handle((result,ex)->{
            CommonUtils.printTheadLog("上一步异常的恢复");
            if(ex != null){
                CommonUtils.printTheadLog("出现异常:" + ex.getMessage());
                return "UnKnown";
            }
            return result;
        });

        CommonUtils.printTheadLog("main continue");
        String ret = future.get();
        CommonUtils.printTheadLog("ret = " + ret);

        CommonUtils.printTheadLog("main end");

    }
//结果 打印结果为 都会异常和该输出的都输出  只是 "ret = "等于null
 result 参数为 null ,否则 ex 参数将为 null

//对回调链中的一次异常进行恢复处理
public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 需求: 对回调链中的一次异常进行恢复处理

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
//            int r = 1 / 0;
            return "result1";
        }).handle((result, ex) -> {
            if (ex != null) {
                System.out.println("出现异常:" + ex.getMessage());
                return "UnKnown1";
            }
            return result;
        }).thenApply(result -> {

            String str = null;
            int len = str.length();

            return result + " result2";
        }).handle((result, ex) -> {
            if (ex != null) {
                System.out.println("出现异常:" + ex.getMessage());
                return "UnKnown2";
            }
            return result;    
        }).thenApply(result -> {
            return result + " result3";
        });

        String ret = future.get();
        CommonUtils.printTheadLog("ret = " + ret);
    }
//结果  出现异常:异常具体嘻嘻  
 ret=UnKnown2+result3

销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。1

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

2014-01-07 2014-01-09 2014-01-11 2014-01-13 2014-01-15 2014-01-17 2014-01-19 2014-01-21 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. 注脚的解释 ↩︎

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值