双异步系列完结撒花,如何解決异步事务问题?

news2024/9/21 8:04:33

在这里插入图片描述

目录

    • 一、前情提要
      • 原始需求:读取一个10万行的Excel
      • 优化1:[**使用双异步后,从 191s 优化到 2s**](https://blog.csdn.net/guorui_java/article/details/135143234)
      • 优化2:[**使用双异步后,如何保证数据一致性?**](https://blog.csdn.net/guorui_java/article/details/135705565)
      • 优化3:[**获取双异步返回值时,如何保证主线程不阻塞?**](https://blog.csdn.net/guorui_java/article/details/135708663)
    • 二、异步某线程失败时,主线程回滚所有异步线程的事务!
    • 三、@Transactional注解
      • 1、@Transactional
      • 2、@Transactional(rollbackFor = Exception.class)
    • 四、注解失效问题
      • 1、@Transactional 应用在非 public 修饰的方法上
      • 2、@Transactional 注解属性 rollbackFor 设置错误
      • 3、同一个类中方法调用,导致@Transactional失效
      • 4、捕获异常
    • 五、通过Future获取异步返回值,添加事务
      • 1、添加事务
        • (1)添加事务 + 不开启异步
        • (2)添加事务 + 开启异步
      • 2、手动添加事务
        • (1)添加事务 + 不开启异步
        • (2)Future获取异步返回值,添加手动事务,异常回滚失败!
    • 六、@async + @Transactional 事务失效问题
    • 七、Spring 的事务传播机制是不能够跨线程的
      • 1、一个异步线程一个事务,然后根据结果统一提交/回滚?
      • 2、核心代码
      • 3、异步线程类
      • 4、事务复制类
      • 5、为何要用事务复制类?而最后提交和回滚的时候也没用它?
    • 八、总结
      • 读取一个10万行的Excel的最佳解决方案是:

大家好,我是哪吒。

一、前情提要

在上一篇文章中,我们通过双异步的方式导入了10万行的Excel,有个小伙伴在评论区问我,如果保证事务呢,如果分批的话。

原始需求:读取一个10万行的Excel

通过串行读取Excel,单个Excel耗时191s。

在这里插入图片描述

优化1:使用双异步后,从 191s 优化到 2s

  1. 分别通过POI和EasyExcel的方式读取Excel并插入数据库;
  2. 探讨了“线程池中的核心线程数设置问题”;
  3. 经过数十次的测试,总结了通过线程池的方式,争取一次性并行入库,效率最佳。

在这里插入图片描述

优化2:使用双异步后,如何保证数据一致性?

通过Future获取异步返回值,再和Excel文件数据行进行比较,实现对数据准确性的判断!

  1. 逐行分析了FutureTask源码,绘制了FutureTask执行流程图;
  2. 分析get()源码,绘制get()方法执行流程图;
  3. 但是,发现了一个问题,Future.get()会造成主线程的阻塞。

在这里插入图片描述

优化3:获取双异步返回值时,如何保证主线程不阻塞?

Java8中引入了CompletableFuture,它实现了对Future的全面升级,可以通过回调的方式,获取异步线程返回值。

CompletableFuture的异步执行通过ForkJoinPool实现, 它使用守护线程去执行任务。

ForkJoinPool在于可以充分利用多核CPU的优势,把一个任务拆分成多个小任务,把多个小任务放到多个CPU上并行执行,当多个小任务执行完毕后,再将其执行结果合并起来。

  1. 通过CompletableFuture优化 “通过Future获取异步返回值”;
  2. CompletableFuture和Future的效率对比;
  3. 自定义ForkJoinPool线程池;
  4. 核心线程数相同的情况下,CompletableFuture的入库效率要优于Future的入库效率,10万条数据大概要快4秒钟;
  5. 通过CompletableFuture.allOf解决阻塞主线程问题;
  6. 总结了CompletableFuture中花俏的语法糖。

二、异步某线程失败时,主线程回滚所有异步线程的事务!

想要保证事务,肯定是使用@Transactional来实现。

现在的场景是导入若干个大的Excel文件数据,因为每个Excel导入的表不同,所以只要保证单Excel的事务即可。

上文中,是使用异步批量读取并插入的方式实现的Excel文件入库。

也就是说,1个主线程事务 + 若干个子线程事务,我们想要保证单Excel的插入事务,所有异步子线程有任何一个报错,都要进行事务回滚,如果全部都没报错,则进行事务提交。

这个时候,有的小伙伴可能会想到,主线程加个@Transactional注解,所有子线程分别加@Transactional注解,就可以了吧?

但是,这样是不行的,子线程的异常只会回滚其自身的事务。

如果Excel中有10万条数据,一次插入4200条数据,最后一次插入3400条。如果其它线程都插入成功了,最后一个报错了,此时,数据库中还是会有96600条数据插入成功,与单Excel的事务需求不符。

通过代码模拟这种情况:

if(end == sheet.getLastRowNum()){
    logger.info("插入最后一批数据,模拟异常");
    int a = 1/0;
}

在这里插入图片描述
在这里插入图片描述

三、@Transactional注解

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

简而言之,@Transactional注解在代码执行出错的时候能够进行事务的回滚。

  1. 在启动类上添加@EnableTransactionManagement注解。
  2. 用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
  3. 在项目中,@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
  4. 在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。

1、@Transactional

使用@Transactional后,当程序发生RuntimeException运行时异常在没有使用try,catch进行捕获的时候,程序都会中止,当程序发生中止,则会触发数据库的回滚。

当使用了trycatch进行捕获到这个异常,假如在catch中加入了throw e抛出异常,则程序中止,数据库回滚。

加入在try catch中没有throw e 抛出异常,只是简单的打印异常,则异常被捕获未抛出异常去终止程序,在trycatch中的操作数据库语句插入失败,在trycatch上面和下面的数据库相关插入语句成功,也就是程序成功跑完,数据库不会发生回滚。

2、@Transactional(rollbackFor = Exception.class)

在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。

四、注解失效问题

1、@Transactional 应用在非 public 修饰的方法上

事务拦截器在目标方法执行前后进行拦截,内部会调用方法来获取Transactional 注解的事务配置信息,调用前会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

2、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。

Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。

如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。

3、同一个类中方法调用,导致@Transactional失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

在同一个类中调用异步方法,等于调用this本类的方法,没有走Spring生成的代理类,也就不会让他异步执行,@Transactional的原理也类似。

4、捕获异常

如果你手动的catch捕获这个异常并进行处理,事务管理器会认为当前事务应该正常commit,就会导致注解失效,如果非要捕获且不失效,就必须在代码块内throw new Exception抛出异常。

五、通过Future获取异步返回值,添加事务

1、添加事务

@Transactional(rollbackFor = Exception.class)
public void readXls(String filePath, String filename) throws Exception{
	try {
		// 省略一些复杂操作...
 		
 		List<Future<Integer>> futureList = new ArrayList<>();
		for (int time = 0; time < times; time++) {
			Future<Integer> sumFuture = readExcelDataAsyncFutureService.readXlsCacheAsyncMybatis();
            futureList.add(sumFuture);
		}
		
		// 主线程获取Future返回值
        boolean futureFlag = getFutureResult(futureList, excelRow);
        if (futureFlag) {
            logger.info("readXlsCacheAsync---插入数据成功,提交事务");
        } else {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            logger.info("readXlsCacheAsync---插入数据失败,回滚事务");
        }
	} catch (Exception e) {
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
		logger.error("readXlsCacheAsync---插入数据异常,回滚事务:", e);
    }
}

@Async("async-executor")//是否开启异步
@Override
public Integer readXlsCacheAsyncMybatis() {
    try {
    	// 省略一些复杂操作...
    }catch (Exception e){
        throw new RuntimeException("插入数据库异常", e);
    }
}
(1)添加事务 + 不开启异步

如果入库异常,事务回滚成功

在这里插入图片描述

(2)添加事务 + 开启异步

回滚失败!

在这里插入图片描述

2、手动添加事务

public void readXls(String filePath, String filename) throws Exception{
 	// 手动开启事务,不自动提交
    TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
	try {
		// 省略一些复杂操作...
 		
 		List<Future<Integer>> futureList = new ArrayList<>();
		for (int time = 0; time < times; time++) {
			Future<Integer> sumFuture = readExcelDataAsyncFutureService.readXlsCacheAsyncMybatis();
            futureList.add(sumFuture);
		}
		
		// 主线程获取Future返回值
        boolean futureFlag = getFutureResult(futureList, excelRow);
        if (futureFlag) {
     	   dataSourceTransactionManager.commit(transactionStatus); // 提交
            logger.info("readXlsCacheAsync---插入数据成功,提交事务");
        } else {
            dataSourceTransactionManager.rollback(transactionStatus);// 回滚
            logger.info("readXlsCacheAsync---插入数据失败,回滚事务");
        }
	} catch (Exception e) {
		dataSourceTransactionManager.rollback(transactionStatus);// 回滚
		logger.error("readXlsCacheAsync---插入数据异常,回滚事务:", e);
    }
}

@Async("async-executor")//是否开启异步
@Override
public Integer readXlsCacheAsyncMybatis() {
    try {
    	// 省略一些复杂操作...
    }catch (Exception e){
        throw new RuntimeException("插入数据库异常", e);
    }
}
(1)添加事务 + 不开启异步

如果入库异常,事务回滚成功

在这里插入图片描述

(2)Future获取异步返回值,添加手动事务,异常回滚失败!

在这里插入图片描述

六、@async + @Transactional 事务失效问题

回顾一下需求:异步某线程失败时,主线程回滚所有异步线程的事务!

是代码有问题,还是就是实现不了呢?

@Async和@Transactional注解都是通过Spring aop实现的,核心都是靠着关键的MethodInterceptor实现,@Async会给对应bean代理对象中放入一个AnnotationAsyncExecutionInterceptor拦截器,而@Transactional会给对应bean的代理对象中放入一个TransactionInterceptor拦截器。

Spring事务管理的传播机制是使用 ThreadLocal 实现的。因为 ThreadLocal 是线程私有的,所以 Spring 的事务传播机制是不能够跨线程的。

七、Spring 的事务传播机制是不能够跨线程的

1、一个异步线程一个事务,然后根据结果统一提交/回滚?

2、核心代码

/**
 * 数据源事务管理器
 */
private DataSourceTransactionManager dataSourceTransactionManager;

@Autowired
public void setUserService(DataSourceTransactionManager dataSourceTransactionManager) {
	this.dataSourceTransactionManager = dataSourceTransactionManager;
}

@Override
public void readXls(String filePath, String filename) {

	List<TransactionStatus> transactionStatusList = Collections.synchronizedList(new ArrayList<>());
	List<TransactionResource> transactionResourceList = Collections.synchronizedList(new ArrayList<>());

	try {
		List<Future<Integer>> futureList = new ArrayList<>();
        for (int time = 0; time < times; time++) {
			Future<Integer> sumFuture = readAsyncFutureTransactionDBService.readXlsCacheAsyncMybatis(sheet, row, start, end, insertBuilder,transactionStatusList,transactionResourceList);
            futureList.add(sumFuture);
		}
		
		// 主线程获取Future返回值
        boolean futureFlag = getFutureResult(futureList, excelRow);
		if (futureFlag) {
			for (int i = 0; i < transactionStatusList.size(); i++) {
				TransactionStatus transactionStatus = transactionStatusList.get(i);
				dataSourceTransactionManager.commit(transactionStatus); // 提交
			}

			logger.info("readXlsCacheAsync---插入数据成功,提交事务");
		} else {
			for (int i = 0; i < transactionStatusList.size(); i++) {
				TransactionStatus transactionStatus = transactionStatusList.get(i);
				dataSourceTransactionManager.rollback(transactionStatus);// 回滚
			}

			logger.info("readXlsCacheAsync---插入数据失败,事务回滚");
			throw new RuntimeException("readXlsCacheAsync---插入数据异常,异常事务回滚");
		}
	} catch (Exception e) {
		logger.error("readXlsCacheAsync---插入数据异常,事务回滚:", e);

		for (int i = 0; i < transactionStatusList.size(); i++) {
			TransactionStatus transactionStatus = transactionStatusList.get(i);
			dataSourceTransactionManager.rollback(transactionStatus);// 回滚
		}
		//connection.rollback();
		throw new RuntimeException("readXlsCacheAsync---插入数据异常,异常事务回滚");
	}
}

3、异步线程类

@Async("async-executor")
@Override
public Future<Integer> readXlsCacheAsyncMybatis(XSSFSheet sheet, 
												XSSFRow row, 
												int start, 
												int end, 
												StringBuilder insertBuilder,
												List<TransactionStatus> transactionStatusList,
		List<ReadAsyncFutureTransactionServiceImpl.TransactionResource> transactionResourceList) throws Exception {
		
	DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
	TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
	// 开启新事务
	transactionStatusList.add(transactionStatus);
	// copy事务资源
	transactionResourceList.add(ReadAsyncFutureTransactionServiceImpl.TransactionResource.copyTransactionResource());

	try {
		// 入库操作
	}catch (Exception e){
		throw new RuntimeException("readXlsCacheAsyncMybatis分批异步读取Excel,通过Mybatis插入数据库异常");
	}
}

4、事务复制类

/**
 * 保存当前事务资源,用于线程间的事务资源COPY操作
 * <p>
 * `@Builder`注解是Lombok库提供的一个注解,它可以用于自动生成Builder模式的代码,使用@Builder注解可以简化创建对象实例的过程,并且可以使代码更加清晰和易于维护
 */
static class TransactionResource {
    // TransactionSynchronizationManager类内部默认提供了下面六个ThreadLocal属性,分别保存当前线程对应的不同事务资源
    // 保存当前事务关联的资源,默认只会在新建事务的时候保存当前获取到的DataSource和当前事务对应Connection的映射关系
    // 当然这里Connection被包装为了ConnectionHolder
    // 事务结束后默认会移除集合中的DataSource作为key关联的资源记录
    private Map<Object, Object> resources;
    //下面五个属性会在事务结束后被自动清理,无需我们手动清理
    // 事务监听者,在事务执行到某个阶段的过程中,会去回调监听者对应的回调接口(典型观察者模式的应用),默认为空集合
    private Set<TransactionSynchronization> synchronizations;
    // 存放当前事务名字
    private String currentTransactionName;
    // 存放当前事务是否是只读事务
    private Boolean currentTransactionReadOnly;
    // 存放当前事务的隔离级别
    private Integer currentTransactionIsolationLevel;
    // 存放当前事务是否处于激活状态
    private Boolean actualTransactionActive;

    /**
     * 对事务资源进行复制
     *
     * @return TransactionResource
     */
    public static TransactionResource copyTransactionResource() {
        return TransactionResource.builder()
                //返回的是不可变集合
                .resources(TransactionSynchronizationManager.getResourceMap())
                //如果需要注册事务监听者,这里记得修改,我们这里不需要,就采用默认负责,spring事务内部默认也是这个值
                .synchronizations(new LinkedHashSet<>()).currentTransactionName(TransactionSynchronizationManager.getCurrentTransactionName()).currentTransactionReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly()).currentTransactionIsolationLevel(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel()).actualTransactionActive(TransactionSynchronizationManager.isActualTransactionActive()).build();
    }

    /**
     * 使用
     */
    public void autoWiredTransactionResource() {
        resources.forEach(TransactionSynchronizationManager::bindResource);
        //如果需要注册事务监听者,这里记得修改,我们这里不需要,就采用默认负责,spring事务内部默认也是这个值
        TransactionSynchronizationManager.initSynchronization();
        TransactionSynchronizationManager.setActualTransactionActive(actualTransactionActive);
        TransactionSynchronizationManager.setCurrentTransactionName(currentTransactionName);
        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(currentTransactionIsolationLevel);
        TransactionSynchronizationManager.setCurrentTransactionReadOnly(currentTransactionReadOnly);
    }

    /**
     * 移除
     */
    public void removeTransactionResource() {
        // 事务结束后默认会移除集合中的DataSource作为key关联的资源记录
        // DataSource如果重复移除,unbindResource时会因为不存在此key关联的事务资源而报错
        resources.keySet().forEach(key -> {
            if (!(key instanceof DataSource)) {
                TransactionSynchronizationManager.unbindResource(key);
            }
        });
    }
}

5、为何要用事务复制类?而最后提交和回滚的时候也没用它?

如何不加会怎么样?

在提交和回滚的时候,会出现异常:

在这里插入图片描述

八、总结

经过不懈的努力,终于解决了“异步某线程失败时,主线程回滚所有异步线程的事务!”这个看起来很简单的问题。

也是对双异步入库系列的一个完结。

通过添加事务,可以有效的控制Excel异步插入数据的准确性。

在这里插入图片描述

读取一个10万行的Excel的最佳解决方案是:

  1. 通过EasyExcel异步读取Excel;
  2. 通过Future获取异步返回值,比较Excel行数和入库数,保证数据入库一致性;
  3. 通过CompletableFuture + 自定义ForkJoinPool线程池的方式执行,解决主线程阻塞问题;
  4. 根据核心线程数,设置每个线程读取的Excel数据行数,以达到效率最佳;
  5. 通过手动添加事务 + 一个线程一个事务 + 复制事务的方式实现异步事务的有效控制。

🏆文章收录于:100天精通Java从入门到就业

全网最细Java零基础手把手入门教程,系列课程包括:Java基础、Java8新特性、Java集合、高并发、性能优化等,适合零基础和进阶提升的同学。

🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

华为OD机试 2023B卷题库疯狂收录中,刷题点这里

刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。

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

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

相关文章

ClickHouse为什么这么快(二)SSE指令优化

上一篇 ClickHouse为什么这么快&#xff08;一&#xff09;减少数据扫描范围 我们说到了ClickHouse中使用列存储&#xff0c;每个列都单独存储为一个文件&#xff0c;每个文件都是由一个或多个数据块组成&#xff0c;也就是说&#xff1a;每个文件由一个或多个数组组成&#xf…

Python限定符

在正则表达式中&#xff0c;限定符用于指定模式的匹配次数或匹配范围。在Python中&#xff0c;正则表达式模块re提供了多种不同的限定符&#xff0c;以实现更灵活和精确的匹配操作。熟悉并理解Python中的限定符对于处理文本和字符串数据非常重要。本文将详细介绍Python中常见的…

DNS配置文件讲解

1. 概述 BIND&#xff1a;Berkeley Internet Name Domain &#xff0c;伯克利因特网域名解析服务是一种全球使用最广泛的、 最高效的、最安全的域名解析服务程序 2. 安装软件 [rootserver ~]# yum install bind -y 3. bind服务中三个关键文件 /etc/named.conf : 主配置文件…

Histone H3K27ac Antibody, SNAP-ChIP® Certified

EpiCypher是一家为表观遗传学和染色质生物学研究提供高质量试剂和工具的专业制造商。EpiCypher&#xff08;国内代理商欣博盛生物&#xff09;推出的ChIP级别的Histone H3K27ac Antibody符合EpiCypher的“SNAP-ChIP Certified”标准&#xff0c;用于ChIP实验中的特异性和有效靶…

Orion-14B-Chat-RAG本地部署的解决方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

刷存在感,Excel转Protobuf/Json通用配置文件

使用场景 最近工作流中有将Excel转Protobuf作为配置文件的技术方案。具体实现是先定一个proto文件&#xff0c;再在一个对应excel表中定义对应字段&#xff0c;由策划在excel进行更改。proto文件可以生成对应语言的脚本&#xff0c;然后将excel转成对应protobuf的binary。 我…

【Tomcat与网络9】提高Tomcat启动速度的八大措施

本文我们来看一下如何对Tomcat进行调优&#xff0c;我们对于Tomcat的调优主要集中在三个方面&#xff1a;提高启动速度、提高系统稳定性和提高并发能力&#xff0c;后两者很多时候是相辅相成的&#xff0c;我们放在一起看。 Tomcat现在一般都嵌入在SpringBoot里&#xff0c;因…

又涨又跌 近期现货黄金价格波动怎么看?

踏入2024年一月的下旬&#xff0c;现货黄金价格可以说没了之前火热的状态&#xff0c;盘面上是又涨又跌。面对这样的行情&#xff0c;很多投资者不知道如何看了。下面我们就来讨论一下怎么把握近期的行情。 先区分走势类型。在现货黄金市场中有两种主要的走势类型&#xff0c;一…

千兆电口模块和万兆电口模块:网络速度的演变

随着信息技术的迅猛发展&#xff0c;网络通信技术也在不断进步。在过去的几十年中&#xff0c;以太网的速度发生了巨大的变化&#xff0c;从最初的百兆以太网&#xff0c;到如今的千兆以太网和万兆以太网甚至40G、100G以太网满足了大数据、云计算、人工智能等新兴应用的需求。在…

Golang语言异常机制解析:错误策略与优雅处理

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 作为开发者来说&#xff0c;我们没办法保证程序在运行过程中永远不会出现异常&#xff0c;对于异常…

前端JavaScript篇之实现一个将多维数组展示的方法有哪些,分别是?

目录 实现一个将多维数组展示的方法有哪些&#xff0c;分别是&#xff1f;方法一&#xff1a;递归展开成一维数组方法二&#xff1a;嵌套展示结构方法三&#xff1a;ES6新增的数组扩展方法 flat()方法四&#xff1a;apply() 结合 concat() 使用以展开成一维数组方法五&#xff…

2024 年 Sketch 替代品的最佳选择:Windows、Web 和 Mac 用户的详细比较

什么是Sketch Sketch是一个著名的、越来越受欢迎的矢量图形设计软件&#xff0c;它通过各种有用的UI设计和原型设计工具使这一过程更易于管理。 Sketch于2010年9月首次发布&#xff0c;后来在2012年获得苹果设计奖。对于Sketch来说&#xff0c;这正好停止了Adobe使用当时领先…

JAVA Web 学习(二)ServLet

二、动态web 资源开发技术——Servlet Servlet&#xff08;小服务程序&#xff09;是一个与协议无关的、跨平台的Web组件&#xff0c;由Servlet容器所管理。运行在服务器端&#xff0c;可以动态地扩展服务器的功能&#xff0c;并采用“请求一响应”模式提供Web服务。 Servlet的…

基于仿射区间的分布式三相不对称配电网潮流算法matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于仿射区间的分布式三相不对称配电网潮流算法matlab仿真。 基于仿射区间的&#xff0c;含分布式电源的配电网三相潮流算法&#xff0c;算法涉及仿射&#xff0c;三相&#x…

python爬虫概念及介绍

1. 什么是互联网爬虫&#xff1f; 解释 1 &#xff1a;通过一个程序&#xff0c;根据 Url ( http : // www . taobao . com ) 进行爬取网页&#xff0c;获取有用信息 解释 2&#xff1a;使用程序模拟浏览器&#xff0c;去向服务器发送请求&#xff0c;获取响应信息 2. 爬虫核…

深度学习经典模型之BERT(下)

在"深度学习经典模型之BERT(上)"我们描述了BERT基本信息、意义、与GPT和Transformer的区别、预训练、自监督等相关信息后&#xff0c;本章节将介绍BERT的输入、Encoder、微调及两个主流变种。 BERT inputs 切词方法 BERT的切词方法用的是WordPiece embeddings&…

MongoDB数据模型和WiredTiger读写模型

MongoDB数据模型 思考&#xff1a;MongoDB为什么会使用BSON&#xff1f; BSON协议与数据类型 JSON JSON是当今非常通用的一种跨语言Web数据交互格式&#xff0c;属于ECMAScript标准规范的一个子集。JSON&#xff08;JavaScript Object Notation, JS对象简谱&#xff09;即J…

[word] word艺术字体如何设置? #知识分享#职场发展#媒体

word艺术字体如何设置&#xff1f; 在工作中有些技巧&#xff0c;可以快速提高工作效率&#xff0c;解决大部分工作&#xff0c;今天给大家分享word艺术字体如何设置的技巧&#xff0c;希望可以帮助到你。 1、设置艺术字 选中文字&#xff0c;然后点击菜单栏的【插入】按钮一一…

机器学习复习(6)——numpy的数学操作

加减法运算 # 创建两个不同的数组 a np.arange(4) #list(0,1,2,3 b np.array([5,10,15,20]) # 两个数组做减法运算 b-a 运行结果&#xff1a; 计算数组的平方 #b*2代表数组b每个元素乘以2 #b**2代表数组b每个元素的2次方 b**2 运行结果&#xff1a; 计算数组的正弦值 #…

8.DNS域名解析服务器

目录 1. 概述 1.1. 产生原因 1.2. 作用&#xff1a; 1.3. 连接方式 1.4. 因特网的域名结构 1.4.1. 拓扑&#xff1a; 1.4.2. 分类 1.4.3. 域名服务器类型划分 2. DNS域名解析过程 2.1. 分类&#xff1a; 2.2. 解析图&#xff1a; 2.2.1. 图&#xff1a; 2.2.2. 过…