Java多线程实战-实现多线程文件下载,支持断点续传、日志记录等功能

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

目录

前言

1 基础知识回顾

1.1 线程的创建和启动

1.2 线程池的使用

2.运行环境说明

3.核心模块实现

3.1下载线程的实现

3.2日志线程的实现

3.3相关工具类的实现

3.4核心业务实现

4.功能测试

总结


前言

在当今快节奏的数字时代,大文件的下载已经成为我们日常生活中不可或缺的一部分。然而,传统的单线程下载器在面临大文件时往往显得力不从心,下载速度缓慢,用户体验不佳。

老读者应该知道,我最近在研究Java多线程并发编程这一块的内容,故想要编写一个多线程下载工具,一是为了知识的落地实践,二是可以将这个工具运用到平时下载大文件的地方。

1 基础知识回顾

为了照顾一些新来的小伙伴,我这里简单讲解一下在Java中一些常用的多线程实现

1.1 线程的创建和启动

在Java中,线程可以通过以下几种方式创建和启动一个新的线程:

继承Thread类:自定义一个类,继承自Thread类,并重写run()方法。

创建线程对象并调用start()方法启动线程。

public class MyThread extends Thread {@Overridepublic void run() {// 线程执行的代码}
}


实现Runnable接口:自定义一个类,实现Runnable接口,并重写run()方法。

创建Runnable对象,并将其传递给Thread对象,然后调用start()方法。

public class MyRunnable implements Runnable {@Overridepublic void run() {// 线程执行的代码}
}


使用ExecutorService:这是一个更高级的方式,用于管理线程池。本次的多线程下载器主要用的就是这种方式实现

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new MyRunnable());

1.2 线程池的使用

线程池是一种管理线程的更高效的方式,可以避免频繁创建和销毁线程的开销。Java中使用ExecutorService接口来管理线程池。

固定大小的线程池:

创建一个固定大小的线程池,最多同时运行5个线程。

ExecutorService executor = Executors.newFixedThreadPool(5);


单线程的Executor:

创建一个只有一个线程的线程池,所有任务按顺序执行。

ExecutorService executor = Executors.newSingleThreadExecutor();


缓存线程池:
创建一个可以根据需要创建新线程的线程池,适合执行短期异步任务。

ExecutorService executor = Executors.newCachedThreadPool();


计划任务的ScheduledExecutorService:
创建一个可以定时执行任务的线程池,适合执行周期性任务。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
scheduler.scheduleAtFixedRate(new MyRunnable(), 0, 10, TimeUnit.SECONDS);

在本次案例中,使用了Executors.newFixedThreadPool(DOWNLOAD_THREAD_NUM + 1)来创建一个固定大小的线程池,用于管理下载任务和日志线程。这种方式确保了线程的复用,并且能够有效地控制线程的数量。

2.运行环境说明

Maven依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.wdbyte</groupId><artifactId>down-bit</artifactId><version>1.0-SNAPSHOT</version><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><archive><manifest><mainClass>com.wdbyte.downbit.DownloadMain</mainClass></manifest></archive><descriptorRefs><!-- 这个jar-with-dependencies是assembly预先写好的一个,组装描述引用 --><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><!--工程名--><finalName>${project.name}</finalName></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></build>
</project>

 项目整体结构如下

3.核心模块实现

3.1下载线程的实现

/*** 多线程下载工具类* @author 牵着猫散步的鼠鼠-LiuShiJie*/
public class DownloadThread implements Callable<Boolean> {/*** 每次读取的数据块大小*/private static int BYTE_SIZE = 1024 * 100;/*** 下载链接*/private String url;/*** 下载开始位置*/private long startPos;/*** 要下载的文件区块大小*/private Long endPos;/*** 标识多线程下载切分的第几部分*/private Integer part;/*** 文件总大小*/private Long contentLenth;public DownloadThread(String url, long startPos, Long endPos, Integer part, Long contentLenth) {this.url = url;this.startPos = startPos;this.endPos = endPos;this.part = part;this.contentLenth = contentLenth;}@Overridepublic Boolean call() throws Exception {if (url == null || url.trim() == "") {throw new RuntimeException("下载路径不正确");}// 文件名String httpFileName = HttpUtls.getHttpFileName(url);if (part != null) {httpFileName = httpFileName + DownloadMain.FILE_TEMP_SUFFIX + part;}// 本地文件大小Long localFileContentLength = FileUtils.getFileContentLength(httpFileName);LogThread.LOCAL_FINISH_SIZE.addAndGet(localFileContentLength);if (localFileContentLength >= endPos - startPos) {LogUtils.info("{} 已经下载完毕,无需重复下载", httpFileName);LogThread.DOWNLOAD_FINISH_THREAD.addAndGet(1);return true;}if (endPos.equals(contentLenth)) {endPos = null;}HttpURLConnection httpUrlConnection = HttpUtls.getHttpUrlConnection(url, startPos + localFileContentLength, endPos);// 获得输入流try (InputStream input = httpUrlConnection.getInputStream(); BufferedInputStream bis = new BufferedInputStream(input);RandomAccessFile oSavedFile = new RandomAccessFile(httpFileName, "rw")) {oSavedFile.seek(localFileContentLength);byte[] buffer = new byte[BYTE_SIZE];int len = -1;// 读到文件末尾则返回-1while ((len = bis.read(buffer)) != -1) {oSavedFile.write(buffer, 0, len);LogThread.DOWNLOAD_SIZE.addAndGet(len);}} catch (FileNotFoundException e) {LogUtils.error("ERROR! 要下载的文件路径不存在 {} ", url);return false;} catch (Exception e) {LogUtils.error("下载出现异常");e.printStackTrace();return false;} finally {httpUrlConnection.disconnect();LogThread.DOWNLOAD_FINISH_THREAD.addAndGet(1);}return true;}}

3.2日志线程的实现

/*** 多线程下载日志记录* @author 牵着猫散步的鼠鼠-LiuShiJie*/
public class LogThread implements Callable<Boolean> {// 本地下载的文件大小public static AtomicLong LOCAL_FINISH_SIZE = new AtomicLong();// 已经下载的文件大小public static AtomicLong DOWNLOAD_SIZE = new AtomicLong();// 下载完成的线程数public static AtomicLong DOWNLOAD_FINISH_THREAD = new AtomicLong();// 待下载的文件总大小private long httpFileContentLength;public LogThread(long httpFileContentLength) {this.httpFileContentLength = httpFileContentLength;}@Overridepublic Boolean call() throws Exception {int[] downSizeArr = new int[5];int i = 0;double size = 0;double mb = 1024d * 1024d;// 文件总大小String httpFileSize = String.format("%.2f", httpFileContentLength / mb);while (DOWNLOAD_FINISH_THREAD.get() != DownloadMain.DOWNLOAD_THREAD_NUM) {double downloadSize = DOWNLOAD_SIZE.get();downSizeArr[++i % 5] = Double.valueOf(downloadSize - size).intValue();size = downloadSize;// 每秒速度double fiveSecDownloadSize = Arrays.stream(downSizeArr).sum();int speed = (int)((fiveSecDownloadSize / 1024d) / (i < 5d ? i : 5d));// 剩余时间double surplusSize = httpFileContentLength - downloadSize - LOCAL_FINISH_SIZE.get();String surplusTime = String.format("%.1f", surplusSize / 1024d / speed);if (surplusTime.equals("Infinity")) {surplusTime = "-";}// 已下大小String currentFileSize = String.format("%.2f", downloadSize / mb + LOCAL_FINISH_SIZE.get() / mb);String speedLog = String.format("> 已下载 %smb/%smb,速度 %skb/s,剩余时间 %ss", currentFileSize, httpFileSize, speed, surplusTime);System.out.print("\r");System.out.print(speedLog);// 一秒更新一次日志Thread.sleep(1000);}System.out.println();return true;}}

3.3相关工具类的实现

文件操作工具类FileUtils ,主要用来获取文件大小长度

public class FileUtils {/*** 获取文件内容长度** @param name* @return*/public static long getFileContentLength(String name) {File file = new File(name);return file.exists() && file.isFile() ? file.length() : 0;}}

 网络请求操作工具类HttpUtls,主要是一些常用的Http操作

/*** 网络请求操作工具类* @author 牵着猫散步的鼠鼠-LiuShiJie*/
public class HttpUtls {/*** 获取 HTTP 链接** @param url* @return* @throws IOException*/public static HttpURLConnection getHttpUrlConnection(String url) throws IOException {URL httpUrl = new URL(url);HttpURLConnection httpConnection = (HttpURLConnection)httpUrl.openConnection();httpConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");return httpConnection;}/*** 获取 HTTP 链接** @param url* @param start* @param end* @return* @throws IOException*/public static HttpURLConnection getHttpUrlConnection(String url, long start, Long end) throws IOException {HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);LogUtils.debug("此线程下载内容区间 {}-{}", start, end);if (end != null) {httpUrlConnection.setRequestProperty("RANGE", "bytes=" + start + "-" + end);} else {httpUrlConnection.setRequestProperty("RANGE", "bytes=" + start + "-");}Map<String, List<String>> headerFields = httpUrlConnection.getHeaderFields();for (String s : headerFields.keySet()) {LogUtils.debug("此线程相应头{}:{}", s, headerFields.get(s));}return httpUrlConnection;}/*** 获取网络文件大小 bytes** @param url* @return* @throws IOException*/public static long getHttpFileContentLength(String url) throws IOException {HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);int contentLength = httpUrlConnection.getContentLength();httpUrlConnection.disconnect();return contentLength;}/*** 获取网络文件 Etag** @param url* @return* @throws IOException*/public static String getHttpFileEtag(String url) throws IOException {HttpURLConnection httpUrlConnection = getHttpUrlConnection(url);Map<String, List<String>> headerFields = httpUrlConnection.getHeaderFields();List<String> eTagList = headerFields.get("ETag");httpUrlConnection.disconnect();return eTagList.get(0);}/*** 获取网络文件名** @param url* @return*/public static String getHttpFileName(String url) {int indexOf = url.lastIndexOf("/");return url.substring(indexOf + 1);}
}

日志工具类LogUtils ,主要负责日志格式化输出

/*** 日志工具类,输出日志* @author 牵着猫散步的鼠鼠-LiuShiJie*/
public class LogUtils {public static boolean DEBUG = false;static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");public static void info(String msg, Object... arg) {print(msg, " -INFO- ", arg);}public static void error(String msg, Object... arg) {print(msg, " -ERROR-", arg);}public static void debug(String msg, Object... arg) {if (DEBUG) { print(msg, " -DEBUG-", arg); }}private static void print(String msg, String level, Object... arg) {if (arg != null && arg.length > 0) {msg = String.format(msg.replace("{}", "%s"), arg);}String thread = Thread.currentThread().getName();System.out.println(LocalDateTime.now().format(dateTimeFormatter) + " " + thread + level + msg);}
}

 迅雷链接转换工具类ThunderUtils ,迅雷链接与普通链接不同,需要转换

/*** 迅雷链接转换工具* @author 牵着猫散步的鼠鼠-LiuShiJie*/
public class ThunderUtils {private static String THUNDER = "thunder://";/*** 判断是否是迅雷链接** @param url* @return*/public static boolean isThunderLink(String url) {return url.startsWith(THUNDER);}/*** 转换成 HTTP URL** @param url* @return*/public static String toHttpUrl(String url) {if (!isThunderLink(url)) {return url;}LogUtils.info("当前链接是迅雷链接,开始转换...");url = url.replaceFirst(THUNDER, "");try {// base 64 转换url = new String(Base64.getDecoder().decode(url.getBytes()), "UTF-8");// url 解码url = URLDecoder.decode(url, "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}// 去头去尾if (url.startsWith("AA")) {url = url.substring(2);}if (url.endsWith("ZZ")) {url = url.substring(0, url.length() - 2);}LogUtils.info("当前链接是迅雷链接,转换结果:{}", url);return url;}
}

3.4核心业务实现

/*** 多线程下载* 断点续传下载 demo* @author 牵着猫散步的鼠鼠-LiuShiJie*/
public class DownloadMain {// 下载线程数量public static int DOWNLOAD_THREAD_NUM = 5;// 下载线程池private static ExecutorService executor = Executors.newFixedThreadPool(DOWNLOAD_THREAD_NUM + 1);// 临时文件后缀public static String FILE_TEMP_SUFFIX = ".temp";// 支持的 URL 协议private static HashSet<String> PROTOCAL_SET = new HashSet();static {PROTOCAL_SET.add("thunder://");PROTOCAL_SET.add("http://");PROTOCAL_SET.add("https://");}public static void main(String[] args) throws Exception {Scanner scanner = new Scanner(System.in);System.out.println("请输入要下载的链接:");String url = scanner.nextLine();long count = PROTOCAL_SET.stream().filter(prefix -> url.startsWith(prefix)).count();if (count == 0) {LogUtils.info("不支持的协议类型");return;}LogUtils.info("要下载的链接是:{}", url);new DownloadMain().download(ThunderUtils.toHttpUrl(url));}public void download(String url) throws Exception {String fileName = HttpUtls.getHttpFileName(url);long localFileSize = FileUtils.getFileContentLength(fileName);// 获取网络文件具体大小long httpFileContentLength = HttpUtls.getHttpFileContentLength(url);if (localFileSize >= httpFileContentLength) {LogUtils.info("{}已经下载完毕,无需重新下载", fileName);return;}List<Future<Boolean>> futureList = new ArrayList<>();if (localFileSize > 0) {LogUtils.info("开始断点续传 {}", fileName);} else {LogUtils.info("开始下载文件 {}", fileName);}LogUtils.info("开始下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));long startTime = System.currentTimeMillis();// 任务切分splitDownload(url, futureList);LogThread logThread = new LogThread(httpFileContentLength);Future<Boolean> future = executor.submit(logThread);futureList.add(future);// 开始下载for (Future<Boolean> booleanFuture : futureList) {booleanFuture.get();}LogUtils.info("文件下载完毕 {},本次下载耗时:{}", fileName, (System.currentTimeMillis() - startTime) / 1000 + "s");LogUtils.info("结束下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));// 文件合并boolean merge = merge(fileName);if (merge) {// 清理分段文件clearTemp(fileName);}LogUtils.info("本次文件下载结束,下载位置为" + fileName);System.exit(0);}/*** 切分下载任务到多个线程** @param url* @param futureList* @throws IOException*/public void splitDownload(String url, List<Future<Boolean>> futureList) throws IOException {long httpFileContentLength = HttpUtls.getHttpFileContentLength(url);// 任务切分long size = httpFileContentLength / DOWNLOAD_THREAD_NUM;long lastSize = httpFileContentLength - (httpFileContentLength / DOWNLOAD_THREAD_NUM * (DOWNLOAD_THREAD_NUM - 1));for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {long start = i * size;Long downloadWindow = (i == DOWNLOAD_THREAD_NUM - 1) ? lastSize : size;Long end = start + downloadWindow;if (start != 0) {start++;}DownloadThread downloadThread = new DownloadThread(url, start, end, i, httpFileContentLength);Future<Boolean> future = executor.submit(downloadThread);futureList.add(future);}}public boolean merge(String fileName) throws IOException {LogUtils.info("开始合并文件 {}", fileName);byte[] buffer = new byte[1024 * 10];int len = -1;try (RandomAccessFile oSavedFile = new RandomAccessFile(fileName, "rw")) {for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName + FILE_TEMP_SUFFIX + i))) {while ((len = bis.read(buffer)) != -1) { // 读到文件末尾则返回-1oSavedFile.write(buffer, 0, len);}}}LogUtils.info("文件合并完毕 {}", fileName);} catch (Exception e) {e.printStackTrace();return false;}return true;}public boolean clearTemp(String fileName) {LogUtils.info("开始清理临时文件 {}{}0-{}", fileName, FILE_TEMP_SUFFIX, (DOWNLOAD_THREAD_NUM - 1));for (int i = 0; i < DOWNLOAD_THREAD_NUM; i++) {File file = new File(fileName + FILE_TEMP_SUFFIX + i);file.delete();}LogUtils.info("临时文件清理完毕 {}{}0-{}", fileName, FILE_TEMP_SUFFIX, (DOWNLOAD_THREAD_NUM - 1));return true;}/*** 使用CheckedInputStream计算CRC*/public static Long getCRC32(String filepath) throws IOException {InputStream inputStream = new BufferedInputStream(new FileInputStream(filepath));CRC32 crc = new CRC32();byte[] bytes = new byte[1024];int cnt;while ((cnt = inputStream.read(bytes)) != -1) {crc.update(bytes, 0, cnt);}inputStream.close();return crc.getValue();}}

4.功能测试

启动main程序,输入下载链接,我们这里使用腾讯QQ安装包的CDN链接(https://dldir1.qq.com/qqfile/qq/PCQQ9.7.17/QQ9.7.17.29225.exe)来测试下载功能

文件下载成功,在项目上级目录文件完整,能够正常使用 

 

总结

本文通过详细解析一个多线程下载器的实现,深入探讨了Java多线程编程的基础知识、工具类的使用以及核心模块的实现。我们学习了如何创建和启动线程、线程的同步和互斥、以及线程池的使用,这些都是多线程下载器的基础。同时,我们了解了如何使用Java的标准库来实现文件操作、网络请求、日志记录等功能。

希望文章对您的学习有帮助,有时间会继续出Java并发编程相关的内容~

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

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

相关文章

Shell常用脚本:文件或目录一键同步到多台服务器

注意&#xff1a; 将本地文件&#xff0c;同步到【/opt/module/script/xsyncByFileIp.txt】里面的目标机器 xsyncByFile.sh #!/bin/bash# 入参参数个数 argsCount$#if(($argsCount0)); thenecho "同步失败&#xff1a;请输入待同步的文件或者目录" exit; fiecho &q…

BetterDisplay for mac V2.2.5 强大的mac显示器管理开源工具

BetterDisplay是Mac OS 一个很棒的工具&#xff01; 它允许您将显示器转换为完全可扩展的屏幕 管理显示器配置覆盖 允许亮度和颜色控制 提供 XDR/HDR 亮度升级&#xff08;Apple Silicon 和 Intel Mac 上兼容的 XDR 或 HDR 显示器的额外亮度超过 100% - 多种方法可用&#x…

CSS 居中对齐 (水平居中 )

水平居中 1.文本居中对齐 内联元素&#xff08;给容器添加样式&#xff09; 限制条件&#xff1a;仅用于内联元素 display:inline 和 display: inline-block; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><…

python单例模式应用之pymongo连接

文章目录 单例模式介绍模块简介安装简单的连接使用单例模式的连接单例类的实现配置的使用单例模式的测试 单例连接的调用 https://gitee.com/allen-huang/python 单例模式介绍 适用场景&#xff1a; 单例模式只允许创建一个对象&#xff0c;因此节省内存&#xff0c;加快对象访…

Linux之selinux详解

华子目录 概念作用selinux与传统的权限区别selinux工作原理名词解释主体&#xff08;subject&#xff09;目标&#xff08;object&#xff09;策略&#xff08;policy&#xff09;&#xff08;多个规则的集合&#xff09;安全上下文&#xff08;security context&#xff09; 文…

RabbitMQ - 04 - Fanout交换机 (广播)

目录 部署demo项目 什么是Fanout交换机 实现Fanout交换机 1.控制台 声明队列 声明交换机 将交换机与队列绑定 2.编写消费者方法 3.编写生产者测试方法 部署demo项目 通过消息队列demo项目进行练习 相关配置看此贴 http://t.csdnimg.cn/hPk2T 注意 生产者消费者的…

【个人开发】llama2部署实践(三)——python部署llama服务(基于GPU加速)

1.python环境准备 注&#xff1a;llama-cpp-python安装一定要带上前面的参数安装&#xff0c;如果仅用pip install装&#xff0c;启动服务时并没将模型加载到GPU里面。 # CMAKE_ARGS"-DLLAMA_METALon" FORCE_CMAKE1 pip install llama-cpp-python CMAKE_ARGS"…

【实验报告】C语言实现猜单词的小游戏

之前帮别人写的一个简单的报告&#xff0c;无偿分享给大家~代码在后面&#xff0c;有一些图片出于懒惰没有上传。比较简单&#xff0c;喜欢的话关注我~&#xff0c;请勿商用~ 1 系统功能模块结构图 该程序主要思路&#xff1a; 头文件设计&#xff0c;存储结构设计&#xff0…

Biomedical knowledge graph-enhanced prompt generation for large language models

1. 生物医学知识图谱增强大语言模型提示生成 论文地址&#xff1a;[2311.17330] Biomedical knowledge graph-enhanced prompt generation for large language models (arxiv.org) 源码地址&#xff1a;https://github.com/BaranziniLab/KG_RAG 2. 摘要 大语言模型&#xff0…

UE4开个头-简易小汽车

跟着谌嘉诚学的小Demo&#xff0c;记录一下 主要涉及到小白人上下车和镜头切换操作 1、动态演示效果 2、静态展示图片 3、蓝图-上下车

机器人大赛有什么用?

机器人大赛在多个方面都具有显著的价值。首先&#xff0c;机器人大赛可以为学生提供一个实践与创新的机会&#xff0c;有助于培养学生的动手实践能力和创新思维。在比赛过程中&#xff0c;学生需要运用所学的知识和技能&#xff0c;设计、制作和调试机器人&#xff0c;这不仅可…

基于冠豪猪优化算法(Crested Porcupine Optimizer,CPO)的无人机三维路径规划(MATLAB)

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化飞行…

在 .NET 项目中复制资源文件夹到生成目录

本文主要介绍在使用 Visual Studio 进行调试和发布时&#xff0c;如何在 .NET 项目中复制资源文件夹到生成目录。 1. 背景 在开发 .NET 项目的过程中&#xff0c;我们有时会遇到需要在 debug 、 release 或是发布时将资源文件夹复制到生成目录的需求。这些资源可能包括图片、配…

Git分支管理(IDEA)

文章目录 Git分支管理&#xff08;IDEA&#xff09;1.Git分支管理&#xff08;IDEA&#xff09;1.基本介绍1.分支理解2.示意图 2.搭建分支和合并的环境1.创建Gitee仓库2.创建普通maven项目3.克隆Gitee项目到E:\GiteeRepository4.复制erp文件夹下的内容到IDEA项目下5.IDEA项目中…

基于微信小程序的校园跑腿小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Django 模版基本语法

Django学习笔记 模版语法 本质&#xff1a;在HTML中写一些占位符&#xff0c;由数据对这些占位符进行替换和处理。 views.py def page2(request):#定义一些变量将变量传送给templates中的html文件name1 sallyname2 yingyinghobbys [swimming,badminton,reading]person {…

03-安装配置jenkins

一、安装部署jenkins 1&#xff0c;上传软件包 为了方便学习&#xff0c;本次给大家准备了百度云盘的安装包 链接&#xff1a;https://pan.baidu.com/s/1_MKFVBdbdFaCsOTpU27f7g?pwdq3lx 提取码&#xff1a;q3lx [rootjenkins ~]# rz -E [rootjenkins ~]# yum -y localinst…

【C语言】linux内核ip_generic_getfrag函数

一、讲解 这个函数ip_generic_getfrag是传输层用于处理分段和校验和的一个辅助函数&#xff0c;它通常用在IP层当需要从用户空间拷贝数据构建成网络数据包时。这个函数的实现提供了拷贝数据和进行校验和计算&#xff08;如果需要的话&#xff09;的功能。函数的参数解释如下&a…

[Spark SQL]Spark SQL读取Kudu,写入Hive

SparkUnit Function&#xff1a;用于获取Spark Session package com.example.unitlimport org.apache.spark.sql.SparkSessionobject SparkUnit {def getLocal(appName: String): SparkSession {SparkSession.builder().appName(appName).master("local[*]").getO…

【C#图解教程】笔记

文章目录 1. C#和.NET框架.NET框架的组成.NET框架的特点CLRCLICLI的重要组成部分各种缩写 2. C#编程概括标识符命名规则&#xff1a; 多重标记和值格式化数字字符串对齐说明符格式字段标准数字格式说明符标准数字格式说明符 表 3. 类型、存储和变量数据成员和函数成员预定义类型…