Android Json 数据解析优化

news2024/11/29 2:42:10

文章目录

  • 基于 Gson 的 Json 数据解析库
    • 1. 背景
    • 2. 使用
      • 2.1 在模块下的 `build.gradle` 文件中加入远程依赖
      • 2.2 常规使用
      • 2.3 其它 API
      • 2.3 Retrofit 替换 Gson
    • 3. Json 数据解析库 UML 类图与时序图
      • 3.1 json-parse 类图
      • 3.2 json-parse 时序图
    • 4. Json 工具类(JsonUtils)
    • 5. Json 解析容错兜底(GsonFactory)
      • 5.1 容错支持的数据类型
      • 5.2 容错处理规则
      • 5.3 GsonFactory 容错工厂适配器
    • 6. Json 异常上报
    • 7. Json 解析库 AB Test
    • 8. Json 脏数据纠正
    • 9. Json 解析库容错覆盖(单元测试)
    • 10. 探讨
      • 10.1 有没有必要处理 Json 解析容错兜底?
      • 10.2 如果后端用的是 Java,还有必要处理容错吗?
      • 10.3 iOS 和前端是如何进行 Json 数据解析?
        • 10.3.1 iOS 端如何进行 Json 数据解析?
        • 10.3.2 前端如何进 Json 数据解析?
    • 11 参考资料

基于 Gson 的 Json 数据解析库

1. 背景

  • 不知道大家在开发的过程中有没有遇见过 Json 数据解析异常等错误,咱们分析其原因大多数是因为其返回的 Json 数据结构与预期的数据结构不一致导致的,而数据结构不一致的来源主要有两点:
  • (1)后端返回的 Json 数据结构与预期的 Json 数据结构不一致,或者存在某些 bug 导致返回的 Json 数据结构不一致;
  • (2)某个字段后端接收的就是一个 Json 字符串,且后端并不关心这个字段具体的内容,而这个 Json 字符串 是由业务端生成(AndoridiOS前端PC),然而多端定义不统一或者某端 bug 也会导致返回的 Json 数据结构不一致。
  • 针对于上述的业务背景,我们希望该解析库能做到如下功能:
  • (1)统一项目中 Json 数据解析的调用,从而方便统一调用来源;
  • (2)针对于某些业务场景有对应的 Json 解析容错兜底方案,以防止因为某些字段数据结构错误而导致整个数据类无法生成;
  • (3)对应的 Json 异常上报机制,能够方便追溯线下线上存在的 Json 数据结构异常问题。

2. 使用

2.1 在模块下的 build.gradle 文件中加入远程依赖

dependencies {
  // Json 解析库
  implementation 'cn.eeo.android.core:json-parse:1.0.0-alpha01'
}
  • 需要注意的是:该 Json 解析库使用的 Gson 框架是基于 2.10 版本, 请保持版本统一,否则将会出现版本兼容问题;

2.2 常规使用

  • 使用 JsonUtils 类中的方法即可满足需求,具体可看后续的第 4 点;

2.3 其它 API

// 设置自定义的 Gson 对象
GsonFactory.setSingletonGson(Gson)

// 创建一个 Gson 构建器(已容错兜底)
val gsonBuilder : GsonBuilder = GsonFactory.newGsonBuilder();

// 注册类型适配器
GsonFactory.registerTypeAdapterFactory(TypeAdapterFactory)

// 注册构造函数创建器
GsonFactory.registerInstanceCreator(Type, InstanceCreator<*>)

// 添加反射访问过滤器
GsonFactory.addReflectionAccessFilter(ReflectionAccessFilter)

// 设置 Json 解析容错监听,具体可看后续的第 6 点 

2.3 Retrofit 替换 Gson

val retrofit: Retrofit = Retrofit.Builder()
  .addConverterFactory(GsonConverterFactory.create(GsonFactory.getSingletonGson()))
  .build()
  • 需要注意的是:Retrofit 替换会不会影响到整体范围,因为容错也不是万能,需要具体业务具体分析。

3. Json 数据解析库 UML 类图与时序图

3.1 json-parse 类图

image

  • 需要注意的是:原图预览请点击此处

3.2 json-parse 时序图

image

  • 需要注意的是:原图预览请点击此处

4. Json 工具类(JsonUtils)

方法名含义
Gson createDefaultGson()创建并返回默认的 Gson,具有容错兜底机制
setGson(String, Gson)设置 Gson 对象
Gson getGson获取默认的 Gson
Gson getGson(String)根据 key 键获取并返回 Gson
String toJson(Any)String toJson(Any, Type)String toJson(Gson, Any)String toJson(Gson, Any, Type)对象转 Json
T fromJson(String, Class<T>)T fromJson(String, Type)T fromJson(Reader, Class<T>)T fromJson(Reader, Type)T fromJson(Gson, String, Class<T>)T fromJson(Gson, String, Type)T fromJson(Gson, Reader, Class<T>)T fromJson(Gson, Reader, Type)Json 串转对象
Type getListType(Type)获取链表类型
Type getSetType(Type)获取集合类型
Type getMapType(Type, Type)获取字典类型
Type getArrayType(Type)获取数组类型
Type getType(Type, Type)获取类型
void setJsonCallback(JsonCallback)设置 Json 解析异常监听器
JsonCallback getJsonCallback()获取并返回 Json 解析异常监听器

5. Json 解析容错兜底(GsonFactory)

5.1 容错支持的数据类型

支持的数据类型含义
String字符串
boolean/Boolean布尔值
int/Integer整数,属于数值类
long/Long长整数,属于数值类
float/Float单精度浮点数,属于数值类
double/Double双精度浮点数,属于数值类
BigDecimal精度更高的浮点数,属于数值类
Array数组集合
BeanBean
MapMap 集合
JSONArray
JSONObject

5.2 容错处理规则

  • 大家可能觉得 Gson 解析容错没什么,那是因为我们对 Gson 解析失败的场景没有了解过:
  • 惊吓不断:后端有数据时返回 JsonObject 类型,没数据返回 [],Gson 会直接抛出异常;
  • 意想不到:如果客户端定义的是 boolean 类型,但是后台返回的是 0 或者 1,Gson 会直接抛出异常;
  • 措手不及:如果客户端定义的是 int 或者 long 类型,但是后台返回的是 float 或者 double 类型,Gson 会直接抛出异常。
  • 基于上述,定义了如下容错处理规则:
  • (1)如果后端返回的类型和客户端定义的类型不匹配,框架就跳过解析这个字段,不影响其它字段正常解析;
  • (2)如果客户端定义的是 boolean 类型,但是后台返回整数,框架则将非 0 的数值则赋值为 true,否则为 false
  • (3)如果客户端定义的是 int 或者 long 类型,但后台返回浮点数,框架就对数值进行直接取整并赋值给字段。

5.3 GsonFactory 容错工厂适配器

/**
 * @Author: xing.tang
 * @Date: 2023/2/16-15:17
 * @Description: 基于 Gson 的 Json 解析容错工厂适配器
 */
class GsonFactory private constructor() {
  companion object {

    /**
     * 根据"对象类型"存储"构造函数创建器"的 ArrayMap 集合
     */
    private val INSTANCE_CREATORS = ArrayMap<Type, InstanceCreator<*>>(0)

    /**
     * 存储"类型解析适配器"的 MutableList 集合
     */
    private val TYPE_ADAPTER_FACTORIES: MutableList<TypeAdapterFactory> = mutableListOf()

    /**
     * 存储"反射访问过滤器"的 MutableList 集合
     */
    private val REFLECTION_ACCESS_FILTERS: MutableList<ReflectionAccessFilter> = mutableListOf()

    @Volatile
    private var singletonGson: Gson? = null

    /**
     * 获取单利的 Gson 对象
     *
     * @return Gson
     */
    fun getSingletonGson(): Gson =
      singletonGson ?: synchronized(this) {
        singletonGson ?: newGsonBuilder().create().also {
          singletonGson = it
        }
      }

    /**
     * 设置单例的 Gson 对象
     *
     * @param gson Gson
     */
    fun setSingletonGson(gson: Gson) {
      this.singletonGson = gson
    }

    /**
     * 创建并返回默认的 Gson 构建对象
     */
    fun newGsonBuilder(): GsonBuilder {
      val gsonBuilder = GsonBuilder()
      if (! BuildConfig.DEBUG) {
        val constructor = ConstructorConstructor(INSTANCE_CREATORS, true, REFLECTION_ACCESS_FILTERS)
        gsonBuilder.registerTypeAdapterFactory(TypeAdapters.newFactory(String::class.java, StringTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(Boolean::class.javaPrimitiveType, Boolean::class.java, BooleanTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(Int::class.javaPrimitiveType, Int::class.java, IntegerTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(Long::class.javaPrimitiveType, Long::class.java, LongTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(Float::class.javaPrimitiveType, Float::class.java, FloatTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(Double::class.javaPrimitiveType, Double::class.java, DoubleTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(BigDecimal::class.java, BigDecimalTypeAdapter()))
          .registerTypeAdapterFactory(CollectionTypeAdapterFactory(constructor))
          .registerTypeAdapterFactory(ReflectiveTypeAdapterFactory(constructor, FieldNamingPolicy.IDENTITY, Excluder.DEFAULT))
          .registerTypeAdapterFactory(MapTypeAdapterFactory(constructor, false))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(JSONObject::class.java, JSONObjectTypeAdapter()))
          .registerTypeAdapterFactory(TypeAdapters.newFactory(JSONArray::class.java, JSONArrayTypeAdapter()))
        // 添加到自定义的类型解析适配器,因为在 GsonBuilder.create 方法中会对 List 进行反转,所以这里需要放到最后的位置上,这样就会优先解析
        for (typeAdapterFactory in TYPE_ADAPTER_FACTORIES) {
          gsonBuilder.registerTypeAdapterFactory(typeAdapterFactory)
        }
      }
      return gsonBuilder
    }

    /**
     * 注册类型解析适配器
     *
     * @param factory 类型解析适配器
     */
    fun registerTypeAdapterFactory(factory: TypeAdapterFactory) {
      TYPE_ADAPTER_FACTORIES.add(factory)
    }

    /**
     * 注册构造函数创建器
     *
     * @param type    对象类型
     * @param creator 实例创建器
     */
    fun registerInstanceCreator(type: Type, creator: InstanceCreator<*>) {
      INSTANCE_CREATORS[type] = creator
    }

    /**
     * 添加反射访问过滤器,同等于 [GsonBuilder.addReflectionAccessFilter]
     *
     * @param filter 反射访问过滤器
     */
    fun addReflectionAccessFilter(filter: ReflectionAccessFilter) {
      REFLECTION_ACCESS_FILTERS.add(0, filter)
    }
  }
}

6. Json 异常上报

  • 自定义 Exception,取名为 JsonException,用作于 Json 解析异常上报的 Exception,如下所示:
/**
 * @Author: xing.tang
 * @Date: 2023/2/13-16:29
 * @Description: Json 解析异常上报的 Exception
 */
class JsonException: Exception {

  /**
   * 使用指定的消息和原因创建异常
   *
   * @param message 描述此异常的可能原因的错误消息
   */
  constructor(message: String): super(message)

  /**
   * 使用指定的消息和原因创建异常
   *
   * @param cause 导致抛出此异常的根异常
   */
  constructor(cause: Throwable): super(cause)

  /**
   * 使用指定的消息和原因创建异常
   *
   * @param message 描述所发生情况的错误消息
   * @param cause 导致抛出此异常的根异常
   */
  constructor(message: String, cause: Throwable): super(message, cause)

}
  • 自定义 Callback,取名为 JsonCallback,用作于 Json 解析异常监听器,如下所示:
/**
 * @Author: xing.tang
 * @Date: 2023/2/13-16:21
 * @Description: Json 解析异常监听器
 */
interface JsonCallback {

  /**
   * 类型解析异常
   *
   * @param typeToken 类型 Token
   * @param fieldName 字段名称(可能为空)
   * @param jsonToken 后台给定的类型
   */
  fun onTypeException(typeToken: TypeToken<*>?, fieldName: String?, jsonToken: JsonToken?)

  /**
   * 对象序列化为 Json 异常
   *
   * @param methodName 方法名称
   * @param any        要序列化的对象
   * @param typeOfAny  any 的特定通用类型
   * @param cause      导致抛出此异常的根异常
   */
  fun onToJsonException(methodName: String, any: Any?, typeOfAny: Type?, cause: Throwable?)

  /**
   * 对象反序列化为 Json 异常
   *
   * @param methodName 方法名称
   * @param json       要转换的 json 字符串
   * @param type       要转换为的给定类型
   * @param cause      导致抛出此异常的根异常
   */
  fun onFromJsonException(methodName: String, json: String?, type: Type?, cause: Throwable?)

  /**
   * 获取对象异常
   *
   * @param methodName 方法名称
   * @param type1      类型1
   * @param type2      可变类型2
   * @param cause      导致抛出此异常的根异常
   */
  fun onGetTypeException(methodName: String, type1: Type?, vararg type2: Type?, cause: Throwable?)

}
  • 自定义 Json 解析库对应的初始化器,取名为 JsonInitializer,用作于初始化 JsonCallback 使用,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="cn.eeo.json">

  <application>
    <meta-data
      android:name="cn.eeo.json.JsonInitializer"
      android:value="cn.eeo.initialize" />
  </application>

</manifest>

/**
 * @Author: xing.tang
 * @Date: 2023/2/17-11:24
 * @Description: Json 解析库对应的初始化器
 */
class JsonInitializer: Initializer {

  override fun dependencies(): List<Class<out Initializer>> = emptyList()

  override fun initialize(context: Context) {
    JsonUtils.setJsonCallback(object: JsonCallback {

      /**
       * 类型解析异常
       *
       * @param typeToken 类型 Token
       * @param fieldName 字段名称(可能为空)
       * @param jsonToken 后台给定的类型
       */
      override fun onTypeException(typeToken: TypeToken<*>?, fieldName: String?, jsonToken: JsonToken?) {
        // 打印输出日志
        JsonUtils.logger.error("onTypeException \ntypeToken:$typeToken \nfieldName:$fieldName \njsonToken:$jsonToken")
        // 上报到 Sentry 错误列表中
        CrashReport.postException(JsonException("onTypeException typeToken:$typeToken fieldName:$fieldName jsonToken:$jsonToken"))
      }

      /**
       * 对象序列化为 Json 异常
       *
       * @param methodName 方法名称
       * @param any        要序列化的对象
       * @param typeOfAny  any 的特定通用类型
       * @param cause      导致抛出此异常的根异常
       */
      override fun onToJsonException(methodName: String, any: Any?, typeOfAny: Type?, cause: Throwable?) {
        // 打印输出日志
        JsonUtils.logger.error("onToJsonException \nmethodName:$methodName \nany:$any \ntypeOfAny:$typeOfAny \ncause:$cause")
        // 上报到 Sentry 错误列表中
        CrashReport.postException(JsonException("onToJsonException methodName:$methodName any:$any typeOfAny:$typeOfAny cause:$cause"))
      }

      /**
       * 对象反序列化为 Json 异常
       *
       * @param methodName 方法名称
       * @param json       要转换的 json 字符串
       * @param type       要转换为的给定类型
       * @param cause      导致抛出此异常的根异常
       */
      override fun onFromJsonException(methodName: String, json: String?, type: Type?, cause: Throwable?) {
        // 打印输出日志
        JsonUtils.logger.error("onFromJsonException \nmethodName:$methodName \njson:$json \ntype:$type \ncause:$cause")
        // 上报到 Sentry 错误列表中
        CrashReport.postException(JsonException("onFromJsonException methodName:$methodName json:$json type:$type cause:$cause"))
      }

      /**
       * 获取对象异常
       *
       * @param methodName 方法名称
       * @param type1      类型1
       * @param type2      类型1
       * @param cause      导致抛出此异常的根异常
       */
      override fun onGetTypeException(methodName: String, type1: Type?, vararg type2: Type?, cause: Throwable?) {
        // 打印输出日志
        JsonUtils.logger.error("onGetTypeException \nmethodName:$methodName \ntype1:$type1 \ntype2:$type2 \ncause:$cause")
        // 上报到 Sentry 错误列表中
        CrashReport.postException(JsonException("onGetTypeException methodName:$methodName type1:$type1 type2:$type2 cause:$cause"))
      }
    })
  }
}

7. Json 解析库 AB Test

  • 线下环境的 AB Test
  • debug 包下采用非容错兜底机制的 GsonBuilder 构建 Gson 去进行 Json 解析,而非 debug 包下采用容错兜底机制的 GsonBuilder 构建 Gson 去进行 Json 解析,其目的是希望如果存在必现错误,即可在开发阶段暴露出来,从而去发现与解决;
  • 线上环境的 AB Test
  • 通过服务器下发的字段配置,去 enabledisable 容错兜底机制,从而实现线上的 AB Test

8. Json 脏数据纠正

  • 错误的 Json 数据结构咱们该如何纠正?
  • 第一步:我们需要定位到错误的 Json 数据结构产生的来源,到底是后端接口存在的问题,还是其它端生产的错误 Json 数据,只有解决了来源,来能阻止线上继续产生新的脏数据;
  • 第二步:其次去对线上已有的脏数据进行数据结构纠正,这个就要看到底是业务端针对某些特殊场景去进行纠正操作,还是后端对脏数据进行分析后通过对应的脚本去进行具体脏数据的精准纠正操作。

9. Json 解析库容错覆盖(单元测试)

  • 通过上述可知,其支持容错的数据类型基本涵盖 99.99% 的开发场景,但是如何进行容错覆盖验证呢?
  • 这就需要借助咱们的单元测试来进行操作了,通过全面的 case 再结合具体的单元测试用例 ,即可完成容错覆盖验证了。
  • 具体可运行该库中的单元测试用例来查看效果:
数据类型容错的范围数据示例
字符串bean、集合、null、布尔值、数值{}、[]、nullfalse、0
布尔值bean、集合、null、字符串、数值{}、[]、null、“”、0
数值bean、集合、null、字符串、布尔值{}、[]、null、“”、false
集合beannull、字符串、布尔值、数值{}、null、“”、false、0
bean集合、null、字符串、布尔值、数值[]、null、“”、false、0

10. 探讨

10.1 有没有必要处理 Json 解析容错兜底?

  • 个人觉得这个问题没有具体的答案。按照理想的状态应该是不需要进行 Json 解析容错兜底的,但是理想很丰满现实很骨感,因为后端返回的数据结构或其它端生产的数据结构是什么样的我们无法把控,但是有一点是可以肯定的,我们都希望它不出现问题,因为返回的 Json 数据结构存在问题而导致页面数据丢失或者整个应用 崩溃,这是我们最不想见到的现象。当然 Json 解析容错兜底也不是万能的,我始终相信没有最好的方案,只有最合适的方案,所以这个需要根据具体的业务场景选定具体的实现方案。

10.2 如果后端用的是 Java,还有必要处理容错吗?

  • 如果后端用的是 PHP,那么十分推荐你使用这个框架,因为 PHP 返回的数据结构很乱,经历过的同学都懂;如果后端用的是 Java,那么就根据实际情况而定。不管后端用的是什么,咱们都秉承一个原则具体问题具体分析。
  • 但是最怕一种现象是,Json 数据结构存在问题,前端没问题,iOS 没问题,为什么 Android 端有问题?然后一问其它端的同学,iOS 做了兜底,前端也做了兜底,就 Android 端没做兜底,Android 为什么不做?总不能说咱们用的谷歌 Gson 解析框架它的机制本身就是这样的。虽说问题产生的原因不是 Android 端,但是对于用户使用感知存在的问题就是 Android 端,这就是很难受的事情了。

10.3 iOS 和前端是如何进行 Json 数据解析?

10.3.1 iOS 端如何进行 Json 数据解析?

  • iOS 端使用的是 NSJSONSerialization 解析器,从而实现 JSONfoundation object 之间的相互转换;参考链接
  • 需要注意的是:iOS 的数值类型(IntLongFloatDouble…)它属于弱类型转换,例如规约的是 Int 类型字段,下发了一个 Double 类型,这样在 iOS 端不会存在问题。

10.3.2 前端如何进 Json 数据解析?

  • 前端使用的是 JSON 解析器,从而实现 JSONJavaScript 对象之间的互相转换;参考链接

11 参考资料

  • Gson-github
  • AndroidUtilCode-github
  • GsonFactory-github

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

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

相关文章

运动控制器PSO视觉飞拍与精准输出的C++开发(一):单轴PSO

本文主要介绍正运动技术EtherCAT控制器在VS平台采用C语言实现的各种PSO功能。正运动提供多种PSO模式供用户搭配不同的场景使用。 本节主要讲解采用TABLE寄存器存储的数据表触发比较&#xff0c;更多周期比较模式、二维三维比较模式参见下一节说明。 一、硬件介绍 ZMC408CE是…

STM32 CubeMX按键点灯

本文代码使用 HAL 库。 文章目录前言一、按键原理图二、CubeMX 创建工程三、代码讲解&#xff1a;1. GPIO的输入HAL库函数&#xff1a;2. 消抖&#xff1a;3. 详细代码四&#xff0c;实验现象&#xff1a;总结前言 我们继续讲解 stm32 f103&#xff0c;这篇文章将详细 为大家讲…

MATLAB | 如何用MATLAB绘制花里胡哨的山脊图

本期推送教大家如何绘制各种样式的山脊图&#xff0c;这里做了一个工具函数用来实现好看的山脊图的绘制&#xff0c;编写不易请多多点赞&#xff0c;大体绘制效果如下&#xff1a; 依旧工具函数放在文末。 教程部分 0 数据准备 数据为多个一维向量放在元胞数组中&#xff0c;…

oracle数据库常用操作

1.连接登录切换用户su - oracle以管理员模式登录到sqlplus&#xff1a;sqlplus / as sysdba oracle登录身份有三种&#xff1a;1.1Normal 普通身份&#xff1b;1.2.sysdba 系统管理员身份&#xff1b;若以 ‘sysdba’ 方式认证&#xff0c;登录用户为 ‘SYS’&#xff0c;为 Or…

spring事务管理器原理?mybatis如何集成spring事务管理器?

目录 1. spring事务管理器开启事务做了些什么事情&#xff1a; 1.1 创建mysql数据库连接&#xff1a; 1.2 开启mysql事务 1.3 将当前事务所使用的连接绑定到ThreadLocal中,供后续执行sql命令使用。 2. mybatis如何集成spring事务管理器&#xff1f; 3. mysql相关命令 4. …

Java常用框架(三)

三、SpringBoot 1.Spring及其优缺点&#xff1f; 1.1 概念 重量级企业开发框架EJB的替代品&#xff0c;通过依赖注入、面向切面编程&#xff0c;使用简单Java对象POJO为企业Java开发提供了相对简单的方法。 1.2 优缺点 1.2.1 优点 组件代码轻量级 1.2.1 缺点 配置重量级…

Spring Batch 高级篇-多线程步骤

目录 引言 概念 案例 转视频版 引言 接着上篇&#xff1a;Spring Batch ItemWriter组件&#xff0c;了解Spring Batch ItemWriter处理组件后&#xff0c;接下来一起学习一下Spring Batch 高级功能-多线程步骤 概念 默认的情况下&#xff0c;步骤基本上在单线程中执行&…

springBoot使用ShardingJDBC实现分表

ShardingSphere的介绍 ShardingSphere是一款起源于当当网内部的应用框架。2015年在当当网内部诞 生&#xff0c;最初就叫ShardingJDBC。2016年的时候&#xff0c;由其中一个主要的开发人员张亮&#xff0c; 带入到京东数科&#xff0c;组件团队继续开发。在国内历经了当当网、…

链动2+1系统|购买三单就能迅速回本,链动2+1模式到底有多暴利?

链动21模式号称起步创业无泡沫&#xff0c;半个月就能盈利上百万&#xff0c;用户裂变速度更是让人瞠目结舌。那么&#xff0c;链动21模式到底有多暴利&#xff1f;其实链动21模式最关键的&#xff0c;是合理的利润分配和奖励机制&#xff0c;让消费者在购物的同时&#xff0c;…

【解决报错】‘jupyter‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件

在当前路径下使用cmd打开后&#xff0c;输入jupyter notebook出现如下错误&#xff1a; 通常可能出现的问题有两种&#xff1a; &#xff08;1&#xff09;你本身就没安装jupyter&#xff0c;如果你配置了anaconda&#xff0c;就自带jupyter&#xff0c;直接跳到问题2。如果确…

Confluence主页面更新记录停留在去年,搜索也只能搜索去年之前的数据问题解决方案

问题描述 Confluence主页最近更新页面不更新了&#xff0c;停留在之前的时间段。其次搜索也只能搜索出来停留在这个时间段之前的数据。 核心原因 索引出现问题了&#xff0c;重建索引即可。 解决办法 直接重启Confluence。 重启Confluence的姿势 描述一下我解决思路&…

28-vuex

vuex 一、vuex 专门在vue中实现集中式状态&#xff08;数据&#xff09;管理的一个Vue插件&#xff0c;对Vue应用中多个组件的共享状态进行集中式的管理&#xff08;读/写&#xff09;&#xff0c;也是一种组件间通信的方式&#xff0c;且适用于任意组件间通信。 使用场景&a…

Java 【数据结构OJ题十道】—— 二叉树篇1

文章目录一、 检查两棵二叉树是否相同二、 另一棵二叉树的子树三、 二叉树的构建及遍历四、序列化二叉树和反序列化二叉树(难)五、二叉树创建字符串六、 二叉树前序非递归遍历实现七、 二叉树中序非递归遍历实现八、 二叉树后序非递归遍历实现九、二叉搜索树中找到两个结点的最…

如何将电脑文件备份到百度网盘

如何将电脑文件备份到百度网盘&#xff1f;说到文件备份&#xff0c;很多小伙伴会将电脑文件备份到移动硬盘或者U盘里&#xff0c;移动硬盘和U盘是比较常见的存储介质&#xff0c;使用和携带起来也是非常方便&#xff0c;因此深受大家的喜欢。除此之外&#xff0c;大家可能还忽…

2023年,IT互联网还有发展前景吗?

不得不说&#xff0c;互联网在整个社会经济发展中扮演着不可或缺的角色&#xff1b;不仅自身的技术具有前沿性&#xff0c;也推动着其他行业进入数字化经济时代&#xff0c;让我们的工作生活变得更加便捷。 在“互联网”时代&#xff0c;每个服务行业都会利用大数据&#xff0…

将自带记事本替换为Notepad2【中文版,带替换文件】

Notepad2是我在寻找一个合适的代码浏览工具的时候发现的&#xff0c;当需要一个用来浏览代码的文本编辑器时候&#xff0c;需要体积小&#xff0c;速度快&#xff0c;语法高亮&#xff0c;解释度高&#xff0c;VsCode作为生产环境已经不适合作为浏览工具了。了解到Notepad2&…

《动手学习深度学习》笔记(二)线性神经网络

三、线性神经网络 3.1 线性回归 3.1.1 介绍 1. 回归是为一个或多个自变量与因变量之间的关系建模的一类方法。而线性回归基于几个简单的假设&#xff1a;① 自变量和因变量关系是线性的&#xff1b;② 允许包含噪声但是噪声遵循正态分布。   2. 训练数据集/训练集&#xff…

算法训练营 day53 动态规划 买卖股票的最佳时机系列2

算法训练营 day53 动态规划 买卖股票的最佳时机系列2 买卖股票的最佳时机III 123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。…

软件项目管理知识回顾---网络图

网络图 9.网络图 9.1简介 1.分类 AOA&#xff0c;双代号&#xff0c;ADMAON,PDM&#xff0c;单代号&#xff0c;前导图2.活动的逻辑管理 头到头/尾&#xff0c;尾到头/尾 依赖关系 3.工序 紧前紧后9.2绘制规则 1.两个节点只能一条线。不能是平行线。平行的话就不知道是哪个活动…

LeetCode-93. 复原 IP 地址

目录题目思路回溯法题目来源 93. 复原 IP 地址 题目思路 意识到这是切割问题&#xff0c;切割问题就可以使用回溯搜索法把所有可能性搜出来&#xff0c;和131.分割回文串就十分类似了。 回溯法 1.递归参数 startIndex一定是需要的&#xff0c;因为不能重复分割&#xff0c…