探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因

文章目录

  • 一、问题背景
  • 二、问题原因
  • 三、问题探析
    • Kotlin空指针校验
    • Gson.fromJson(String json, Class<T> classOfT)
    • TypeToken
    • Gson.fromJson(JsonReader reader, TypeToken<T> typeOfT)
    • TypeAdapter 和 TypeAdapterFactory
    • ReflectiveTypeAdapterFactory
    • RecordAdapter 和 FieldReflectionAdapter
  • 四、解决方法

一、问题背景

在一次开发过程中,由于在 Kotlin 定义的实体类多了一个 json 不存在的 时,即使是对象类型是不可空的对象且指定了默认值,使用 Gson 库解析出来的实体对象中的那个变量是null,导致后面使用的此变量的时候导致出现空指针异常。比如:
实体类对象定义如下

data class Entity(/*** 存在的元素*/val existParam: String,/*** 不存在的元素*/val nonExistParam: String = ""
)

nonExistParamjson 结构中不存在的 keyjson 如下

{"existParam" : "exist"
}

使用 Gson 进行解析 json

val jsonEntity = Gson().fromJson(json, Entity::class.java)
println("entity = $jsonEntity")

最后得到的输出为:
entity = Entity(existParam=exist, nonExistParam=null)

此时可以发现,nonExistParam 已经被指定为不可空的String 类型,且使用了默认值 "",但解析出来的实体类中nonExistParam=null,如果此时不注意直接使用 nonExistParam,可能引发空指针异常。

二、问题原因

此问题的原因是,Gson 在解析实体类的时候会使用反射构造方法创建对象,在通过反射的方式设置对象的值。因此,如果实体类的成员在json中不存在,则不会有机会被赋值,其会保持一个默认值(对于对象来说即为空)。而在 Kotlin 中,只要在调用实际方法的时候,会触发Kotlin的空校验,从而抛出空指针异常,提早发现问题。但是Gson的反射的方式避开了这个空校验,所以成员的值为 null,直到使用时可能会出现空指针异常

三、问题探析

我们需要探寻 Gson 在解析 json 的时候,究竟发生了什么,导致会出现解析出来的对象出现了 null

Kotlin空指针校验

但是,我们知道Kotlin是对可空非常敏感的,已经指定了成员是不可空的,为什么会把 null 赋值给了不可空成员呢。

我们可以看 Kotlin 的字节码,并反编译成java源码,可以看到最后由Kotlin生成的java源码是怎样的。
在这里插入图片描述
我们可以得到如下的两个方法。

public Entity(@NotNull String existParam, @NotNull String nonExistParam) {Intrinsics.checkNotNullParameter(existParam, "existParam");Intrinsics.checkNotNullParameter(nonExistParam, "nonExistParam");super();this.existParam = existParam;this.nonExistParam = nonExistParam;
}// $FF: synthetic method
public Entity(String var1, String var2, int var3, DefaultConstructorMarker var4) {if ((var3 & 2) != 0) {var2 = "";}this(var1, var2);
}

第一个即为构造方法,传递了两个参数,且有 Intrinsics.checkNotNullParameter 可空检查。如果这里有空,则会抛出异常。而下一个则是因为对 nonExistParam 的变量设置了默认值生成的构造方法,默认值为 “”
因此,只有正常调用构造方法的时候,才会触发可空的检查。

Gson.fromJson(String json, Class classOfT)

首先,我们使用的方法是 Gson.fromJson(String json, Class<T> classOfT),这个方法是传进一个 json 的字符串和实体对象的 Class 类型,随后的返回值就是一个实体对象。方法如下:

public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {T object = fromJson(json, TypeToken.get(classOfT));return Primitives.wrap(classOfT).cast(object);
}

我们先看 Primitives.wrap(classOfT).cast(object); 这句的作用,点进去看 Primitives.wrap()方法:

/*** Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise* returns {@code type} itself. Idempotent.** <pre>*     wrap(int.class) == Integer.class*     wrap(Integer.class) == Integer.class*     wrap(String.class) == String.class* </pre>*/
@SuppressWarnings({"unchecked", "MissingBraces"})
public static <T> Class<T> wrap(Class<T> type) {if (type == int.class) return (Class<T>) Integer.class;if (type == float.class) return (Class<T>) Float.class;if (type == byte.class) return (Class<T>) Byte.class;if (type == double.class) return (Class<T>) Double.class;if (type == long.class) return (Class<T>) Long.class;if (type == char.class) return (Class<T>) Character.class;if (type == boolean.class) return (Class<T>) Boolean.class;if (type == short.class) return (Class<T>) Short.class;if (type == void.class) return (Class<T>) Void.class;return type;
}

从代码中可以看出,这个方法的作用就是将基本数据类型转换成包装类,即将 int 转换成 Integer,将 float 转换成 Float 等。如果非基本数据类,则直接返回类的本身。而随后接的 .cast(object) 则是强制数据类型转换的的Class类接口,即是 (T) object

因此最后一句的作用只是用来强制转换对象的,与解析 json 无关。我们回到第一句 T object = fromJson(json, TypeToken.get(classOfT));,这句代码调用了 Gson.fromJson(String json, TypeToken<T> typeOfT),并使用 TypeToken 包装了 class

TypeToken

我们先看 TypeToken 的官方文档解释:

Represents a generic type T. Java doesn’t yet provide a way to represent generic types, so this class does. Forces clients to create a subclass of this class which enables retrieval the type information even at runtime.

这是一个代表泛型T(generic type T)的类,在 Java 运行时会进行泛型擦除,因此在运行过程中是无法拿到泛型的准确类型,因此 TypeToken 被创建出来,可以在运行时创建基于此类的子类并拿到泛型的信息。也即这个类通过包装泛型类,提供了在运行时获取泛型对象的类信息的能力。

Gson.fromJson(JsonReader reader, TypeToken typeOfT)

Gson.fromJson(String json, TypeToken<T> typeOfT) 方法开始,层次往下只是将 String 或 其他类型的来源封装成 JsonReader类,代码如下:

public <T> T fromJson(String json, TypeToken<T> typeOfT) throws JsonSyntaxException {if (json == null) {return null;}StringReader reader = new StringReader(json);return fromJson(reader, typeOfT);
}public <T> T fromJson(Reader json, TypeToken<T> typeOfT)throws JsonIOException, JsonSyntaxException {JsonReader jsonReader = newJsonReader(json);T object = fromJson(jsonReader, typeOfT);assertFullConsumption(object, jsonReader);return object;
}

首先使用 StringReader 包装 json 字符串,随后使用 JsonReader 包装 StringReader,随后再调用 Gson.fromJson(JsonReader reader, TypeToken<T> typeOfT) 进行解析 json,得到 <T> 对象。因此我们来看 Gson.fromJson(String json, TypeToken<T> typeOfT) 方法。

public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT)throws JsonIOException, JsonSyntaxException {boolean isEmpty = true;Strictness oldStrictness = reader.getStrictness();if (this.strictness != null) {reader.setStrictness(this.strictness);} else if (reader.getStrictness() == Strictness.LEGACY_STRICT) {// For backward compatibility change to LENIENT if reader has default strictness LEGACY_STRICTreader.setStrictness(Strictness.LENIENT);}try {JsonToken unused = reader.peek();isEmpty = false;TypeAdapter<T> typeAdapter = getAdapter(typeOfT);return typeAdapter.read(reader);} catch (EOFException e) {/** For compatibility with JSON 1.5 and earlier, we return null for empty* documents instead of throwing.*/if (isEmpty) {return null;}throw new JsonSyntaxException(e);} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IOException e) {// TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxExceptionthrow new JsonSyntaxException(e);} catch (AssertionError e) {throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);} finally {reader.setStrictness(oldStrictness);}
}

这个方法的一开始是将Gson的 Strictness设置给 JsonReader。随后再获取 类型的 TypeAdapter,使用TypeAdapterread.read(JsonReader in),进行解析 json得到实体对象。

TypeAdapter 和 TypeAdapterFactory

TypeAdapter 是一个抽象类,其有两个抽象方法

/*** Writes one JSON value (an array, object, string, number, boolean or null) for {@code value}.** @param value the Java object to write. May be null.*/
public abstract void write(JsonWriter out, T value) throws IOException;/*** Reads one JSON value (an array, object, string, number, boolean or null) and converts it to a* Java object. Returns the converted object.** @return the converted Java object. May be {@code null}.*/
public abstract T read(JsonReader in) throws IOException;

也就是 write() 方法定义如何把 实体对象 转换成 json字符串 的实现,和 read() 方法定义如何把 json字符串 转换成 实体对象 的实现。默认已经有部分实现了 Java 常用类的转换方式,如基础数据类 int,float,boolean等 和 map 、set、list 提供转换方式。

TypeAdapterFactory是一个接口,只有一个 creat() 的方法

/*** Returns a type adapter for {@code type}, or null if this factory doesn't support {@code type}.*/
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);

此接口将支持的类型 type 返回一个 TypeAdapter,支持的 type 可以是多种类型。如果不支持的话就返回null。因此 TypeAdapterFactoryTypeAdapter 互相配合,可以生成解析和生成json的具体实现方法。

通过一个类型获取 TypeAdapterGson.getAdapter() 方法如下

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {Objects.requireNonNull(type, "type must not be null");TypeAdapter<?> cached = typeTokenCache.get(type);if (cached != null) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter = (TypeAdapter<T>) cached;return adapter;}Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();boolean isInitialAdapterRequest = false;if (threadCalls == null) {threadCalls = new HashMap<>();threadLocalAdapterResults.set(threadCalls);isInitialAdapterRequest = true;} else {// the key and value type parameters always agree@SuppressWarnings("unchecked")TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);if (ongoingCall != null) {return ongoingCall;}}TypeAdapter<T> candidate = null;try {FutureTypeAdapter<T> call = new FutureTypeAdapter<>();threadCalls.put(type, call);for (TypeAdapterFactory factory : factories) {candidate = factory.create(this, type);if (candidate != null) {call.setDelegate(candidate);// Replace future adapter with actual adapterthreadCalls.put(type, candidate);break;}}} finally {if (isInitialAdapterRequest) {threadLocalAdapterResults.remove();}}if (candidate == null) {throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);}if (isInitialAdapterRequest) {/** Publish resolved adapters to all threads* Can only do this for the initial request because cyclic dependency TypeA -> TypeB -> TypeA* would otherwise publish adapter for TypeB which uses not yet resolved adapter for TypeA* See https://github.com/google/gson/issues/625*/typeTokenCache.putAll(threadCalls);}return candidate;
}

首先,从缓存Map 的 typeTokenCache 中取出 TypeAdapter,如果有的话,则直接返回此 TypeAdapter 进行使用。

Objects.requireNonNull(type, "type must not be null");
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter = (TypeAdapter<T>) cached;return adapter;
}

随后从 ThreadLocal 中去取出 TypeAdapter,如果有的话,则直接返回此 TypeAdapter 进行使用。如果没有当前线程的 threadCalls Map,则直接创建新的threadCalls

Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
boolean isInitialAdapterRequest = false;
if (threadCalls == null) {threadCalls = new HashMap<>();threadLocalAdapterResults.set(threadCalls);isInitialAdapterRequest = true;
} else {// the key and value type parameters always agree@SuppressWarnings("unchecked")TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);if (ongoingCall != null) {return ongoingCall;}
}

随后遍历 Gson 对象的 TypeAdapterFactory List,如果是适合的对象,即通过 TypeAdapterFactory.create() 方法可以创建 TypeAdapter,则直接返回此对象。如果找不到,则会抛出异常。

TypeAdapter<T> candidate = null;
try {FutureTypeAdapter<T> call = new FutureTypeAdapter<>();threadCalls.put(type, call);for (TypeAdapterFactory factory : factories) {candidate = factory.create(this, type);if (candidate != null) {call.setDelegate(candidate);// Replace future adapter with actual adapterthreadCalls.put(type, candidate);break;}}
} finally {if (isInitialAdapterRequest) {threadLocalAdapterResults.remove();}
}if (candidate == null) {throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
}

因此需要去研究不同类型的 TypeAdapter 的做了什么。

ReflectiveTypeAdapterFactory

Gson 的构造方法中,会将支持的 TypeAdapterFactory 添加进 Gson 类的 fatories 中,有以下语句:

List<TypeAdapterFactory> factories = new ArrayList<>();// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy));// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);// users' type adapters
factories.addAll(factoriesToBeAdded);// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.BYTE_FACTORY);
factories.add(TypeAdapters.SHORT_FACTORY);
TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
factories.add(TypeAdapters.newFactory(double.class, Double.class, doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy));
factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));
factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));
factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
// Add adapter for LazilyParsedNumber because user can obtain it from Gson and then try to
// serialize it again
factories.add(TypeAdapters.newFactory(LazilyParsedNumber.class, TypeAdapters.LAZILY_PARSED_NUMBER));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
factories.add(TypeAdapters.CURRENCY_FACTORY);
factories.add(TypeAdapters.LOCALE_FACTORY);
factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DefaultDateTypeAdapter.DEFAULT_STYLE_FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);if (SqlTypesSupport.SUPPORTS_SQL_TYPES) {
factories.add(SqlTypesSupport.TIME_FACTORY);
factories.add(SqlTypesSupport.DATE_FACTORY);
factories.add(SqlTypesSupport.TIMESTAMP_FACTORY);
}factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(new ReflectiveTypeAdapterFactory(constructorConstructor,fieldNamingStrategy,excluder,jsonAdapterFactory,reflectionFilters));this.factories = Collections.unmodifiableList(factories);

首先我们根据这个列表顺序,结合 for (TypeAdapterFactory factory : factories) 分析得到,对于自己定义的实体类,使用的 TypeAdapterFactoryReflectiveTypeAdapterFactory,即是反射型的 TypeAdapterFactory

我们先来看 ReflectiveTypeAdapterFactory.create() 方法创建 TypeAdapter,这段代码的作用是根据 class的类型生成不同的TypeAdapter

public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {Class<? super T> raw = type.getRawType();if (!Object.class.isAssignableFrom(raw)) {return null; // it's a primitive!}// Don't allow using reflection on anonymous and local classes because synthetic fields for// captured enclosing values make this unreliableif (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {// This adapter just serializes and deserializes null, ignoring the actual values// This is done for backward compatibility; troubleshooting-wise it might be better to throw// exceptionsreturn new TypeAdapter<T>() {@Overridepublic T read(JsonReader in) throws IOException {in.skipValue();return null;}@Overridepublic void write(JsonWriter out, T value) throws IOException {out.nullValue();}@Overridepublic String toString() {return "AnonymousOrNonStaticLocalClassAdapter";}};}FilterResult filterResult =ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);if (filterResult == FilterResult.BLOCK_ALL) {throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for "+ raw+ ". Register a TypeAdapter for this type or adjust the access filter.");}boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will// always be false on JVMs that do not support records.if (ReflectionHelper.isRecord(raw)) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter =(TypeAdapter<T>)new RecordAdapter<>(raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);return adapter;}ObjectConstructor<T> constructor = constructorConstructor.get(type);return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
}

首先,对于 私有类 、 匿名内部类 、非静态内部类是不支持生成json的,此时会返回 nullTypeAdapter 或者 不生成 jsonTypeAdapter

Class<? super T> raw = type.getRawType();if (!Object.class.isAssignableFrom(raw)) {return null; // it's a primitive!
}// Don't allow using reflection on anonymous and local classes because synthetic fields for
// captured enclosing values make this unreliable
if (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {// This adapter just serializes and deserializes null, ignoring the actual values// This is done for backward compatibility; troubleshooting-wise it might be better to throw// exceptionsreturn new TypeAdapter<T>() {@Overridepublic T read(JsonReader in) throws IOException {in.skipValue();return null;}@Overridepublic void write(JsonWriter out, T value) throws IOException {out.nullValue();}@Overridepublic String toString() {return "AnonymousOrNonStaticLocalClassAdapter";}};
}

如果是 Java 14 之后 Record类,则使用 RecordAdapterTypeAdapter

// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
// always be false on JVMs that do not support records.
if (ReflectionHelper.isRecord(raw)) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter =(TypeAdapter<T>)new RecordAdapter<>(raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);return adapter;
}

而如果是普通的类型,则使用 FieldReflectionAdapterTypeAdapter

ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));

RecordAdapter 和 FieldReflectionAdapter

RecordAdapterFieldReflectionAdapter 都是 Adapter 的子类,其都没有覆写 writeread 的方法,因此我们直接看 Adapter 的的 read 方法。

@Override
public T read(JsonReader in) throws IOException {if (in.peek() == JsonToken.NULL) {in.nextNull();return null;}A accumulator = createAccumulator();Map<String, BoundField> deserializedFields = fieldsData.deserializedFields;try {in.beginObject();while (in.hasNext()) {String name = in.nextName();BoundField field = deserializedFields.get(name);if (field == null) {in.skipValue();} else {readField(accumulator, in, field);}}} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IllegalAccessException e) {throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);}in.endObject();return finalize(accumulator);
}

首先,会通过 A accumulator = createAccumulator(); 方法获取到一个指定类型的对象,从方法中可以看到,其实是调用 constructor.construct(); 反射调用构造方法生成指定类型的对象。

// FieldReflectionAdapter.java
@Override
T createAccumulator() {return constructor.construct();
}// RecordAdapter.java
@Override
Object[] createAccumulator() {return constructorArgsDefaults.clone();
}

在初始化的时候,会先调用 getBoundFields() 方法,通过反射的方式,获取指定类型已经声明了的成员。因此通过get 方法,去判断 jsonkey 是否存在,

BoundField field = deserializedFields.get(name);
if (field == null) {in.skipValue();
} else {readField(accumulator, in, field);
}

可以看 FieldReflectionAdapterreadField 方法 (Kotlin对象未使用Recond

@Override
void readField(T accumulator, JsonReader in, BoundField field)throws IllegalAccessException, IOException {field.readIntoField(in, accumulator);
}

继续往下看 BoundField.readIntoField()

@Override
void readIntoField(JsonReader reader, Object target)throws IOException, IllegalAccessException {Object fieldValue = typeAdapter.read(reader);if (fieldValue != null || !isPrimitive) {if (blockInaccessible) {checkAccessible(target, field);} else if (isStaticFinalField) {// Reflection does not permit setting value of `static final` field, even after calling// `setAccessible`// Handle this here to avoid causing IllegalAccessException when calling `Field.set`String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);}field.set(target, fieldValue);}
}

最后是通过反射的方式,field.set(target, fieldValue);json 中的 value 设置到指定对象中具体的成员中。

因此,如果实体类的成员在json中不存在,则不会有机会被赋值,其会保持一个默认值(对于对象来说即为空)

四、解决方法

Gson 解析 json 的源码中可以得出,由于使用了反射的方式,所以最后生成对象中可能会出现null,尤其是实体类中存在 json 没有的 key ,或者虽然 key 存在时但 value 就是null。因此,在设计json的实体类的时候,需要考虑成员是可空的情况,尽量使用可空类型,避免出现空指针异常。或者使用kotlinx.serialization 进行Kotlin JSON序列化,保证数据的可空安全性。

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

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

相关文章

ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(存储类外设之SPIFFS)

目录 ESP-ADF外设子系统深度解析&#xff1a;esp_peripherals组件架构与核心设计&#xff08;存储类外设之SPIFFS&#xff09;1. 简介2. 模块概述功能定义架构位置核心特性 SPIFFS外设SPIFFS外设概述SPIFFS外设层次架构图 SPIFFS外设API和数据结构外设层API公共API内部API内部数…

【Pandas】pandas DataFrame truediv

Pandas2.2 DataFrame Binary operator functions 方法描述DataFrame.add(other)用于执行 DataFrame 与另一个对象&#xff08;如 DataFrame、Series 或标量&#xff09;的逐元素加法操作DataFrame.add(other[, axis, level, fill_value])用于执行 DataFrame 与另一个对象&…

开发网页程序时预览时遇到跨域问题解决方法

CocosCreator 开发h5游戏要用接口、开发html程序网页程序在chrome中预览时都会遇到跨域问题,怎么办? 网上有很多方法,主要是通过服务器端去配置,但那个相对来说消弱安全问题,这个不建议,因为是开发,个人行业,我们知道问题所以,简单点就主要是通过chrome的参数来禁用: 关闭 Ch…

C语言main的参数;argc与argv

目录 前言 什么是命令行参数 argc与argv argc (Argument Count) argv (Argument Vector) 示例 前言 在C语言中&#xff0c;main函数的标准形式通常有两种&#xff1a; int main(void)int main(int argc, char *argv[]) 其中&#xff0c;argc 和 argv 是用于处理命令行参数…

实验一 进程控制实验

一、实验目的 1、掌握进程的概念&#xff0c;理解进程和程序的区别。 2、认识和了解并发执行的实质。 3、学习使用系统调用fork()创建新的子进程方法&#xff0c;理解进程树的概念。 4、学习使用系统调用wait()或waitpid()实现父子进程同步。 5、学习使用getpid()和getppi…

【Python Web开发】01-Socket网络编程01

文章目录 1.套接字(Socket)1.1 概念1.2 类型1.3 使用步骤 Python 的网络编程主要用于让不同的计算机或者程序之间进行数据交换和通信&#xff0c;就好像人与人之间打电话、发消息一样。 下面从几个关键方面通俗易懂地介绍一下&#xff1a; 1.套接字(Socket) 在 Python 网络编…

Git 配置 GPG 提交签名

使用 GPG 对 Git 提交进行签名&#xff0c;可以证明该提交确实是你本人提交的。这在团队协作和代码审核中非常有用&#xff0c;GitHub/GitLab 等平台也会显示 “Verified” 标签。 &#x1f9e9; 一、检查是否已安装 GPG gpg --version 如果未安装&#xff0c;可使用以下命令…

MySQL运维三部曲初级篇:从零开始打造稳定高效的数据库环境

文章目录 一、服务器选型——给数据库一个舒适的家二、系统调优——打造高性能跑道三、MySQL配置——让数据库火力全开四、监控体系——数据库的体检中心五、备份恢复——数据安全的最后防线六、主从复制——数据同步的艺术七、安全加固——守护数据长城 引言&#xff1a;从小白…

实践项目开发-hbmV4V20250407-跨平台开发框架深度解析与VSCode一站式开发实践

跨平台开发框架深度解析与VSCode一站式开发实践 在当今多端应用开发需求激增的背景下&#xff0c;跨平台开发框架成为了众多开发者的首选。本文将围绕React Native、Taro及其结合方案&#xff0c;以及Uni-app、MUI、Quasar等轻量级框架展开详细分析&#xff0c;并探讨如何在VS…

Android15沉浸式界面顶部有问题

Android15沉浸式界面顶部有问题 往往开发人员的手机没这么高级&#xff0c;客户或者老板的手机是Android15的。 我明明就设了状态栏透明&#xff0c;我的手机也没问题。但Android15是有问题的。 先看下有问题的界面&#xff1a; 解决方案&#xff1a; 处理1&#xff1a; if (…

uni-app 状态管理深度解析:Vuex 与全局方案实战指南

uni-app 状态管理深度解析&#xff1a;Vuex 与全局方案实战指南 一、Vuex 使用示例 1. 基础 Vuex 配置 1.1 项目结构 src/ ├── store/ │ ├── index.js # 主入口文件 │ └── modules/ │ └── counter.js # 计数器模块 └── main.js …

【STM32单片机】#11 I2C通信(软件读写)

主要参考学习资料&#xff1a; B站江协科技 STM32入门教程-2023版 细致讲解 中文字幕 开发资料下载链接&#xff1a;https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwddspb 单片机套装&#xff1a;STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协 实验&…

每天一道面试题@第一天

1&#xff1a;TCP和UDP的区别&#xff0c;TCP为什么是三次握手&#xff0c;不是两次&#xff1f; 因为TCP是全双工协议&#xff0c;区别在于TCP可靠&#xff0c;UDP不可靠&#xff0c;效率更高。 详解&#xff1a; TCP&#xff08;传输控制协议&#xff09;和 UDP&#xff08;…

一款强大的实时协作Markdown工具 | CodiMD 9.6K ⭐

CodiMD 介绍 CodiMD 是一个开源的实时协作 Markdown 笔记工具&#xff0c;它允许用户在任何平台上共同编辑 Markdown 文档。核心功能是实时协作&#xff0c;它允许多个用户同时编辑同一个文档&#xff0c;并实时看到彼此的更改。支持实时渲染预览&#xff0c;支持超多的富文本格…

若依如何切换 tab 不刷新

方法 如上图配置 菜单中选是否缓存&#xff1a;缓存 资料 前端手册 |RuoYi:

【浙江大学DeepSeek公开课】回望AI三大主义与加强通识教育

回望AI三大主义与加强通识教育 一、人工智能三大主义二、人工智能发展历程三、从 ChatGPT 到 DeepSeek四、人工智能通识教育五、人工智能的挑战与未来 一、人工智能三大主义 符号主义 &#xff1a;逻辑推理&#xff0c;将推理视为计算过程。如苏格拉底三段论&#xff0c;通过前…

边缘计算全透视:架构、应用与未来图景

边缘计算全透视&#xff1a;架构、应用与未来图景 一、产生背景二、本质三、特点&#xff08;一&#xff09;位置靠近数据源&#xff08;二&#xff09;分布式架构&#xff08;三&#xff09;实时性要求高 四、关键技术&#xff08;一&#xff09;硬件技术&#xff08;二&#…

C++——多态、抽象类和接口

目录 多态的基本概念 如何实现多态 在C中&#xff0c;派生类对象可以被当作基类对象使用 编程示例 关键概念总结 抽象类 一、抽象类的定义 基本语法 二、抽象类的核心特性 1. 不能直接实例化 2. 派生类必须实现所有纯虚函数才能成为具体类 3. 可以包含普通成员函数和…

初级达梦dba的技能水准

在x86环境&#xff08;windows、linux&#xff09;安装单机软件&#xff0c;安装客户端创建过至少20套数据库&#xff0c;优化参数并更新过正式许可会用逻辑导出导入以及dmrman备份了解manager工具的使用配置sqllog日志&#xff0c;并能解释输出内容能够分析因磁盘空间不足、内…

监控页面卡顿PerformanceObserver

监控页面卡顿PerformanceObserver 性能观察器掘金 const observer new PerformanceObserver((list) > {}); observer.observe({entryTypes: [longtask], })