首页 > Java > java教程 > 【整理分享】8种开发工具,提升工作效率,再也不做加班人!

【整理分享】8种开发工具,提升工作效率,再也不做加班人!

青灯夜游
发布: 2022-09-15 19:46:51
转载
2920 人浏览过

你还在因为加班熬夜而秃头吗?你还在因为奇葩需求而造轮子吗?那你找对人了!!本文切身感受程序员之痛苦,背后默默吐血整理了一篇文章,希望对大家有所帮助。冲冲冲!!

【整理分享】8种开发工具,提升工作效率,再也不做加班人!

工欲善其事 必先利其器

身为一个程序员,每天面对的事情就是写代码和吹牛逼了。但是总是感觉自己这两个事情没有达到一个平衡点,总感觉每天写代码的时间太多了,都没有多少让自己吹的时间了。不知道大家有没有这些问题和疑惑呢?

我们已知程序员是最会偷懒的生物!哎!那么问题来了,那怎么摸鱼时间还是这么少呢?难道是我们太菜了吗?不不不,可不要小瞧自己,那会是啥原因嘞?

答案就是,当然是你还没看这篇文章呗,本文切身感受程序员之痛苦,背后默默吐血整理了一篇文章,现在分享给大家,希望对大家有所帮助。

目录

  • 整体预览图

  • JSON解析工具

  • HTTP网络请求工具

  • 字符串处理工具

  • 集合处理工具

  • 文件流处理工具

  • 加解密工具

  • JAVA bean 对象转换工具

  • 缓存和限流工具


开始上手

整体预览图

本文会从图中分类触发,介绍相关工具包,并简单介绍使用。因为本文篇幅有限,所以只当做是一个引子。具体细节还是都得大家在写代码的时候慢慢体会。

1.png

JSON 解析工具

json 解析工具在开发中有多常用相信不用我多说了吧,可以说是程序员天天用到的工具,这也是我将它放到第一个来说的原因,下面我们来一起看一下,概括和使用吧,GO! 笔者我用的比较多的是 Fastjson ,它是阿里开源的一款进行 JSON 解析的工具,用法也是相当简单。

2.png

1、maven 导入 pom 坐标

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>
登录后复制

2、下面看怎么使用

  • JSON 字符串与实体对象互相转化
// 字符串转对象
Studen student = JSON.parseObject("{"name":"小明","age":18}", Student.class);
// 对象转字符串
String str = JSON.toJSONString(student);
登录后复制
  • JSON 字符串与 JSONObject 互相转化

JSONObject只是一种数据结构,可以理解为JSON格式的数据结构(key-value 结构),可以使用put方法给json对象添加元素。JSONObject可以很方便的转换成字符串,也可以很方便的把其他对象转换成JSONObject对象

// 字符串转JSONObject对象
JSONObject jsonObject = JSONObject.parseObject("{"name":"小明","age":18}");
// JSONObject对象转字符串
String str = jsonObject.toJSONString();
登录后复制
  • JSON 字符串转化为 集合类
// 定义解析字符串
String studentListStr = "[{"name":"小明","age":18},{"name":"小牛","age":24}]";
// 解析为 List<Student>
List<Student> studentList = JSON.parseArray(studentListStr, Student.class);
// 定义解析字符串
String studentMapStr = "{"name":"小明","age":18}";
// 解析为 Map<String,String>
Map<String, String> stringStringMap = 
JSONObject.parseObject(studentMapStr, new TypeReference<Map<String, String>>(){});
登录后复制

fastjson 就介绍到这里,这里只是介绍了简单的使用,更加详细的用法请参考官方的文档,里面还有更多的用法等着你的哦~~


HTTP 网络请求工具

除了 JSON 工具,作为一个优秀的互联网打工人,不学会网络请求,怎么能够在这个行业占有一席之地呢?HTTP 网络请求工具你值得拥有~~ 根据我的个人意愿,我简单介绍 httpclient的用法,因为我对这个比较熟悉

3.png

1、maven 导入 pom 坐标

2、如何使用

  • GET 请求(无参)
/**
* 无参的 GET 请求
*/
public static void noArgsGetHttp() throws IOException {
    // 定义 httpclient
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    // 创建 httpGet
    HttpGet httpGet = new HttpGet("http://www.baidu.com");
    // 定义返回结果
    CloseableHttpResponse execute = null;
    // 发送执行
    execute = httpClient.execute(httpGet);
    // 获取返回值
    HttpEntity entity = execute.getEntity();
    System.out.println("响应状态为:" + execute.getStatusLine());
    if (Objects.nonNull(entity)) {
        System.out.println("响应内容长度为:" + entity.getContentLength());
        System.out.println("响应内容为:" + EntityUtils.toString(entity));
    }
    // 释放资源
    if (httpClient != null) {
        httpClient.close();
    }
    if (execute != null) {
        execute.close();
    }
}
登录后复制

响应状态为:HTTP/1.1 200 OK 响应内容长度为:-1 响应内容为:

  • GET 请求(有参)
 /**
* 有参的 GET 请求
*/
public static void haveArgsGetHttp() throws IOException, URISyntaxException {
    // 定义 httpclient
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    // 创建参数列表
    List<NameValuePair> valueParamsList = new ArrayList<>();
    valueParamsList.add(new BasicNameValuePair("studentId","1"));
    // 创建对应请求 Uri
    URI uri = new URIBuilder()
        .setScheme("http")
        .setHost("localhost")
        .setPath("/getStudentInfo")
        .setParameters(valueParamsList)
        .build();
    // 根据 Uri 创建 httpGet
    HttpGet httpGet = new HttpGet(uri);
    // 定义返回结果
    CloseableHttpResponse execute = null;
    // 发送执行
    execute = httpClient.execute(httpGet);
    // 获取返回值
    HttpEntity entity = execute.getEntity();
    System.out.println("响应状态为:" + execute.getStatusLine());
    if (Objects.nonNull(entity)) {
        System.out.println("响应内容长度为:" + entity.getContentLength());
        System.out.println("响应内容为:" + EntityUtils.toString(entity));
    }
    // 释放资源
    if (httpClient != null) {
        httpClient.close();
    }
    if (execute != null) {
        execute.close();
    }
}
登录后复制
  • POST 请求(有参数)
/**
* post 有参数
*/
public static void haveArgsPosthttp() throws IOException {
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    // 创建Post请求
    HttpPost httpPost = new HttpPost("http://localhost:12345/posttest");
    JSONUtil.Student student = new JSONUtil.Student();
    student.setName("潘晓婷");
    student.setAge(18);

    String jsonString = JSON.toJSONString(student);
    StringEntity entity = new StringEntity(jsonString, "UTF-8");
    // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
    httpPost.setEntity(entity);
    httpPost.setHeader("Content-Type", "application/json;charset=utf8");
    // 响应模型
    CloseableHttpResponse response = httpClient.execute(httpPost);
    // 从响应模型中获取响应实体
    HttpEntity responseEntity = response.getEntity();
    System.out.println("响应状态为:" + response.getStatusLine());
    if (responseEntity != null) {
        System.out.println("响应内容长度为:" + responseEntity.getContentLength());
        System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
    }
    // 释放资源
    if (httpClient != null) {
        httpClient.close();
    }
    if (response != null) {
        response.close();
    }
}
登录后复制

啊!这样一步一步总结下来好累啊,看到这里的小伙伴们,点一下手里的小赞。嘿嘿~~ 当然我只是简单介绍一下 httpClient 的使用,具体高深的使用方法和配置可以参考其他博主的详细介绍,让我们介绍下一个常用的工具吧。


字符串处理工具

字符串使我们开发中最常见的类型,也是我们最常需要操作的类型了。如果不知道字符串的常用工具,那在写代码的时候简直就是场灾难!!!

字符串判空,截取字符串、转换大小写、分隔字符串、比较字符串、去掉多余空格、拼接字符串、使用正则表达式等等。

StringUtils 给我们提供了非常丰富的选择。我们着重以本类来介绍使用。

1、导入maven坐标

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
登录后复制

2、常用方法介绍

  • 字符串判空 ⭐⭐⭐

(isNotBlankisBlanK)

String str = " ";
// 是否不为空
boolean res1 = StringUtils.isNotBlank(str);
// 是否为空
boolean res2 = StringUtils.isBlank(str);
登录后复制

(isNotEmptyisEmpty)

String str = " ";
// 是否不为空
boolean res1 = StringUtils.isNotEmpty(str);
// 是否为空
boolean res2 = StringUtils.isEmpty(str);
登录后复制

优先推荐使用 isBlankisNotBlank 方法,因为 它会把空字符串也考虑进去

  • 分隔字符串 —— split —— ⭐⭐⭐
String str2 = "1,2,3";
String[] split = StringUtils.split(str2);
System.out.println(Arrays.toString(split));
登录后复制

相较于 Stringsplit 方法来说,StringUtilssplit 方法不会有空指针异常

  • 判断是否纯数字 —— isNumeric —— ⭐⭐

给定一个字符串,判断它是否为纯数字 可以使用isNumeric方法

String str3 = "1";
boolean numeric = StringUtils.isNumeric(str3);
System.out.println(numeric);
登录后复制

当然,这个工具类除了上面简单的三个方法之外们还有其他很多对于我们来说很使用的方法,但是这里就不一一举例了, 有兴趣的小伙伴们可以看源码统计一下


集合相关处理工具

哦吼~~看完了字符串的常用工具,重中之重的集合它来了,如果说没有字符串,我们的程序就无法运行,那么没有集合,我们将会是每天在加班的中度过了,出去后将会自豪的说,老板的车轮胎我也是做了贡献的。

既然集合工具这么重要,那么当然要重点介绍。学会相关工具的使用,真的是能让我们事半功倍的,真的是能让摸鱼时间大大增加的,不信你看看。

4.png

Collections

它是 JAVA 的集合工具包,内部包括了很多我们常用的方法,下图展示了其中一些,方便食用!

5.png

  • 排序—— sort—— ⭐⭐⭐

在我们日常开发工作中,经常会遇到一些集合排序的需求。sort 就可以很好的帮助我们做好这一点

List<Integer> list = new ArrayList<>();
list.add(2);
list.add(1);
list.add(3);
//升序
Collections.sort(list);
System.out.println(list);
//降序
Collections.reverse(list);
System.out.println(list);
登录后复制

结果: [1, 2, 3] [3, 2, 1]

  • 获取最大最小值 —— max / min ——⭐⭐⭐

最大值最小值是我们操作集合最常见的方法了吧!!Collections 就有现成的方法帮助我们实现

// 最大值最小值
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);

Integer max = Collections.max(intList);
Integer min = Collections.min(intList);

System.out.println("集合元素最大值:" + max);
System.out.println("集合元素最小值:" + min);
登录后复制

结果:集合元素最大值:3 集合元素最小值:1

  • 转换线程安全集合—— synchronizedList ——⭐⭐⭐

在多线程状态下,普通集合会产生并发问题,比如 ArrayList 并发添加会产生空值情况,这时我们又不想改动我们之前的集合怎么办? 我们简单的通过Collections 的线程安全转化就可以做到了,简简单单一行代码就可以做到!是不是方便的很!

List<Integer> synchronizedList = Collections.synchronizedList(intList);
登录后复制

当然,Collections 还有很多有用和有趣的方法等着我们去探索,只是只是作为了一个抛转引玉的效果,就不过多的赘述了。

CollectionUtils

我最常用的便是 CollecionUtils,它是 apache 开源的工具包。它的功能可以说是相当强大的,不信你可以往下看看,反正你能想到的集合操作基本上它都有。

6.png

  • 集合判空—— isNotEmpty——⭐⭐⭐

我们最常用的集合方法,没有之一,必须掌握它!!

List<String> stringList = new ArrayList<>();
boolean notEmpty = CollectionUtils.isNotEmpty(stringList);
System.out.println("集合不是空的吗?"+ notEmpty);
登录后复制

集合不是空的吗?false

  • 交集/并集/补集/差集——⭐⭐⭐

在开发中,经常需要将多集合进行交并补等的数学操作,不会还是傻傻的子集写循环处理吧!那样还能有摸鱼的时间吗?下面就是大大提升效率的工具!!!

List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
List<Integer> list2 = new ArrayList<>();
list2.add(3);
list2.add(4);
// 获取并集
Collection<Integer> union = CollectionUtils.union(list1, list2);
System.out.println(union);

// 获取交集
Collection<Integer> intersection = CollectionUtils.intersection(list1, list2);
System.out.println(intersection);

//获取交集的补集
Collection<Integer> disjunctionList = CollectionUtils.disjunction(list1, list2);
System.out.println(disjunctionList);

// 获取差集
Collection<Integer> subtract = CollectionUtils.subtract(list1, list2);
System.out.println(subtract);
登录后复制

两集合并集:   [1, 2, 3, 4]

两集合交集: [3]

两集合交集的补集:[1, 2, 4]

两集合差集: [1, 2]

  • 判断两集合是否相等——isEqualCollection——⭐⭐⭐
// 判断两集合是否相等
List<Integer> list3 = new ArrayList<>();
list1.add(3);
list1.add(4);
List<Integer> list4 = new ArrayList<>();
list2.add(3);
list2.add(4);
boolean equalCollection = CollectionUtils.isEqualCollection(list3, list4);
System.out.println("两集合相等吗?:" + equalCollection);
登录后复制

两集合相等吗?:true

Lists

最后在集合的工具类中再补充一个 google 官方的java包,里面有很多我们想不到的超级方便的小工具,既然是说集合,我们说一下它里面的 Lists 类,也是超级好用的!

7.png

  • 快速初始化集合——newArrayList——⭐⭐⭐

相信大家在开发中,都有过初始化集合的需要吧!那么我们一般都是 新建一个 ArrayList 然后一个一个 add 进去,现在告诉大家,不用这么麻烦,一个方法新建带初始化全搞定,真香警告!!

// 快速初始化集合
ArrayList<Integer> integers1 = Lists.newArrayList(1, 2, 3);
System.out.println(integers1);
登录后复制

[1, 2, 3]

  • 集合分页——partition——⭐⭐⭐

有时候我们想将我们的大集合分散成为一些小集合,我们又不想手动操作怎么办?这些痛点肯定已经有人帮助我们想好了!!来来来,介绍一下!!

// 数组分页
ArrayList<Integer> list7 = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<List<Integer>> partition = Lists.partition(list7, 5);
System.out.println(partition);
登录后复制

[ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10] ]

介绍完了 集合相关的操作类,下面我们也要介绍一下文件流相关操作,这个操作在开发中也是经常用到的。


文件流处理工具

相信我们在工作已经厌烦了,经常写** IO 流相关的类了吧。经常简单的读取和写入一个文件,我们需要大费周章的去定义一些 InputStream OutputStream **等,感觉有一种杀鸡用牛刀的错觉。 介绍一个文件操作工具类,省去大量操作和关闭行为,超级好用,平常我可是经常用的。

8.png

  • 将信息写入文件——writeByteArrayToFile——⭐⭐⭐

最最常用的操作,怎么样很简单吧,就只用一行代码,秒杀!

// 将 test 写入 test.txt 文件
FileUtils.writeByteArrayToFile(new File("C:\Users\test.txt"), "test".getBytes());
登录后复制
  • 从文件读取信息——readFileToByteArray——⭐⭐⭐

知道了怎么往文件里写东西,他的好兄弟读取我们也得知道啊!

// 读取 test.txt 文件
byte[] bytes = FileUtils.readFileToByteArray(new File("D:\Users\test.txt"));
System.out.println("读取到的文本为:" + new String(bytes));
登录后复制

读取到的文本为:test

API 很多,我也不能一一为大家介绍,深入的了解还需要大家去,熟练地运用起来,并且是不是的去看看官方的文档,查漏补缺,相信你也可以见识到很多让你叹为观止的方法。


加解密工具类

平常我们经常会遇到比如对用户的密码进行加密 (MD5) ,校验接口的签名 (sha256) 加密等等 用到加密的场景虽然不是很多,但是有这样的工具何乐而不为呢?

因为常用的加密方法并不多,这里介绍两个方法给大家,想知道其他更多用法的伙伴们,去自己探索吧

9.png

// MD5 加密
String MD5 = DigestUtils.md2Hex("123");
System.out.println("加密后的结果:" + MD5);

//  sha(安全哈希算法) 加密
String sha256Hex = DigestUtils.sha256Hex("123");
System.out.println("sha256加密后:" + sha256Hex);
登录后复制

MD5加密后的结果:ef1fedf5d32ead6b7aaf687de4ed1b71

sha256加密后:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e3


JAVA bean 对象转换工具

说到这里,我们来说一下对象转化的工具,在开发中有些规范,比如DTO、DO、VO等等,之间,如果我们需要转换,单纯的我们要一个一个的 set 值,真是一项苦逼的活。

10.png

BeanUtils

java bean对象的相关转化,我在这里介绍两个 ,一个 是大家都非常熟悉的 BeanUtils,还有一个就是我平常在开发中经常使用的 MapStructBeanUtils 最常用的莫过于对象的的拷贝了 。 不过面对需要深拷贝的对象大家要注意了,这里并不推荐大家使用此工具去实现

Student student = new Student();
student.setName("小明");
student.setAge(18);
Student newStudent = new Student();
BeanUtils.copyProperties(student,newStudent);
System.out.println(newStudent);
登录后复制

Student(name=小明, age=18)


MapStruct

下面我们重点说一下 MapStruct 这个转化,BeanUtils 就是一个大老粗,只能同属性映射,或者在属性相同的情况下,允许被映射的对象属性少。

但当遇到被映射的属性数据类型被修改或者被映射的字段名被修改,则会导致映射失败。而 mapstruct 就是一个巧媳妇儿了。

她心思细腻,把我们可能会遇到的情况都给考虑到了(要是我也能找一个这样的媳妇儿该多好,内心笑出了猪声)

1、首先啥都不用想上来我们先把该导的包导进去

<dependency>
	<groupId>org.mapstruct</groupId>
	<!-- jdk8以下就使用mapstruct -->
	<artifactId>mapstruct-jdk8</artifactId>
	<version>1.2.0.Final</version>
</dependency>
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct-processor</artifactId>
	<version>1.2.0.Final</version>
</dependency>
登录后复制

2、用一下

  • 对象之间字段完全相同

第一步:定义好我们的基础类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student{
	private String name;
	private Integer age ;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher{
	private String name;
	private Integer age ;
}
登录后复制

第二步:接下来定义一个接口,但是注意不需要实现,他就呢能够帮我们转化很神奇的

@Mapper
public interface UserCovertToTeacher {
    UserCovertToTeacher INSTANCE = Mappers.getMapper(UserCovertToTeacher.class);
    Teacher toCovert(Student student);
}
登录后复制

最后一步:在代码中调用,聪明的小伙伴看下面代码,一下就明白了,就是这么简单

Student student = new Student();
student.setName("小明");
student.setAge(18);
Teacher teacher = UserCovertToTeacher.INSTANCE.toCovert(student);
System.out.println(teacher);
登录后复制

Teacher(name=小明, age=18)

  • 对象之间字段存在不相同情况

我们介绍了完全两个类字段相同的情况,那么,更加令我们头疼的 有多个字段名字不同但是有对应关系应该怎么搞呢?

我们进阶介绍一下,如何处理这种参数名不同的对应关系 目前假设我们新定义一个微信类,我们的学生要注册到微信上,我们就要将学生对象转化为微信对象

@Data
@AllArgsConstructor
@NoArgsConstructor
static class WeiXin{
	private String username;
	private Integer age ;
}
登录后复制

但是我们发现和我们上面的学生类,的名字参数名不同,怎么对应过去的? 答案就是在对方法上配置

@Mapper 
public interface UserCovertToWeinxin {
    UserCovertToWeinxin INSTANCE = Mappers.getMapper(UserCovertToWeinxin.class);
	// 配置字段映射规则
    @Mapping(source = "name",target = "username")
    BeanUtilTest.WeiXin toCovert(BeanUtilTest.Student student);
}
登录后复制
Student student = new Student();
student.setName("小明");
student.setAge(18);
WeiXin weiXin = UserCovertToWeinxin.INSTANCE.toCovert(student);
System.out.println(weiXin);
登录后复制

WeiXin(username=小明, age=18)

这么简单的两个小例子可包含不了 MapStruct这么强大的功能,不管是日期格式化、还是表达式解析、还是深拷贝,都能一一搞定,只是限于本篇文章,无法跟大家说了。想想都很伤心呢! 但是还是那句话,抛砖引玉么!剩下的就交给聪明的小伙伴们了!


缓存和限流器工具

最后一小节,我给大家带来我的珍藏,压箱底的东西要拿出来了,大家还不快快点赞收藏,记好,错过了,可就没有下家店了。

我也是在 guava 中发现了很多好用的工具的 首先介绍缓存工具,开发中,我们常用的内存缓存,也就常常是定义一个 Map 去存放,但是单纯的 Map 只能存和取,确无法做到,缓存过期、缓存淘汰,和相关通知等等复杂操作。

我们有必要学习掌握一种工具,能够 Cover 上面所有情况的缓存工具,有需求就有工具类,永远记住。!!!,Cache 登场了,还是老规矩,先看看它怎么用吧。

Cache

定义一个简单定时过期的缓存

Cache<String, String> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .build();
// 放入缓存
cache.put("小明","活着");
Thread.sleep(10000);
// 从缓存拿取值
System.out.println(cache.getIfPresent("小明"));
登录后复制

null

看到没,结果显而易见,超过了缓存时间,就会自己释放。嘿嘿。

定义一个缓存符合以下限制:

  • 限制访问并发
  • 设置初始化容量
  • 限制缓存数量上限
Cache<String, String> cache = CacheBuilder.newBuilder()、
		    // 最大容量,超过按照 LRU 淘汰
                .maximumSize(100)
		    // 初始容量
                .initialCapacity(10)
		    // 并发等级 10
                .concurrencyLevel(10)
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .build();
登录后复制

两个小例子,大家看明白了没有,真正的干货,还不赶紧用起来。

除了这个,一个限流器也是常常需要的,我们总不能自记去写一个限流器吧,需要考虑的太多,性能还不行哎!那就用接下来介绍的这个工具

RateLimiter

限流器大家在并发场景下经常会遇到,最简单的实现限流就是令牌桶算法,原理很简单,但是具体实现是很复杂的,RateLimiter 帮助我们解决这一点,只需要调用简单的 API 就能实现并发限流

定义限流器,每1秒钟通过 1 个请求

RateLimiter rateLimiter = RateLimiter.create(1,1,TimeUnit.SECONDS);
登录后复制

并发两个去获取,看一下结果吧,是不是符合我们的预期

new Thread(()->{
	System.out.println("线程 1 获取到执行权限了吗?" + rateLimiter.tryAcquire());
}).start();

new Thread(()->{
	System.out.println("线程 2 获取到执行权限了吗?" + rateLimiter.tryAcquire());
}).start();
登录后复制

线程 1 获取到执行权限了吗?true

线程 2 获取到执行权限了吗?false

怎么样,是不是只能有一个通过,简单例子说明问题。 具体用法还得大家在实际开发中具体体会,笔者在这里就不多BB了!!留着时间大家赶快去练习吧。争取成为一个 API 调用高手。

修炼完成

经过上面这么多的讲解、案例和对知识的思考,相信大家已经初步掌握了线程池的使用方法和细节,以及对原理运行流程的掌握, 如果你觉得本文对你有一定的启发,引起了你的思考。 点赞、转发、收藏,下次你就能很快的找到我喽!

(学习视频分享:编程基础视频

以上是【整理分享】8种开发工具,提升工作效率,再也不做加班人!的详细内容。更多信息请关注PHP中文网其他相关文章!

相关标签:
来源:juejin.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板