Flutter 网络请求之Dio库

Flutter 网络请求之Dio库

  • 前言
  • 正文
    • 一、配置项目
    • 二、网络请求
    • 三、封装
      • ① 单例模式
      • ② 网络拦截器
      • ③ 返回值封装
      • ④ 封装请求
    • 四、结合GetX使用
    • 五、源码

前言

  最近再写Flutter系列文章,在了解过状态管理之后,我们再来学习一下网络请求。

正文

  网络请求对于一个线上的App来说是必不可少的,那么Flutter中的网络请求同样也是官方的没有第三方的那么好用,这里我们使用Dio,目前来说比较好用简洁的网络库。

一、配置项目

  首先我们创建一个名为study_http的项目。

在这里插入图片描述

创建项目之后,我们配置一下依赖库,在项目的pubspec.yaml文件中,添加如下所示代码:

dependencies:get:dio: ^5.4.0

添加位置如下图所示:

在这里插入图片描述

然后点击Pub get,获取并安装所添加的库,安装成功之后,项目配置完成。

二、网络请求

  下面我们来设计一个场景,页面上有一个图片和一个按钮,默认显示一个图片,点击按钮之后请求网络接口,返回一个图片,将这个请求返回的网络图片显示出来,首先我们在lib下新建一个https的目录,然后这个目录下新建一个https_page.dart文件 ,里面代码如下所示:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';class HttpsPage extends StatelessWidget {var imgPath ="https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1h31Ip.img?w=768&h=1226&m=6&x=326&y=887&s=506&d=118".obs;final dio = Dio();void request() async {var response = await dio.get('https://www.dmoe.cc/random.php?return=json');//转化为JsonString jsonString = jsonEncode(response.data);print(jsonString);// 解析JSON字符串Map<String, dynamic> json = jsonDecode(jsonString);// 获取特定字段值imgPath.value = json['imgurl'];}Widget build(BuildContext context) {return Scaffold(body: Center(child: Container(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Obx(() => Image.network(imgPath.value,width: 200,)),SizedBox(height: 10),ElevatedButton(onPressed: () {request();},child: Text("请求网络"),)],),),));}
}

  说明一下这个代码,这里使用了Get库,不了解的可以看看我上一篇文章:Flutter 状态管理之GetX库,创建了一个可观察的变量,然后写了一个请求网络的方法,使用了Dio库的Get请求,请求一个API地址,你可以将这个地址在浏览器中测试,确保它可以返回值。这是我请求的结果,如下图所示:
在这里插入图片描述
  通过网络请求会返回一个response 对象,我们将对象转换为Json字符串,然后再获取字符串中的imgurl的值,也就是这个图片的网络地址链接,最后再更新这个imgPath的值,Obx()包裹的内容就会刷新,下面我们运行一下看看效果:

在这里插入图片描述

这是默认的图片,然后点击一下请求网络的按钮,经过短暂的网络延迟之后就会加载出网络请求返回后的图片,如下图所示:

在这里插入图片描述

这个请求返回的图片类似于每日一图,所以变化很大,因此你只要有加载出来就可以,不需要跟我一样。

三、封装

  在对Dio库进行进行使用的时候,我们通常会进行封装而不是直接使用。Flutter原生的网络请求是使用HttpClient,使用起来相当繁琐,因此Dio对于HttpClient进行了封装,那么我们为什么还需要对Dio进行封装呢?这就是考虑到实际中的业务处理了,封装都是针对于实际情况来的,下面我们看看怎么封装这个Dio库。

① 单例模式

  在使用网络请求时,通常会有多个网络请求,我们可以写一个单例,将一些基本的内容写在单例里面,写几个方法供其他地方调用,下面我们首先来写一个单例在lib下新建一个net包,包下新建一个network_manager.dart文件,代码如下所示:

import 'package:dio/dio.dart';/// 网络管理
class NetworkManager {static NetworkManager? _instance = NetworkManager._internal();late Dio dio;static NetworkManager getInstance() {_instance ??= NetworkManager._internal();return _instance!;}NetworkManager._internal() {// 配置BaseOptionsBaseOptions options = BaseOptions(baseUrl: "",//连接超时connectTimeout: const Duration(seconds: 15),//接收超时receiveTimeout: const Duration(seconds: 10),//内容类型contentType: 'application/json;Charset=UTF-8',//响应数据类型:JsonresponseType: ResponseType.json);dio = Dio(options);}get(String url, {option, params}) async {Response response;try {response =await dio.get(url, options: Options(responseType: ResponseType.json));print("response.data:${response.data}");print("response.data:${response.statusCode}");print("response.data:${response.statusMessage}");print("response.data:${response.headers}");} on Exception catch (e) {print("Get方法出错:${e.toString()}");}}}

  下面说明一下上面代码,首先我们写了一个getInstance()方法,在这里面判断_instance 是否为空,为空则NetworkManager._internal(),对dio进行一些基本的配置,然后初始化dio 对象,不为空则,直接返回_instance 。然后写了一个get()方法,方法里面就是一个get请求,我们在之前已经页面中已经写好了,同时我们打印一下返回的数据,下面我们在前面的页面中改造一下。修改https_page.dart中的request()方法,代码如下所示:

  void request() async {NetworkManager.getInstance().get('https://www.dmoe.cc/random.php?return=json');}

这里就是直接使用单例中的方法,我们就不需要再当前页面创建dio对象了,运行一下,看控制台日志,如下图所示:

在这里插入图片描述
现在我们的方法在单例中有效果,我们继续往下走。

② 网络拦截器

  现在的这个日志确实不怎么好看,为了解决这个问题,也为了我们看日志的时候一目了然,我们可以自定义一个拦截器,在net包下新建一个interceptor包,该包下新建一个custom_interceptor.dart文件,里面的代码如下所示:

import 'dart:convert';import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';///日志拦截器
class CustomInterceptor extends Interceptor {void onRequest(RequestOptions options, RequestInterceptorHandler handler) {StringBuffer buffer = StringBuffer();buffer.write('⌈‾‾ Request ヾ(•ω•`)o \n');buffer.write('| \n');buffer.write('| - Url:   ${options.baseUrl + options.path}\n');buffer.write('| - Method:${options.method}\n');buffer.write('| - Header:${options.headers.toString()}\n');final data = options.data;if (data != null) {if (data is Map) {buffer.write('| - Body:  ${options.data.toString()}\n');} else if (data is FormData) {final formDataMap = {}..addEntries(data.fields)..addEntries(data.files);buffer.write("| - Body:  ${formDataMap.toString()}\n");} else {buffer.write("| - Body:  ${data.toString()}\n");}}buffer.write('⌊_____________________________________________________________________');printDebugLog(buffer);return handler.next(options);}void onResponse(Response response, ResponseInterceptorHandler handler) {StringBuffer buffer = StringBuffer();buffer.write('⌈‾‾ Response O(∩_∩)O \n');buffer.write('| \n');buffer.write('| - Code:   ${response.statusCode}\n');buffer.write('| - CodeMsg:${response.statusMessage}\n');buffer.write('| - Header:\n');response.headers.forEach((key, value) {buffer.write('|    $key  $value\n');});final data = response.data;if (data != null) {if (data is Map) {buffer.write('| - Data:  ${response.data.toString()}\n');String dataJson = jsonEncode(response.data);buffer.write('| - Json:  $dataJson\n');} else if (data is FormData) {final formDataMap = {}..addEntries(data.fields)..addEntries(data.files);buffer.write("| - Data:  ${formDataMap.toString()}\n");} else {buffer.write("| - Data:  ${data.toString()}\n");}}buffer.write('⌊_____________________________________________________________________');printDebugLog(buffer);return handler.next(response);}void onError(DioException err, ErrorInterceptorHandler handler) {//处理错误信息handlerError(err);StringBuffer buffer = StringBuffer();buffer.write('⌈‾‾ Error (っ °Д °;)っ\n');buffer.write('| \n');buffer.write('| - ExceptionType:${err.type.name}\n');buffer.write('| - ErrorMsg:     ${err.message}\n');buffer.write('| - StatusCode:   ${err.response?.statusCode}\n');buffer.write('| - StatusMsg:    ${err.response?.statusMessage}\n');buffer.write('⌊_____________________________________________________________________');printDebugLog(buffer);return handler.next(err);}///处理错误信息 --自行去实现里面的功能代码void handlerError(DioException err) {switch (err.type) {//连接超时case DioExceptionType.connectionTimeout:break;//响应超时case DioExceptionType.receiveTimeout:break;//发送超时case DioExceptionType.sendTimeout:break;//请求取消case DioExceptionType.cancel:break;//错误响应 404 等case DioExceptionType.badResponse:break;//错误证书case DioExceptionType.badCertificate:break;//未知错误default:break;}}void printDebugLog(StringBuffer buffer) {if (kDebugMode) {print(buffer.toString());}}
}

  在这里面我们继承了创建CustomInterceptor 类,继承DioInterceptor ,重写里面onRequest(请求前)onResponse(响应前)onError(错误时)的拦截方法,在里面对于相关数据信息进行打印,同时只在debug模式下打印,下面我们回到NetworkManager中,使用这个自定义拦截器。

import 'interceptor/custom_interceptor.dart';

首先导包,然后在_internal()方法中增加如下代码:

    //添加日志拦截器dio.interceptors.add(CustomInterceptor());

添加位置如下图所示:

在这里插入图片描述

再将get方法中的打印注释掉

在这里插入图片描述

然后我们重新运行一下,请求网络接口,查看控制台日志,如下图所示:

在这里插入图片描述

这样看起来是否会清晰一些呢,可以自行调整,我们接着往下走。

③ 返回值封装

  对返回值的封装,我们可以分为两步,第一步就是在响应前封装,第二步在响应后转换。先来看第一步,在net包下新建一个response包,该包下新建一个base_response.dart,代码如下所示:

///自定义响应封装
class BaseResponse<T> {//状态码final int? code;//状态描述final String? msg;//数据final T data;BaseResponse({required this.code,required this.msg,required this.data});String toString() {StringBuffer buffer = StringBuffer();buffer.write('{');buffer.write('"code":$code');buffer.write('"msg":"$msg"');buffer.write('"data":"$data"');buffer.write('}');return super.toString();}
}

  这里就是对默认的Response进行一次封装,然后这里的data就是我们接口所拿到的返回值, 下面我们改动一下之前的自定义拦截器custom_interceptor.dart中的代码,主要就是修改onResponse()方法,代码如下:

  void onResponse(Response response, ResponseInterceptorHandler handler) {//返回自定义的Basefinal baseResponse = BaseResponse(code: response.statusCode, msg: response.statusMessage, data: response.data);response.data = baseResponse;StringBuffer buffer = StringBuffer();buffer.write('⌈‾‾ Response O(∩_∩)O \n');buffer.write('| \n');buffer.write('| - Code:   ${response.statusCode}\n');buffer.write('| - CodeMsg:${response.statusMessage}\n');buffer.write('| - Header:\n');response.headers.forEach((key, value) {buffer.write('|    $key  $value\n');});final data = response.data;if (data != null) {if (data is Map) {buffer.write('| - Data:  ${response.data.toString()}\n');String dataJson = jsonEncode(response.data);buffer.write('| - Json:  $dataJson\n');} else if (data is FormData) {final formDataMap = {}..addEntries(data.fields)..addEntries(data.files);buffer.write("| - Data:  ${formDataMap.toString()}\n");} else {buffer.write("| - Data:  ${baseResponse.data.toString()}\n");}}buffer.write('⌊_____________________________________________________________________');printDebugLog(buffer);return handler.next(response);}

核心代码就是这一段

在这里插入图片描述

  将response.data封装到BaseResponse中,然后再赋值返回。然后我们再对返回值进行一个JSON转Bean的操作,AS中提供了一个插件,FlutterJsonBeanFactory,安装。

在这里插入图片描述

  安装好之后,我们可以重启一下AS,然后就来根据JSON返回值构建Dart的Bean。在lib包下新建一个model包,然后鼠标右键model包,点击New → JsonToDartBeanAction,如下图所示:

在这里插入图片描述

输入文件名称,然后将接口返回的JOSN:

{"code": "200","imgurl": "https://image.baidu.com/search/down?url=https://tvax3.sinaimg.cn//large/a15b4afegy1fmvk16yis3j21hc0u0tpx.jpg","width": "1920","height": "1080"
}

赋值进去,如下图所示:

在这里插入图片描述

点击Make,完成构建。

在这里插入图片描述

  构建之后会在model包下生成一个img_entity.dart,我刚才输入的是img,_entity是这个插件自己添加的,然后会生成一个generated文件夹,里面可以看到一个img_entity.g.dart文件,里面的内容就是对你JSON和Bean之间的转化代码的生成,我们不需要关心。先不急着使用这个返回值,我们继续往下走。

④ 封装请求

  接着我们封装请求方法,针对网络请求有get、post、put等等方式,在dio库中,最终实际上调用的都是request请求,在net包下新建一个method包,该包下新建一个bese_method.dart,代码如下:

enum BaseMethod {get,post,put,delete,patch,head
}

  这里代码很简单,就是列举了dio的网络请求方式,然后我们回到network_manager.dart中,在里面新增一个request()方法,其他的代码也做了修改,整体代码如下所示:

  class NetworkManager {factory NetworkManager() => _getInstance();static NetworkManager? _instance = NetworkManager._initialize();late Dio _dio;static NetworkManager _getInstance() {_instance ??= NetworkManager._initialize();return _instance!;}NetworkManager._initialize() {// 配置BaseOptionsBaseOptions options = BaseOptions(baseUrl: "",//连接超时connectTimeout: const Duration(seconds: 15),//接收超时receiveTimeout: const Duration(seconds: 10),//内容类型contentType: 'application/json;Charset=UTF-8',//响应数据类型:JsonresponseType: ResponseType.json);_dio = Dio(options);//添加日志拦截器_dio.interceptors.add(CustomInterceptor());}///网络请求Future<T> request<T>(String path,{BaseMethod method = BaseMethod.get, Map<String, dynamic>? params,data, Options? options}) async {const methodValues = {BaseMethod.get: 'get',BaseMethod.post: 'post',BaseMethod.put: 'put',BaseMethod.delete: 'delete',BaseMethod.patch: 'patch',BaseMethod.head: 'head',};options ??= Options(method: methodValues[method]);try {Response response;response = await _dio.request(path,data: data, queryParameters: params, options: options);return response.data;} on DioException catch (e) {throw e;}}
}

下面我们再回到https_page.darat中去使用,修改request()方法,代码如下所示:

  void request() async {BaseResponse response = await NetworkManager().request('https://www.dmoe.cc/random.php?return=json');ImgEntity imgEntity = ImgEntity.fromJson(response.data);imgPath.value = imgEntity.imgurl;}

如果有报错注意一下导包

import '../model/img_entity.dart';

运行一下,效果和之前是一样的,然后我们再来改动一下,针对于这个API地址:

https://www.dmoe.cc/random.php?return=json

我们可以分为两部分。

基础地址

https://www.dmoe.cc/

功能地址

random.php?return=json

  一般的项目中,基础地址不会经常变,也就是ip地址,而不同的功能会根据实际情况去改变接口,因此这一部分我们需要和实际方法进行绑定,下面我们在NetworkManager中增加一行代码:

final _mBaseUrl = "https://www.dmoe.cc/";

然后修改baseUrl的值,之前是空字符串,如下图所示:

在这里插入图片描述

再去修改实际调用的地方,如下图所示:

在这里插入图片描述

  这样就对一个API地址进行了分离,这在实际开发中是很常见的做法。对于dio的封装就到这里了,肯定不是完善了,因为还有很多东西没有考虑到,我们可以根据实际中的需要再去添加,我这里就不赘述了,下面我们结合GetX去使用。

四、结合GetX使用

在https包下新建一个https_controller.dart,代码如下:

import 'package:get/get.dart';import '../model/img_entity.dart';
import '../net/network_manager.dart';
import '../net/response/base_response.dart';class HttpsController extends GetxController {var imgPath ="https://img-s-msn-com.akamaized.net/tenant/amp/entityid/BB1h31Ip.img?w=768&h=1226&m=6&x=326&y=887&s=506&d=118".obs;void request() async {BaseResponse response = await NetworkManager().request('random.php?return=json');ImgEntity imgEntity = ImgEntity.fromJson(response.data);imgPath.value = imgEntity.imgurl;}}

这里就是将网络请求相关的变量和方法都放到HttpsController 中,然后我们再回到HttpsPage,修改代码如下所示:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'https_controller.dart';class HttpsPage extends StatelessWidget {final httpsController = Get.put(HttpsController());Widget build(BuildContext context) {return Scaffold(body: Center(child: Container(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Obx(() => Image.network(httpsController.imgPath.value,width: 200,)),SizedBox(height: 10),ElevatedButton(onPressed: () => httpsController.request(),child: Text("请求网络"),)],),),));}
}

主要改动地方如下图所示:

在这里插入图片描述

这样基本上就符合现在的开发理念了,数据和UI进行分离,再次运行,效果依然一样,好了,本篇文章就到这里。

五、源码

源码地址:study_http

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

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

相关文章

starknet之 class_hash

文章目录 问题背景什么是Class Hash问题背景 部署合约报错:ReferenceError: Buffer is not defined 什么是Class Hash 官方: https://book.starknet.io/ch04-03-01-deploy-standard-account.html?highlight=class%20hash#finding-the-class-hash 要部署智能合约,您需要在…

微软在其Windows系统中暗示了AI的未来,推出了更聪明的Copilot功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

批量梯度下降、随机梯度下降、小批量梯度下降

一、批量梯度下降&#xff08;Batch Gradient Descent,BGD&#xff09; 在批量梯度下降中&#xff0c;每次迭代都使用整个训练集的数据进行梯度计算和参数更新。也就是说&#xff0c;每次迭代都对所有的样本求取梯度&#xff0c;然后更新参数。由于要处理整个训练集&#xff0c…

刷题计划_冲绿名

现在 rating 是 1104 准备刷 100道 1200的题&#xff0c;把实力提升到 1200 &#xff0c;上一个绿名 每一个分数段的题都写一百道&#xff0c;争取早日上蓝 现在 虽然 cf 里面显示写了一些这个分数段的题&#xff0c;但是自己训练的时候&#xff0c;其实是没有训练一道这个分…

【Py/Java/C++三种语言详解】LeetCode每日一题240214【二叉树BFS】LeetCode102、二叉树的层序遍历

有LeetCode交流群/华为OD考试扣扣交流群可加&#xff1a;948025485 可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1336了解算法冲刺训练 文章目录 题目链接题目描述解题思路DFS和BFS异同用队列维护的BFS 代码PythonJavaC时空复杂度 相关习题华为OD算法/大…

Linux 基础/子目录分配/文件路径

在Linux系统中&#xff0c;整个系统只具有一个根目录“/”&#xff0c;用斜杠表示。根目录是整个文件系统的顶层目录&#xff0c;在他下面可以创建其他的目录和文件。 Linux中的子目录分配&#xff1a; /bin - 基本命令的二进制文件&#xff0c;这些命令可供所有用户使用&am…

MySQL主从环境,主库改端口后,从库如何操作?

主库&#xff1a;mysql-111 从库&#xff1a;mysql-112 主库由3306端口修改成3307后&#xff0c; 从库执行如下命令 mysql> stop slave; mysql> change master to master_port3307; mysql> CHANGE MASTER TO MASTER_HOST192.168.10.111,MASTER_USERbeifen,MASTER_PA…

生活中有很多压力,怎么办?

在这篇文章的最开始&#xff0c;我想跟你一起做一个思维实验&#xff1a; 假如现在有一个按钮&#xff0c;按下去之后&#xff0c;你会过上一段新的生活。这段生活的走向跟你原本生活的走向大体一样&#xff0c;不同之处在于&#xff1a;它会消除你未来生活中的一切压力。你将不…

BUGKU-WEB 矛盾

题目描述 进入场景看看&#xff1a; 代码如下&#xff1a; $num$_GET[num]; if(!is_numeric($num)) { echo $num; if($num1) echo flag{**********}; }解题思路 需要读懂一下这段PHP代码的意思明显是一道get相关的题目&#xff0c;需要提供一个num的参数,然后需要传入一个不…

【PyQt】12-滑块、计数控件

文章目录 前言一、滑块控件 QSlider运行结果 二、计数器控件 QSpinBox运行结果 总结 前言 1、滑块控件 2、计数控件 一、滑块控件 QSlider #Author &#xff1a;susocool #Creattime:2024/2/15 #FileName:28-滑块控件 #Description: 通过滑块选择字体大小 import sys from PyQ…

数字的魅力之情有独钟的素数

情有独钟的素数 什么是素数 素数&#xff08;Prime number&#xff09;也称为质数&#xff0c;是指在非0自然数中&#xff0c;除了1与其本身之外不拥有其他因数的自然数。也就是说&#xff0c;素数需要满足两个条件&#xff1a; 大于1的整数&#xff1b;只拥有1和其自身两个…

精读Relational Embedding for Few-Shot Classification (ICCV 2021)

Relational Embedding for Few-Shot Classification (ICCV 2021) 一、摘要 该研究提出了一种针对少样本分类问题的新方法&#xff0c;通过元学习策略来学习“观察什么”和“在哪里关注”。这种方法依赖于两个关键模块&#xff1a;自相关表示&#xff08;SCR&#xff09;和交叉…

统一数据返回格式 及 可能遇到的问题;统一异常处理

统一数据返回格式 统一数据返回格式就像我们寄快递一样&#xff0c;不管你需要寄的东西具体是什么都需要将它打包到统一的快递箱中。 此时我们需要一个“快递箱”用来将返回的数据“装”在里面。这个类是根据业务情况来自行定义的。 Data public class Resp<T> {//200…

[嵌入式AI从0开始到入土]14_orangepi_aipro小修补含yolov7多线程案例

[嵌入式AI从0开始到入土]嵌入式AI系列教程 注&#xff1a;等我摸完鱼再把链接补上 可以关注我的B站号工具人呵呵的个人空间&#xff0c;后期会考虑出视频教程&#xff0c;务必催更&#xff0c;以防我变身鸽王。 第1期 昇腾Altas 200 DK上手 第2期 下载昇腾案例并运行 第3期 官…

PHP毕业设计图片分享网站76t17

图片分享网站主要是为了提高工作人员的工作效率和更方便快捷的满足用户&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各个模块是通过许多今天的发达系统做出合理的分析来确定考虑用户的可操作性&#xff0c;遵循开发的系统优化的原则&#xff0c;…

【C语言】Linux内核bind系统调用代码

一、Linux 4.9内核bind系统调用代码注释 int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) {struct socket *sock; // 定义socket对象的指针struct sockaddr_storage address; // 用于存储从用户空间复制过来的地址int err…

谷歌搜索技巧与 ChatGPT 实用指南:提升你的在线生产力

探索谷歌搜索技巧&#xff0c;提升搜索效率 前言 在搜索三巨头百度、必应、谷歌中&#xff0c;谷歌在搜索精确度以及多语言兼容性方面有明显的优势。其次在国内想要使用谷歌搜索你需要会科学上网&#xff08;这里不说&#xff09;。 一.排除干扰内容&#xff08;广告&#xff…

vue-进阶语法(四)

目录 v-model原理 v-model应用于组件 sync修饰符 ref 和 $refs&#xff08;重点&#xff09; $nextTick v-model原理 原理&#xff1a;v-model本质上是一个语法糖。例如应用在输入框上&#xff0c;就是 value属性 和 input事件 的合写。 作用&#xff1a;提供数据的双向…

压缩PDF的大小-Adobe Acrobat Pro

经常遇到上传的pdf太大&#xff0c;无法成功上传。 今天找到一个方法&#xff1a; 打开Adobe Acrobat Pro软件 → 文件 → 另存为其他&#xff08;H&#xff09;... →缩小大小的PDF 版本选择 4.0 最低的版本。 文件由9M变为1.5M。

Codeforces Round 924 (Div. 2) B - D

B. Equalize 题目&#xff1a; 思路&#xff1a;首先排序然后去重&#xff08;可以用set来去重&#xff09;&#xff0c;我们可以肯定的是&#xff0c;如果连续k个数最大值最小值的差小于等于n的话&#xff0c;那么这个长度为k的区间就符合答案要求&#xff0c;那么k就和答案…