Java日期工具-Joda-Time和FastDateFormat
目录
文章目录
- 1.基本介绍
- 2.JDK中的SimpleDateFormat
- 2.1问题复现
- 2.2问题解析
- 2.3解决方案
- 2.3.1方案一:每次都实例化
- 2.3.2方案二:使用synchronized同步
- 2.3.3方案三:使用ThreadLocal
- 3.Joda-Time工具
- 3.1使用
- 3.1.1引入依赖
- 3.1.2使用
- 3.1.3补充说明
- 3.2封装JodaTime工具类
- 4.JDK8中的日期库
- 4.1使用简单案例
- 4.2和JodaTime对比
- 5.Apache的 FastDateFormat
- 原理解析
- 6.总结
1.基本介绍
Java自带日期格式化工具DateFormat ,但是DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐使用joda-time 库 或者使用Apache的 FastDateFormat。
2.JDK中的SimpleDateFormat
这里主要是针对jdk7之前的,在jdk8中jdk也提供了线程安全的日期工具类,我们后面会提到。
2.1问题复现
以下代码是复现 SimpleDateFormat中的线程不安全问题:
import java.text.ParseException;
import java.text.SimpleDateFormat;public class TestSimpleDateFormat {//(1)创建单例实例static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {//(2)创建多个线程,并启动for (int i = 0; i <10 ; ++i) {Thread thread = new Thread(new Runnable() {public void run() {try {//(3)使用单例日期实例解析文本System.out.println(sdf.parse("2021-12-13 15:17:27"));} catch (ParseException e) {e.printStackTrace();}}});thread.start();//(4)启动线程}}
}
上面代码做了如下几件事:
- 代码(1)创建了SimpleDateFormat的一个实例
- 代码(2)创建10个线程,每个线程都公用同一个sdf对象对文本日期进行解析,多运行几次就会抛出java.lang.NumberFormatException异常,加大线程的个数有利于该问题复现。
- 代码(3)使用单例的simpleDateFormat解析文本
- 代码(4)启动线程
运行以上代码会抛出以下异常:
Exception in thread "Thread-4" Exception in thread "Thread-6" Exception in thread "Thread-7" Exception in thread "Thread-0" Exception in thread "Thread-5" Exception in thread "Thread-8" Exception in thread "Thread-2" Exception in thread "Thread-1" Exception in thread "Thread-3" Exception in thread "Thread-9" java.lang.NumberFormatException: empty Stringat sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2089)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.njit.dateformat.jdk.TestSimpleDateFormat$1.run(TestSimpleDateFormat.java:16)at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
出现了线程安全问题,导致程序无法正常执行结束。
2.2问题解析
这里我先贴以下SimpleDateFormat的类图结构:
可知每个SimpleDateFormat实例里面有一个Calendar对象,从后面会知道其实SimpleDateFormat之所以是线程不安全的就是因为Calendar是线程不安全的,后者之所以是线程不安全的是因为其中存放日期数据的变量都是线程不安全的,比如里面的fields,time等。
通过追踪源码:
public Date parse(String text, ParsePosition pos){//(1)解析日期字符串放入CalendarBuilder的实例calb中.....Date parsedDate;try {//(2)使用calb中解析好的日期数据设置calendarparsedDate = calb.establish(calendar).getTime();...}catch (IllegalArgumentException e) {...return null;}return parsedDate;}
Calendar establish(Calendar cal) {...//(3)重置日期对象cal的属性值cal.clear();//(4) 使用calb中中属性设置cal...//(5)返回设置好的cal对象return cal;
}
代码(1)主要的作用是解析字符串日期并把解析好的数据放入了 CalendarBuilder的实例calb中,CalendarBuilder是一个建造者模式,用来存放后面需要的数据。
代码(3)重置Calendar对象里面的属性值,如下代码:
public final void clear(){for (int i = 0; i < fields.length; ) {stamp[i] = fields[i] = 0; // UNSET == 0isSet[i++] = false;}areAllFieldsSet = areFieldsSet = false;isTimeSet = false;}
}
- 代码(4)使用calb中解析好的日期数据设置cal对象
- 代码(5) 返回设置好的cal对象
从上面步骤可知步骤(3)(4)(5)操作不是原子性操作,当多个线程调用parse
方法时候比如线程A执行了步骤(3)(4)也就是设置好了cal对象,在执行步骤(5)前线程B执行了步骤(3)清空了cal对象,由于多个线程使用的是一个cal对象,所以线程A执行步骤(5)返回的就可能是被线程B清空后的对象,当然也有可能线程B执行了步骤(4)被线程B修改后的cal对象。从而导致程序错误。
2.3解决方案
2.3.1方案一:每次都实例化
每次使用时候new一个SimpleDateFormat的实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其它引用,就会需要被回收,开销会很大。
2.3.2方案二:使用synchronized同步
究其原因是因为多线程下步骤(3)(4)(5)三个步骤不是一个原子性操作,那么容易想到的是对其进行同步,让(3)(4)(5)成为原子操作,可以使用synchronized进行同步,具体如下:
import java.text.ParseException;
import java.text.SimpleDateFormat;public class TestSimpleDateFormatImprove {// (1)创建单例实例static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {// (2)创建多个线程,并启动for (int i = 0; i < 10; ++i) {Thread thread = new Thread(new Runnable() {public void run() {try {// (3)使用单例日期实例解析文本synchronized (sdf) {System.out.println(sdf.parse("2021-12-13 15:17:27"));}} catch (ParseException e) {e.printStackTrace();}}});thread.start();// (4)启动线程}}
}
使用同步意味着多个线程要竞争锁,在高并发场景下会导致系统响应性能下降。
2.3.3方案三:使用ThreadLocal
这样每个线程只需要使用一个SimpleDateFormat实例相比第一种方式大大节省了对象的创建销毁开销,并且不需要对多个线程直接进行同步,使用ThreadLocal方式代码如下:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;public class TestSimpleDateFormatThreadLocal {// (1)创建threadlocal实例static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){@Overrideprotected SimpleDateFormat initialValue(){return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}};public static void main(String[] args) {// (2)创建多个线程,并启动for (int i = 0; i < 10; ++i) {Thread thread = new Thread(new Runnable() {public void run() {try {// (3)使用单例日期实例解析文本System.out.println(safeSdf.get().parse("2021-12-13 15:17:27"));} catch (ParseException e) {e.printStackTrace();}}});thread.start();// (4)启动线程}}
}
代码(1)创建了一个线程安全的SimpleDateFormat实例,步骤(3)在使用的时候首先使用get()方法获取当前线程下SimpleDateFormat的实例,在第一次调用ThreadLocal的get()方法适合会触发其initialValue方法用来创建当前线程所需要的SimpleDateFormat对象。
3.Joda-Time工具
由于Joda-Time很优秀,在Java 8出现前的很长时间内成为Java中日期时间处理的事实标准,用来弥补JDK的不足。在Java 8中引入的java.time
包是一组新的处理日期时间的API,遵守JSR 310
。值得一提的是,Joda-Time的作者Stephen Colebourne
和Oracle一起共同参与了这些API的设计和实现。
3.1使用
3.1.1引入依赖
<dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.5</version>
</dependency>
下面介绍5个最常用的date-time类:
- Instant - 不可变的类,用来表示时间轴上一个瞬时的点
- DateTime - 不可变的类,用来替换JDK的Calendar类
- LocalDate - 不可变的类,表示一个本地的日期,而不包含时间部分(没有时区信息)
- LocalTime - 不可变的类,表示一个本地的时间,而不包含日期部分(没有时区信息)
- LocalDateTime - 不可变的类,表示一个本地的日期-时间(没有时区信息)
注意:不可变的类,表明了正如Java的String类型一样,其对象是不可变的。即,不论对它进行怎样的改变操作,返回的对象都是新对象。
Instant比较适合用来表示一个事件发生的时间戳。不用去关心它使用的日历系统或者是所在的时区。
DateTime的主要目的是替换JDK中的Calendar类,用来处理那些时区信息比较重要的场景。
LocalDate比较适合表示出生日期这样的类型,因为不关心这一天中的时间部分。
LocalTime适合表示一个商店的每天开门/关门时间,因为不用关心日期部分。
3.1.2使用
JodaTime包中的是线程安全的,日期格式化使用了自己的类来替换了JDK实现中的线程不安全类Calendar,从而保证了线程安全,具体使用如下:
import org.joda.time.*;public class TestJodaTime {static DateTimeFormatter baseFormat = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {// 日期格式化// 方式一DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");DateTime dt = new DateTime();System.out.println(dt.toString(formatter));//日期格式化 方式二String dateFormat = "yyyy-MM-dd HH:mm:ss";System.out.println(dt.toString(dateFormat));// 测试线程安全问题for (int i = 0; i <100 ; ++i) {Thread thread = new Thread(new Runnable() {public void run() {DateTime dateTime = baseFormat.parseDateTime("2021-12-19 17:07:05");System.out.println(dateTime);}});thread.start();}// 获取当前时间DateTime dt = new DateTime();System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));// 获取月份及年dt = new DateTime();System.out.println(dt.monthOfYear().getAsText());System.out.println(dt.year().getAsText());// 获取10年后dt = new DateTime();DateTime dtyear10 = dt.year().addToCopy(10);System.out.println(dtyear10.toString("yyyy-MM-dd HH:mm:ss"));// 获取当天的零点dt=new DateTime().withMillisOfDay(0);System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));//获取明天的零点dt=new DateTime().withMillisOfDay(0).plusDays(1);System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));//获取每个月的第一天和最后一天dt=new DateTime();System.out.println(dt.dayOfMonth().withMinimumValue().dayOfMonth().get());System.out.println(dt.dayOfMonth().withMaximumValue().dayOfMonth().get());// 获取当天的12:30dt=new DateTime().withHourOfDay(12).withMinuteOfHour(30).withSecondOfMinute(0);System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));// 获取每个月10号12:30dt=new DateTime().withDayOfMonth(10).withHourOfDay(12).withMinuteOfHour(30).withSecondOfMinute(0);System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));// 获取每年3月10号12:30dt=new DateTime().withMonthOfYear(3).withDayOfMonth(10).withHourOfDay(12).withMinuteOfHour(30).withSecondOfMinute(0);System.out.println(dt.toString("yyyy-MM-dd HH:mm:ss"));// 时间差DateTime start=new DateTime(2021,2,25,9,0);DateTime end=new DateTime(2021,3,27,13,10);Period period=new Period(start,end);System.out.println("month:"+period.getMonths());System.out.println("days:"+period.getDays());System.out.println("hours:"+period.getHours());System.out.println("minutes:"+period.getMinutes());System.out.println("second:"+period.getSeconds());// 相差天数DateTime start2=new DateTime(2021,3,25,9,0);DateTime end2 =new DateTime(2021,3,27,13,10);System.out.println(Days.daysBetween(start2,end2).getDays());//相差小时数 1DateTime start3=new DateTime(2021,3,25,9,0);DateTime end3 =new DateTime(2021,3,27,13,10);System.out.println(Hours.hoursBetween(start3,end3).getHours());//相差小时数 2DateTime start4=new DateTime(2021,3,25,9,0);DateTime end4=new DateTime(2021,3,27,13,10);Duration duration=new Duration(start4,end4);System.out.println(duration.getStandardHours());// 相差分钟数DateTime start5=new DateTime(2021,3,25,9,0);DateTime end5 =new DateTime(2021,3,27,13,10);System.out.println(Minutes.minutesBetween(start5,end5).getMinutes());// 相差秒数DateTime start6=new DateTime(2021,3,25,9,0);DateTime end6 =new DateTime(2021,3,27,13,10);System.out.println(Seconds.secondsBetween(start6,end6));// 判断时间是否在某个范围内DateTime start7=new DateTime(2021,2,25,9, 0);DateTime end7=new DateTime(2021,3,27,13,10);Interval interval=new Interval(start7,end7);System.out.println(interval.contains(new DateTime(2021,3,27,13,00)));System.out.println(interval.contains(new DateTime(2021,3,27,13,30)));//是否为闰月dt = new DateTime();DateTime.Property month = dt.monthOfYear();System.out.println("是否闰月:" + month.isLeap());}
}
3.1.3补充说明
JodaTime中创建DateTime对象的方式有如下几种,更推荐使用第二种方式:
//方法一DateTime dateTime = new DateTime();
//方法二(推荐使用此方法)
DateTime dateTime = SystemFactory.getClock().getDateTime();
//方法三
DateTime dateTime = new DateTime(2000, //year1, // month1, // day0, // hour (midnight is zero)0, // minute0, // second0 // milliseconds
);
3.2封装JodaTime工具类
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Months;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;import java.util.Date;
import java.util.concurrent.TimeUnit;public class DateTimeUtil {private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";public static String dateToStr(Date date, String formatStr) {if (date == null) {return StringUtils.EMPTY;}DateTime dateTime = new DateTime(date);return dateTime.toString(formatStr);}public static Date strToDate(String dateTimeStr, String formatStr) {DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(formatStr);DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);return dateTime.toDate();}public static String dateToStr(Date date) {if (date == null) {return StringUtils.EMPTY;}DateTime dateTime = new DateTime(date);return dateTime.toString(STANDARD_FORMAT);}public static Date strToDate(String dateTimeStr) {DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);return dateTime.toDate();}public static String dateToStr(long timestamp, String pattern) {DateTime dateTime = new DateTime(timestamp);return dateTime.toString(pattern);}public static String dateToStr(long timestamp) {return dateToStr(timestamp, STANDARD_FORMAT);}/*** 前month月的时间** @param months 月数* @return date*/public static Date minusMonth(Months months) {DateTime dateTime = new DateTime();DateTime minus = dateTime.minus(months);return minus.toDate();}public static long currentTime() {return System.currentTimeMillis();}public static void sleep(TimeUnit timeUnit, int timeout) {try {timeUnit.sleep(timeout);} catch (Exception ex) {throw new RuntimeException(ex);}}public static long strToMills(String dateTimeStr) {DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(STANDARD_FORMAT);DateTime dateTime = dateTimeFormatter.parseDateTime(dateTimeStr);return dateTime.getMillis();}
}
4.JDK8中的日期库
jdk8中的日期库吸收了JodaTime库的优秀方案(JodaTime作者参与API的设计和实现),在使用中两个使用的差别并不是很大。(你可以认为JDK8官方就是借鉴JodaTime)。在JodaTime官网也推荐大家在KDJ8之后迁移到JDK中的日期库中。
4.1使用简单案例
因为和JodaTime的使用差别不是很大,这里只做简单的demo:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;public class TestJDK8Time {static DateTimeFormatter baseFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {// 日期格式化DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime localDateTime = LocalDateTime.now();System.out.println(formatter.format(localDateTime));// 日期增减LocalDateTime localDateTime2 = LocalDateTime.now();localDateTime2 = localDateTime.plusDays(2); // 增加两天localDateTime2 = localDateTime.plusHours(2); // 增加两个小时localDateTime2 = localDateTime.minusDays(1); //减少一天localDateTime2 = localDateTime2.minusHours(1); // 减少一个小时// 测试线程安全问题for (int i = 0; i <100 ; ++i) {Thread thread = new Thread(new Runnable() {public void run() {LocalDateTime parse = LocalDateTime.parse("2021-12-19 17:07:05", baseFormat);System.out.println(parse);}});thread.start();}}
4.2和JodaTime对比
具体使用的区别可参考:Java基础之如何取舍Joda与 Java8 日期库
5.Apache的 FastDateFormat
我们知道,SimpleDateFormat 是线程不安全的,主要原因是 format 方法内部调用 calendar.setTime 方法,整个过程都是没有加锁或同步的,如果同时有多个线程调用到这一步,则会出现线程安全问题。
public final String format(Date date) {return format(date, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString();
}// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {// Convert input date to time field listcalendar.setTime(date);...
}
所以,大部分时候则是在方法内部 new 出新的 DateFormat 对象再做格式化,如:DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
。
但在高访问量的情况下,频繁创建实例也会导致内存开销大和 GC 频繁问题。
Apache 的 commons-lang 包下有个 FastDateFormat 可以方便的解决上述问题。它主要是用来进行日期的转化的,具体使用如下:
首先引入依赖:
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.10</version></dependency>
使用代码如下:
import org.apache.commons.lang3.time.FastDateFormat;import java.text.ParseException;public class TestFastDateFormat {static FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {// 测试线程安全问题for (int i = 0; i <100 ; ++i) {Thread thread = new Thread(new Runnable() {public void run() {try {System.out.println(fdf.parse("2021-12-13 18:17:27"));} catch (ParseException e) {e.printStackTrace();}}});thread.start();}}
}
原理解析
首先我们看他是如何避免掉线程安全问题:
//FastDateFormat
@Override
public String format(final Date date) {return printer.format(date);
}@Overridepublic String format(final Date date) {final Calendar c = Calendar.getInstance(timeZone, locale);c.setTime(date);return applyRulesToString(c);}
源码中 Calender 是在 format 方法里创建的,肯定不会出现 setTime 的线程安全问题。这样线程安全疑惑解决了。
然后我们看下他是如何提升性能的,看下对应的源码:
/*** 获得 FastDateFormat实例,使用默认格式和地区** @return FastDateFormat*/
public static FastDateFormat getInstance() {return CACHE.getInstance();
}/*** 获得 FastDateFormat 实例,使用默认地区<br>* 支持缓存** @param pattern 使用{@link java.text.SimpleDateFormat} 相同的日期格式* @return FastDateFormat* @throws IllegalArgumentException 日期格式问题*/
public static FastDateFormat getInstance(final String pattern) {return CACHE.getInstance(pattern, null, null);
}
这里有用到一个CACHE,看来用了缓存,往下看
private static final FormatCache<FastDateFormat> CACHE = new FormatCache<FastDateFormat>(){@Overrideprotected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {return new FastDateFormat(pattern, timeZone, locale);}
};//
abstract class FormatCache<F extends Format> {...private final ConcurrentMap<MultipartKey, F> cInstanceCache= new ConcurrentHashMap<>(7);private static final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache= new ConcurrentHashMap<>(7);...
}
在getInstance 方法中加了ConcurrentMap 做缓存,提高了性能。且我们知道ConcurrentMap 也是线程安全的。
6.总结
如果是使用JDK8以及之后的版本,建议使用JDK自带的时间库来处理日期时间的转换等场景,如果是JDK8之前的版本,则建议使用JodaTime库来处理。
Java日期工具-Joda-Time和FastDateFormat相关推荐
- java日期工具类DateUtil
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. DateUtil类 [java] view plain copy package com.util; ...
- java日期工具类(转载)
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...
- Java 日期工具类(今天、昨天、本周、上周、本月、上月、本年及特定时间的开始时间和结束时间)
timestamp格式的部分 /*** 获取当天的开始时间* @return Timestamp* @author LoveEmperor_王子様* date: 2019/8/15 15:50*/pu ...
- java日期工具类、日期格式校验、日期格式化
文章目录 前言 日期格式校验 String转Date Date格式化为String类型 获取指定日期所在季度的第一天 获取指定日期所在季度的最后一天 LocalDate转Date 日期utils工具类 ...
- 天干地支 工具类java_java日期工具类DateUtil-续二
该版本是一次较大的升级,农历相比公历复杂太多(真佩服古人的智慧),虽然有规律,但涉及到的取舍.近似的感念太多,况且本身的概念就已经很多了,我在网上也是查阅了很多的资料,虽然找到一些计算的方法,但都有些 ...
- java中常用的日期工具类
java中常用的日期工具类 日期相关的类: package net.yto.ofclacct.core.util;import java.text.ParseException; import jav ...
- java日期转化工具类
package com.rest.ful.utils;import java.text.DateFormat; import java.text.ParseException; import java ...
- Java日期及时间库插件 -- Joda Time.
转载自 Java日期及时间库插件 -- Joda Time. 来到新公司工作也有一个多月了, 陆陆续续做了一些简单的项目. 今天做一个新东西的时候发现了 Joda Time的这个东西, 因为以前用 ...
- java时间日期工具类_java日期处理工具类
java日期处理工具类 import java.text.DecimalFormat; import java.text.ParsePosition; import java.text.SimpleD ...
最新文章
- golang 中 map 转 struct
- 上海市经济信息化委关于支持新建互联网数据中心项目用能指标的通知
- win7 VS2013 新建工程 编译lua5.1 静态库
- 自定义报表 java_报表为什么会没完没了?怎么解决这个问题?
- input里面只有name属性 可以用id定位么_Selenium元素定位 提高篇 CSS定位元素
- Java讲课笔记04:变量与常量
- 安徽省农商行计算机类考试,2017安徽农商行备考:计算机的系统组成
- Android学习笔记---28_网络通信之通过HTTP协议实现文件上传,组拼http 的post方法,传输内容
- win7 安装 vmware出错: failed to create the requested registry key key installer error 1021 的解决办法。...
- 基于java企业人事管理系统mysql
- 波士顿学院的计算机科学,美国波士顿学院计算机科学专业本科申请
- 复制url直接能跳过验证_爬虫黑科技-绕开百度人机验证
- 【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Form表单填写、Dialog对话框弹出
- Rate Limiting Algorithms (限流算法)
- matplotlib cmap取值
- CentOS 7下载地址(ISO文件)安装
- java对接快递100追踪物流信息
- linux 删除gpt,删除GPT-默认返回MBR
- LabVIEW基础-图形和图表
- vue开发移动端,mui框架的各种使用方法
热门文章
- java对接支付宝当面付支付和查询
- MHA-结合MySQL半同步复制高可用集群(Centos7)
- 帮老婆系列-关于计算Excel表去除指定时间段后的时间差
- 2021年中国棘轮手柄市场趋势报告、技术动态创新及2027年市场预测
- 植物大战僵尸——最强阵容
- 微软服务器是永久授权的吗,微软再次针对Office永久授权版套件提价10% 将在10月1日起生效...
- ubuntu16.04编译高翔的ORBSLAM2_with_pointcloud_map,并保存点云图
- pycharm提示无法加载文件 C:\Users\admin\Desktop\pythonLX\venv\Scripts\activate.ps1,因为在此系统上禁止运行脚本
- 真子集的三种表示方法的区别(⊂与⫋ 与⊊)
- 短距离的无线连接技术--蓝牙