Linux中的时间函数

参考:

几种取时间的方法(附代码)

Linux中gmtime和localtime的区别(time_t格式转换为tm格式)

C 库函数 - time()

mktime和localtime_r能在多线程环境下使用么?

Linux_C环境编程:时间日期函数总结

细说时间测量RDTSC和RDTSCP

一、C标准库time.h

    C的标准库定义了函数的获取方法,关于函数的数据类型有:time_t、clock_t和struct tm。

  1. time_t:long long型
  2. clock_t:long long型
  3. struct tm

 1.1 时间获取函数tmie()

time_t time( time_t *arg );// 用法
time_t t;
time(&t);
t = time(NULL);

time()返回自 1970年来的时间,以秒为单位。

1.2 时间转换函数

 图中展示了时间转换函数:(1)gmtime、loacltime;(2)mktime;(3)asctime;(4)strftime。

gmtime和loacltime函数

struct tm *gmtime(const time_t *timer)
struct tm *localtime(const time_t *timer)

从函数声明上两个函数一样。但是区别在于:gmtime是将输入转换成0时区,如果想转换成北京时间,需要在年数上加1900,月份上加1(因为是从0开始的),小时数加上8;而localtime是转换成本地时区,还句话来说,是根据系统设置的时区tzname/timezone/daylight转换成设定的时区,如果想转换成北京时间,需要在年数上加1900,月份上加1;

    这里有没有好奇,输入是time_t的指针,但是输出一个指向struct tm的指针,这个指针哪来的?指向哪?看一个示例

#include <stdio.h>
#include <time.h>int main()
{time_t t = time(NULL);struct tm* ttm = localtime(&t);printf("%d-%d-%d %d:%d:%d\n", ttm->tm_year + 1900, ttm->tm_mon + 1,ttm->tm_mday, ttm->tm_hour, ttm->tm_min, ttm->tm_sec);
}

这也是这两个函数的问题所在:其返回的结果是time.h文件中定义的全局静态变量。这就造成了多线程安全的问题。因此多线程下,可能获得时间被其他线程修改了。因此linux中有了线程安全的函数,也在头文件time.h中。

struct tm *gmtime_r( const time_t *timer, struct tm *buf );
struct tm *localtime_r(const time_t *timep, struct tm *buf);

也就是输入变成两个,返回的指针就是输入的buf指针。例如

#include <stdio.h>
#include <time.h>int main()
{time_t t = time(NULL);struct tm ttm;localtime_r(&t, &ttm);printf("%d-%d-%d %d:%d:%d\n", ttm->tm_year + 1900, ttm->tm_mon + 1,ttm->tm_mday, ttm->tm_hour, ttm->tm_min, ttm->tm_sec);
}

这样就是线程安全的?然而并不是的,在mktime和localtime_r能在多线程环境下使用么?文章中说这localtime_r函数都考虑了时区转换,而时区的计算要使用全局变量tzname/timezone/daylight。这本质上就是线程不安全的。

mktime函数

time_t mktime(struct tm *timeptr)

mktime是一个逆过程,将tm的时间转换成秒数。同样,考虑了时区转换,本质上就是线程不安全的。

asctime和strftime函数

    这两个函数都是将tm结构体转换成字符串。

char* asctime(const struct tm *timeptr);
char* asctime_r( const struct tm* time_ptr, char* buf );

从函数原型上看,上面第一个函数也是线程不安全的,莫名其妙输出一个指向指针。下面则是线程安全版本。首先,第一个函数输出固定形式是

Www Mmm dd hh:mm:ss yyyy\nWww ——来自 time_ptr->tm_wday 的星期之日的三字母英文缩写, Mon 、 Tue 、 Wed 、 Thu 、 Fri 、 Sat 、 Sun 之一。 
Mmm ——来自 time_ptr->tm_mon 的月名的三字母英文缩写, Jan 、 Feb 、 Mar 、 Apr 、 May 、 Jun 、 Jul 、 Aug 、 Sep 、 Oct 、 Nov 、 Dec 之一。
dd ——来自 timeptr->tm_mday 的 2 位月之日,如同由 sprintf 以 %2d 打印
hh ——来自 timeptr->tm_hour 的 2 位时,如同由 sprintf 以 %.2d 打印
mm ——来自 timeptr->tm_min 的 2 位分,如同由 sprintf 以 %.2d 打印
ss ——来自 timeptr->tm_sec 的 2 位秒,如同由 sprintf 以 %.2d 打印
yyyy ——来自 timeptr->tm_year + 1900 的 4 位年,如同由 sprintf 以 %4d 打印

而下面那个函数输出形式和第一个一样,但是就存在一个问题,就是buf大小小于26可能就会越界。因此,推荐使用strftime函数。

    strftime函数原型如下:

size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
str -- 这是指向目标数组的指针,用来复制产生的 C 字符串。
maxsize -- 这是被复制到 str 的最大字符数。
format -- 这是 C 字符串,包含了普通字符和特殊格式说明符的任何组合。这些格式说明符由函数替换为表示 tm 中所指定时间的相对应值。格式说明符是:
说明符替换为实例
%a缩写的星期几名称Sun
%A完整的星期几名称Sunday
%b缩写的月份名称Mar
%B完整的月份名称March
%c日期和时间表示法Sun Aug 19 02:56:02 2012
%d一月中的第几天(01-31)19
%H24 小时格式的小时(00-23)14
%I12 小时格式的小时(01-12)05
%j一年中的第几天(001-366)231
%m十进制数表示的月份(01-12)08
%M分(00-59)55
%pAM 或 PM 名称PM
%S秒(00-61)02
%U一年中的第几周,以第一个星期日作为第一周的第一天(00-53)33
%w十进制数表示的星期几,星期日表示为 0(0-6)4
%W一年中的第几周,以第一个星期一作为第一周的第一天(00-53)34
%x日期表示法08/19/12
%X时间表示法02:50:06
%y年份,最后两个数字(00-99)01
%Y年份2012
%Z时区的名称或缩写CDT
%%一个 % 符号%

 例如

#include <stdio.h>
#include <time.h>int main ()
{time_t rawtime;struct tm *info;char buffer[80];time( &rawtime );info = localtime( &rawtime );strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);printf("格式化的日期 & 时间 : |%s|\n", buffer );return(0);
}// 输出
// 格式化的日期 & 时间 : |2018-09-19 08:59:07|

ctime函数

char *ctime(const time_t *timer)

同理,输出是Www Mmm dd hh:mm:ss yyyy\n形式

1.3 时间差值函数difftime()

    time.h还提供了计算两个时间差的函数difftime()

double difftime(time_t time1, time_t time2)

有点奇怪,为什么返回值是double,解释说:在POSIX系统中,time_t是以秒为单位计量的,并且difftime相当于算术减法,但C和C++允许time_t具有分数单位。

1.4 clock函数

    在time.h中还定义了一种用于程序计时的函数clock

clock_t clock(void)

需要配合CLOCKS_PER_SEC一起使用,这个返回的tick数目是从程序开始运行时的tick数,在 32 位系统中,CLOCKS_PER_SEC 等于 1000000,该函数大约每 72 分钟会返回相同的值。在64位系统中clock_t时64位。

#include <time.h>
#include <stdio.h>int main()
{clock_t start_t, end_t;double total_t;int i;start_t = clock();printf("程序启动,start_t = %ld\n", start_t);printf("开始一个大循环,start_t = %ld\n", start_t);for(i=0; i< 10000000; i++){}end_t = clock();printf("大循环结束,end_t = %ld\n", end_t);total_t = (double)(end_t - start_t) / CLOCKS_PER_SEC;printf("CPU 占用的总时间:%f\n", total_t  );printf("程序退出...\n");return(0);
}/*输出
程序启动,start_t = 2614
开始一个大循环,start_t = 2614
大循环结束,end_t = 28021
CPU 占用的总时间:0.025407
程序退出...
*/

1.5 timespec_get函数

    前面的时间精度很低,只到秒,有时候,需要更精确的时间,所以可以有了这个函数

int timespec_get(struct timespec *ts, int base);

这里涉及到另一个数据结构struct timespec

struct timespec {time_t tv_sec;  // 秒long   tv_nsec; // 纳秒
};

base:时间基准常量,C11 标准定义了 TIME_UTC,表示协调世界时 (UTC)。

若成功则为 base 的值,否则为零。

1.6 clock_gettime函数

    time.h文件另一个用于获取高分辨率时间的函数,但需要 POSIX 支持(至少对于unix系统是满足的)。

int clock_gettime(clockid_t clk_id, struct timespec *tp);

 cld_id有四种类型:

  • CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变
  • CLOCK_MONOTONIC,从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
  • CLOCK_PROCESS_CPUTIME_ID,本进程到当前代码系统CPU花费的时间
  • CLOCK_THREAD_CPUTIME_ID,本线程到当前代码系统CPU花费的时间

成功时,这些函数返回 0;否则,这些函数返回 -1 ,并设置 errno,其时间精度依然可以达到纳秒。实际上,还有类似用于设置时间和进程休眠的函数clock_settime、clock_nanosleep。而clock_getres则是用于获取时钟分辨率的

int clock_getres(clockid_t clk_id, struct timespec *res);

函数返回由 clk_id 指定的时钟的分辨率,并将其放置在res指向的位置。但是,如果res为 NULL,则不返回任何分辨率。 

二、linux提供的时间函数

    linux系统本身提供了一个时间接口函数在头文件sys/time.h中,函数名称:gettimeofday

int gettimeofday(struct timeval *tv, struct timezone *tz);

gettimeofday()会把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中,日常使用中并不会获得时区信息,所以第二个参数一般为null。

struct timeval{long tv_sec;  /*秒*/long tv_usec; /*微妙*/
};struct timezone{int tz_minuteswest;/*和greenwich 时间差了多少分钟*/int tz_dsttime;    /*type of DST correction*/
}

两个时间结构体如上。相比时间库time.h,比time精度高,但比timespec_get精度低。同样,可以配合tm结构一起使用,例如

#include <stdio.h>
#include <sys/time.h>
#include <time.h>// gcc -o time_2 time_2.cint main()
{struct timeval tm_now;//1.获取当前时间戳(tv_sec, tv_usec)gettimeofday(&tm_now,NULL); // 第二个参数是时区//2.转换成本地时间,精确到秒struct tm *p_local_tm;p_local_tm = localtime(&tm_now.tv_sec) ;printf("now datetime: %04d-%02d-%02d %02d:%02d:%02d.%06ld\n",p_local_tm->tm_year+1900, p_local_tm->tm_mon+1, p_local_tm->tm_mday, p_local_tm->tm_hour, p_local_tm->tm_min, p_local_tm->tm_sec,tm_now.tv_usec); // 有微秒时间戳了return 0;
}

三、更高级版本

    更高级的时间获取版本不在经过系统或者库函数,而是通过内联汇编直接读取cpu内部保存时间的寄存器。

uint64_t get_tsc() // TSC == Time Stamp Counter寄存器
{
#ifdef __i386__uint64_t x;__asm__ volatile("rdtsc" : "=A"(x));return x;
#elif defined(__amd64__) || defined(__x86_64__)uint64_t a, d;__asm__ volatile("rdtsc" : "=a"(a), "=d"(d));return (d << 32) | a;
#else // ARM架构CPUuint32_t cc = 0;__asm__ volatile ("mrc p15, 0, %0, c9, c13, 0":"=r" (cc));return (uint64_t)cc; 
#endif
}

以x86_64为例(因为看不懂其他的),通过rdtsc指令,可以将TSC的数值存放在EDX:EAX中,然后读取寄存器的数值。

-----------------------------------以下内容全部来自 细说时间测量RDTSC和RDTSCP----------------------

3.1 TSC的坑

    TSC曾经是一个极高精度,极低开销的取时间的方法,但是随着CPU往多核、多处理器、低功耗的方向上走,在使用TSC时就会遇到很多坑。

【坑1】比如有的CPU会根据机器负载情况动态调节工作频率, 那么单位时间CPU的指令周期数就会发生变化,也就很难将其转换成时间。另外,CPU进入休眠再次重启后,TSC会清零。

【坑2】再比如,在同一处理器的多个核心之间,以及不同处理器的不同核心之间,rdtsc的结果是否是同步的呢?如果不同步,那么取时的结果就不能用来相互比较。

【坑3】再比如,Intel的处理器自Pentium Pro开始,引入了乱序执行的功能,导致程序读取的TSC结果可能不准。如果编写测试程序的时候没有主动回避,也可能会掉到坑里。

3.2 官方填坑

在较新版本的CPU中,引入了常量速率TSC的特性(constant rate TSC)。可以通过如下命令查看你的CPU是否支持(我的机器有四个核,因此输出了四条):cat /proc/cpuinfo | grep constant_tsc

 支持该特性的CPU,其TSC是按照其标称频率流逝的,与CPU的实际工作频率与状态无关。如果你的CPU也是支持constant_tsc特性的,那么【坑1】算是填上了。

    关于【坑2】,即不同核心读取的tsc是否同步,目前没有找到统一的说法,Intel的官方手册也没有明说,比如:vol 3b,17.15.1 Invariant TSC章节

  The time stamp counter in newer processors may support an enhancement, referred to as invariant TSC. Processor’s support for invariant TSC is indicated by CPUID.80000007H:EDX[8].
The invariant TSC will run at a constant rate in all ACPI P-, C-. and T-states. This is the architectural behavior moving forward. On processors with invariant TSC support, the OS may use the TSC for wall clock timer services (instead of ACPI or HPET timers). TSC reads are much more efficient and do not incur the overhead associated with a ring transition or access to a platform resource.

    但这里面只是说TSC能够在CPU处于任何(电源)状态下都能保证以标称速率递增,并没有明确说明TSC能够在多核甚至多处理器的情况下保持同步。另一个蛛丝马迹是在Linux内核代码中

这里有一个unsynchronized_tsc()函数,用于判断系统的TSC是不是同步的,代码实现如下:

/** Make an educated guess if the TSC is trustworthy and synchronized* over all CPUs.*/
int unsynchronized_tsc(void)
{if (!boot_cpu_has(X86_FEATURE_TSC) || tsc_unstable)return 1;#ifdef CONFIG_SMPif (apic_is_clustered_box())return 1;
#endifif (boot_cpu_has(X86_FEATURE_CONSTANT_TSC))return 0;if (tsc_clocksource_reliable)return 0;/** Intel systems are normally all synchronized.* Exceptions must mark TSC as unstable:*/if (boot_cpu_data.x86_vendor != X86_VENDOR_INTEL) {/* assume multi socket systems are not synchronized: */if (num_possible_cpus() > 1)return 1;}return 0;
}

这里有几个有意思的点:

  • 开头的注释说,“make an educated guess”,即有根据的猜测,即这里是不是TSC同步的判断依然是一个猜测
  • 中间的代码判断了是否开启了CONSTANT TSC特性,如果开启就直接返回0,即TSC是同步的,也就是说,只要我们在cpuinfo里看到constant_tsc的flag,就证明我们的机器的TSC是同步的
  • 后面还有一句注释“Intel systems are normally all synchronized.Exceptions must mark TSC as unstable:”,即Intel的系统,只要用户没有手动禁用TSC同步,一般都是同步的。
  • 在Intel CPU下还有一个注释“assume multi socket systems are not synchronized”,即在多处理器系统上,不同CPU(处理器、socket、NUMA节点)之间的TSC是不同步的。

看到这里,我们基本上可以确定了,即:

  • 如果你的cpuinfo里有constant_tsc的flag,那么无论在同一CPU不同核心之间,还是在不同CPU的不同核心之间,TSC都是同步的,可以随便用
  • 如果你用的是Intel的CPU,但是cpuinfo里没有constant_tsc的flag,那么在同一处理器的不同核心之间,TSC仍然是同步的,但是不同CPU的不同核心之间不同步,尽量不要用

至此,【坑2】也基本上解决了。

关于【坑3】,即乱序执行问题,可以使用RDTSCP命令来代替RDTSC,前者开销虽然略高,但胜在稳定好用。另外,如果不想用这个指令,还可以用memory barrier技术(后面的文章中我们将详细解释该技术)或者CPUID指令来实现,不过这两者我都没试,据说开销也不小,详细资料可以就参见参考资料中的wiki页面和intel的官方手册。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> 
#include <stdint.h>
#include <time.h>
#include <sys/time.h>// gcc -o time_6 time_6.cuint64_t get_tsc()
{uint64_t a, d;__asm__ volatile("rdtsc" : "=a"(a), "=d"(d));return (d << 32) | a;
}uint64_t get_tscp()
{uint64_t a, d;__asm__ volatile("rdtscp" : "=a"(a), "=d"(d));return (d << 32) | a;
}#define LOOP_TIMES 1000000000int main(int argc, char **argv)
{uint64_t beg_tsc, end_tsc;long loop;long sum;printf("-------------rdtsc-------------\n");loop = LOOP_TIMES;sum = 0;while(loop--){beg_tsc = get_tsc(); end_tsc = get_tsc();sum += (end_tsc - beg_tsc);}printf("AVG_CYCLE : %ld\n", sum / LOOP_TIMES);sleep(1);printf("-------------rdtscp-------------\n");loop = LOOP_TIMES;sum = 0;while(loop--){beg_tsc = get_tscp(); end_tsc = get_tscp();sum += (end_tsc - beg_tsc);}printf("AVG_CYCLE : %ld\n", sum / LOOP_TIMES);return 0;
}

测试结果如下:

 

我一共跑了三次,每次差别都不大,RDTSCP指令比RDTSC多耗费10个指令周期左右,慢不到1倍。如果你能接受这点差别,建议还是用RDTSCP命令吧。另外,RDTSCP指令也是需要平台支持的,是否支持可以使用cat /proc/cpuinfo | grep rdtscp命令查看

3.3 使用建议

  • 如果你的cpuinfo里面没有constant_tsc的flag,建议老老实实用clock_gettime吧,或者换台支持constant_tsc的机器
  • 如果你的cpuinfo里面有constant_tsc的flag,那么在同一处理器的不同核心之间可以放心使用TSC,跨处理器的不同核之间,尽量避免使用,可能会有未知的问题
  • 如果不是对性能极其敏感,尽量使用RDTSCP代替RDTSC,前者略慢,但能避免CPU乱序执行问题

参考资料

再论 Time stamp counter - 一念天堂 - 博客园

Pitfalls of TSC usage | Oliver Yang

linux - rdtsc accuracy across CPU cores - Stack Overflow

另外C++封装代码:GitHub - MengRao/tscns: A low overhead nanosecond clock based on x86 TSC

 -----------------------------------------至此,上面全部来自那篇博客的内容已结束-----------------------------

四、总结

最后放上还是上面那个博客测试的上面的时间函数的性能(没办法,这个人太厉害了.....)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> 
#include <stdint.h>
#include <time.h>
#include <sys/time.h>// gcc -o time_5 time_5.cuint64_t get_by_time()
{time_t tm_now;time(&tm_now);return tm_now;
}uint64_t get_by_gettimeofday()
{struct timeval tm_now;gettimeofday(&tm_now,NULL);return tm_now.tv_sec;
}uint64_t get_by_clock_gettime()
{struct timespec tm_now;clock_gettime(CLOCK_REALTIME, &tm_now);return tm_now.tv_sec;
}uint64_t get_cpu_freq()
{FILE *fp=popen("lscpu | grep CPU | grep MHz | awk  {'print $3'}","r");if(fp == NULL)return 0;char cpu_mhz_str[200] = { 0 };fgets(cpu_mhz_str,80,fp);fclose(fp);return atof(cpu_mhz_str) * 1000 * 1000;
}uint64_t get_by_tsc()
{uint64_t a, d;__asm__ volatile("rdtsc" : "=a"(a), "=d"(d));return (d << 32) | a;
}void print_diff(uint64_t loop_times, uint64_t beg_tsc, uint64_t end_tsc)
{   double tt_ns = (end_tsc - beg_tsc) * 1.0 * 1000 * 1000 * 1000 / get_cpu_freq();printf("Number Loop :   %lu\n", loop_times);printf("Total Time  :   %.02lf ns\n", tt_ns);printf("Avg Time    :   %.02lf ns\n", tt_ns / loop_times);
}#define LOOP_TIMES 1000000000int main(int argc, char **argv)
{uint64_t beg_tsc, end_tsc;long loop;printf("-------------time()-------------\n");loop = LOOP_TIMES;beg_tsc = get_by_tsc(); while(loop--)get_by_time();end_tsc = get_by_tsc();print_diff(LOOP_TIMES, beg_tsc, end_tsc);printf("-------------gettimeofday()-------------\n");loop = LOOP_TIMES;beg_tsc = get_by_tsc(); while(loop--)get_by_gettimeofday();end_tsc = get_by_tsc();print_diff(LOOP_TIMES, beg_tsc, end_tsc);printf("-------------clock_gettime()-------------\n");loop = LOOP_TIMES;beg_tsc = get_by_tsc(); while(loop--)get_by_clock_gettime();end_tsc = get_by_tsc();print_diff(LOOP_TIMES, beg_tsc, end_tsc);printf("-------------rdtsc-------------\n");loop = LOOP_TIMES;beg_tsc = get_by_tsc(); while(loop--)get_by_tsc();end_tsc = get_by_tsc();print_diff(LOOP_TIMES, beg_tsc, end_tsc);return 0;
}

 

可以看到:

  • time函数最快,但是精度太低
  • gettimeofday和clock_gettime虽然精度高,但是都比较慢
  • rdtsc精度和速度都十分优秀

另外需要注意一点的是,上述测试结果跟机器配置有很大关系。

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

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

相关文章

探索NVM:让Node.js开发如虎添翼的利器

文章目录 前言一、NVM简介&#xff1a;版本管理的瑞士军刀二、NVM能解决什么问题&#xff1f;三、如何使用NVM​&#xff1f;总结 前言 在这个日新月异的编程世界里&#xff0c;Node.js凭借其高效的非阻塞I/O操作和轻量级的事件驱动模型&#xff0c;成为了全栈开发、微服务架构…

音视频入门基础:H.264专题(16)——FFmpeg源码中,判断某文件是否为H.264裸流文件的实现

一、引言 通过FFmpeg命令可以判断出某个文件是否为AnnexB格式的H.264裸流&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为AnnexB格式的H.264裸流呢&#xff1f;它内部其实是通过h264_probe函数来判断的。从文章《FFmpeg源码&#xff1a;av_probe_input_format3函数分析》中…

winfrom 文件自动生成

数据页面展示 添加定时器执行每个表数据的生成计划 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.IO; using System.Windows.Forms; using …

【STM32嵌入式系统设计与开发---拓展】——1_11_1串口通信(USART)

这里写目录标题 1、一般我们都怎么进行通信的&#xff1f;&#xff08;1&#xff09;串行通行&#xff08;Serial Communication&#xff09;&#xff08;2&#xff09;并行通信&#xff08;3&#xff09;无线网络通信&#xff08;4&#xff09;网络通信&#xff08;5&#xff…

Django [实战] 通过表单上传文件

在Django中上传文件通常涉及到以下几个步骤&#xff1a; 设置你的模型以包含一个FileField或ImageField&#xff08;如果上传的是图片&#xff09;。创建一个表单&#xff0c;该表单包含一个Form或ModelForm&#xff0c;其中包含一个FileField。在你的视图中处理上传的文件。在…

Modbus转BACnet/IP网关快速对接Modbus协议设备与BA系统

摘要 在智能建筑和工业自动化领域&#xff0c;Modbus和BACnet/IP协议的集成应用越来越普遍。BA&#xff08;Building Automation&#xff0c;楼宇自动化&#xff09;系统作为现代建筑的核心&#xff0c;需要高效地处理来自不同协议的设备数据&#xff0c;负责监控和管理建筑内…

可以免费合并pdf的软件 合并pdf文件的软件免费 合并pdf的软件免费

在数字化办公的今天&#xff0c;pdf格式因其稳定性和跨平台兼容性被广泛使用。然而&#xff0c;当我们需要将多个 pdf 文件合并为一个时&#xff0c;却往往感到力不从心。本文将为你介绍几款强大的pdf文件合并软件&#xff0c;让你轻松管理文档。 方法一、使用pdf转换器 步骤1…

easyExcel和poi的版本对应

easypoi3.0.5对应的poi版本_easypoi和poi版本对应-CSDN博客 https://github.com/alibaba/easyexcel/blob/v3.2.0/pom.xml 解决 java.lang.NoClassDefFoundError: org/apache/poi/POIXMLTypeLoader 报错-CSDN博客 参考这个文档解决的- 引入最佳版本是3.15版本 java.lang.NoClas…

微服务:网关

网关 网关,即网络的关口,当一个网络传输到另一个网络时就需要经过网关来实现 数据的路由和转发 以及 数据安全的校验 网关技术实现 SpringCloudGateWay: 基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强 SpringCloudGateWay 依赖 <!--网关--><depe…

JAVA毕业设计152—基于Java+Springboot+vue+小程序的个人健康管理系统小程序(源代码+数据库+15000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue小程序的个人健康管理系统小程序(源代码数据库15000字论文)152 一、系统介绍 本项目前后端分离带小程序(可以改为ssm版本)&#xff0c;分为用户、管理员两种…

Ubuntu下载jdk:cannot execute binary file

虚拟机上Ubuntu系统安装jdk且配置环境之后&#xff0c;java -version显示cannot execute binary file&#xff0c;多番查阅推测是由于系统和jdk版本不兼容的原因。 uname -m查看系统版本位i686&#xff0c;是32位的&#xff0c;和64位的jdk版本不兼容。因此&#xff0c;下载32位…

[css3] 如何设置边框颜色渐变

div {border: 4px solid;border-image: linear-gradient(to right, #8f41e9, #578aef) 1; }参考&#xff1a; 5种CSS实现渐变色边框&#xff08;Gradient borders方法的汇总

如何通过smtp设置使ONLYOFFICE协作空间服务器可以发送注册邀请邮件

什么是ONLYOFFICE协作空间 ONLYOFFICE协作空间&#xff0c;是Ascensio System SIA公司出品的&#xff0c;基于Web的&#xff0c;开源的&#xff0c;跨平台的&#xff0c;在线文档编辑和协作的解决方案。在线Office包含了最基本的办公三件套&#xff1a;文档编辑器、幻灯片编辑…

FPGA实验1:简单逻辑电路

一、实验目的及要求 学习Create-SOPC实验平台的使用方法&#xff1b;熟悉Quartus II 软件平台和使用 VHDL 语言设计电路的方法&#xff1b;学习简单逻辑电路的设计、仿真和硬件测试。 二、实验原理 运用Quartus II 集成环境下的VHDL文本设计方法设计半加器&#xff0c;进行波…

LInux工具(2)

目录 1.关于底行模式的一个设置 1.1设置行号 1.2取消行号 2.简单vim配置 2.1简单认识 2.2配置选项 2.3其他说明 3.库的引入 3.1背景知识 3.2对应指令 3.3相关介绍 3.4.o文件和库的链接 3.5静态库的安装和测试 3.6动静态库对比 1.关于底行模式的一个设置 1.1设置行…

黑马点评-Postman卡住sending Requst原因解决

不知道为什么&#xff0c;用这个c1e1d5的token就会一直卡死&#xff0c;但是换了一个token就解决了&#xff0c;目前不知道为什么 解决了&#xff0c;原来是这个请求下面的函数发生了死循环&#xff01;&#xff01;太瓜皮了我超&#xff01; 把num写成了count&#xff0c;导…

函数(递归)

递归&#xff1a;程序调用自身编程技巧称为递归。 在学习递归前需要粗略的了解一下内存&#xff0c;内存分为三类&#xff0c;分别是栈区、堆区和静态区。对于栈区来说&#xff0c;每调用一次函数都会为本次函数开辟一块空间&#xff0c;然而栈区也是有空间限制的&#xff0c;随…

Golang | Leetcode Golang题解之第242题有效的字母异位词

题目&#xff1a; 题解&#xff1a; func isAnagram(s, t string) bool {if len(s) ! len(t) {return false}cnt : map[rune]int{}for _, ch : range s {cnt[ch]}for _, ch : range t {cnt[ch]--if cnt[ch] < 0 {return false}}return true }

Temporal-Kit 及 Ebsynth-流程

https://www.youtube.com/watch?vBL77HVIviJM 预处理 Ebsynth-流程

全国区块链职业技能大赛第八套区块链产品需求分析与方案设计

任务1-1:区块链产品需求分析与方案设计 医疗健康平台中涉及到医院、医生、患者等参与方,他们需要在区块链医疗健康平台中完成账户注册、身份上链、挂号就诊、查询病例等多种业务活动。通过对业务活动的功能分析,可以更好的服务系统的开发流程。基于医疗健康平台系统架构,以…