探寻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 = ""
)
nonExistParam 是 json 结构中不存在的 key,json 如下
{"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。因此 TypeAdapterFactory 和 TypeAdapter 互相配合,可以生成解析和生成json的具体实现方法。
通过一个类型获取 TypeAdapter 的 Gson.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) 分析得到,对于自己定义的实体类,使用的 TypeAdapterFactory 为 ReflectiveTypeAdapterFactory,即是反射型的 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的,此时会返回 null 的 TypeAdapter 或者 不生成 json 的 TypeAdapter
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类,则使用 RecordAdapter 的 TypeAdapter
// 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;
}
而如果是普通的类型,则使用 FieldReflectionAdapter 的 TypeAdapter
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
RecordAdapter 和 FieldReflectionAdapter
RecordAdapter 和 FieldReflectionAdapter 都是 Adapter 的子类,其都没有覆写 write 和 read 的方法,因此我们直接看 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 方法,去判断 json 的 key 是否存在,
BoundField field = deserializedFields.get(name);
if (field == null) {in.skipValue();
} else {readField(accumulator, in, field);
}
可以看 FieldReflectionAdapter 的 readField 方法 (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序列化,保证数据的可空安全性。
相关文章:
探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因
文章目录 一、问题背景二、问题原因三、问题探析Kotlin空指针校验Gson.fromJson(String json, Class<T> classOfT)TypeTokenGson.fromJson(JsonReader reader, TypeToken<T> typeOfT)TypeAdapter 和 TypeAdapterFactoryReflectiveTypeAdapterFactoryRecordAdapter …...
冰川流域提取分析——ArcGIS pro
一、河网提取和流域提取视频详细GIS小熊 || 6分钟学会水文分析—河网提取(以宜宾市为例)_哔哩哔哩_bilibili 首先你要生成研究区域DEM,然后依次是填洼→流向→流量→栅格计算器→河网分级→栅格河网矢量化(得到河网.shpÿ…...
wordpress 垂直越权(CVE=2021-21389)漏洞复现详细教程
关于本地化搭建vulfocus靶场的师傅可以参考我置顶文章 KALI搭建log4j2靶场及漏洞复现全流程-CSDN博客https://blog.csdn.net/2301_78255681/article/details/147286844 描述: BuddyPress 是一个用于构建社区站点的开源 WordPress 插件。在 7.2.1 之前的 5.0.0 版本的 BuddyP…...
MySQL 线上大表 DDL 如何避免锁表(pt-online-schema-change)
文章目录 1、锁表问题2、pt-online-schema-change 原理3、pt-online-schema-change 实战3.1、准备数据3.2、安装工具3.3、模拟锁表3.4、解决锁表 1、锁表问题 在系统研发过程中,随着业务需求千变万化,避免不了调整线上MySQL DDL数据表的操作,…...
uni-app 状态管理深度解析:Vuex 与全局方案实战指南
uni-app 状态管理深度解析:Vuex 与全局方案实战指南 一、Vuex 使用示例 1. 基础 Vuex 配置 1.1 项目结构 src/ ├── store/ │ ├── index.js # 主入口文件 │ └── modules/ │ └── counter.js # 计数器模块 └── main.js …...
剑指offer经典题目(五)
目录 栈相关 二叉树相关 栈相关 题目一:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。OJ地址 图示如下。 主要思想:我们…...
3、排序算法1---按考研大纲做的
一、插入排序 1、直接插入排序 推荐先看这个视频 1.1、原理 第一步,索引0的位置是有序区(有序区就是有序的部分,刚开始就只有第一个数据是有序的)。第二步,将第2个位置到最后一个位置的元素,依次进行排…...
llama-webui docker实现界面部署
1. 启动ollama服务 [nlp server]$ ollama serve 2025/04/21 14:18:23 routes.go:1007: INFO server config env"map[OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST: OLLAMA_KEEP_ALIVE:24h OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:4 OLLAMA_MAX_…...
jinjia2将后端传至前端的字典变量转换为JS变量
后端 country_dict {AE: .amazon.ae, AU: .amazon.com.au} 前端 const country_list JSON.parse({{ country_list | tojson | safe }});...
如何深入理解引用监视器,安全标识以及访问控制模型与资产安全之间的关系
一、核心概念总结 安全标识(策略决策的 “信息载体) 是主体(如用户、进程)和客体(如文件、数据库、设备)的安全属性,用于标记其安全等级、权限、访问能力或受保护级别,即用于标识其安全等级、权限范围或约束…...
Linux的Socket开发补充
是listen函数阻塞等待连接,还是accept函数阻塞等待连接? 这两个函数的名字,听起来像listen一直在阻塞监听,有连接了就accept,但其实不是的。 调用listen()后,程序会立即返回,继续执行后续代码&a…...
Flutter异常Couldn‘t find dynamic library in default locations
Flutter项目在Windows系统使用ffigen生成代码时报下面的错误: [SEVERE] : Couldnt find dynamic library in default locations. [SEVERE] : Please supply one or more path/to/llvm in ffigens config under the key llvm-path. Unhandled exception: Exception: …...
Spring-AOP分析
Spring分析-AOP 1.案例引入 在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我们了解到了IOC容器的创建过程,在文末也提到了AOP相关,但是没有作细致分析,这篇文章就结合示例&am…...
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
在AI技术快速发展的今天,编程方式正在经历一场革命性的变革。从传统的"人写代码"到"AI辅助编程",再到"AI自主编程",开发效率得到了质的提升。Cline作为一款基于VSCode的AI编程助手,通过其独特的pro…...
【专业解读:Semantic Kernel(SK)】大语言模型与传统编程的桥梁
目录 Start:什么是Semantic Kernel? 一、Semantic Kernel的本质:AI时代的操作系统内核 1.1 重新定义LLM的应用边界 1.2 技术定位对比 二、SK框架的六大核心组件与技术实现 2.1 内核(Kernel):智能任务调度中心 2…...
PHP 8 中的 Swow:高性能纯协程网络通信引擎
一、什么是 Swow? Swow 是一个高性能的纯协程网络通信引擎,专为 PHP 设计。它结合了最小化的 C 核心和 PHP 代码,旨在提供高性能的网络编程支持。Swow 的核心目标是释放 PHP 在高并发场景下的真正潜力,同时保持代码的简洁和易用性…...
你学会了些什么211201?--http基础知识
概念 HTTP–Hyper Text Transfer Protocol,超文本传输协议;是一种建立在TCP上的无状态连接(短连接)。 整个基本的工作流程是:客户端发送一个HTTP请求(Request ),这个请求说明了客户端…...
每天学一个 Linux 命令(29):tail
可访问网站查看,视觉品味拉满: http://www.616vip.cn/29/index.html tail 命令用于显示文件的末尾内容,默认显示最后 10 行。它常用于实时监控日志文件或查看文件的尾部数据。以下是详细说明和示例: 命令格式 tail [选项] [文件...]常用选项 选项描述-n <NUM> …...
【形式化验证基础】活跃属性Liveness Property和安全性质(Safety Property)介绍
文章目录 一、Liveness Property1、概念介绍2、形式化定义二、Safety Property1. 定义回顾2. 核心概念解析3. 为什么强调“有限前缀”4. 示例说明4.1 示例1:交通信号灯系统4.2 示例2:银行账户管理系统5. 实际应用的意义三. 总结一、Liveness Property 1、概念介绍 在系统的…...
技工院校无人机专业工学一体化人才培养方案
随着无人机技术在农业植保、地理测绘、应急救援等领域的深度应用,行业复合型人才缺口持续扩大。技工院校作为技能型人才培养主阵地,亟需构建与行业发展同步的无人机专业人才培养体系。本文基于"工学一体化"教育理念,从课程体系、实…...
PI0 Openpi 部署(仅测试虚拟环境)
https://github.com/Physical-Intelligence/openpi/tree/main 我使用4070tisuper, 14900k,完全使用官方默认设置,没有出现其他问题。 目前只对examples/aloha_sim进行测试,使用docker进行部署, 默认使用pi0_aloha_sim模型(但是文档上没找到对应的&…...
计算机视觉——利用AI幻觉检测图像是否是生成式算生成的图像
概述 俄罗斯的新研究提出了一种非常规方法,用于检测不真实的AI生成图像——不是通过提高大型视觉-语言模型(LVLMs)的准确性,而是故意利用它们的幻觉倾向。 这种新方法使用LVLMs提取图像的多个“原子事实”,然后应用自…...
性能测试工具和JMeter功能概要
主流性能测试工具 LoadRunner JMeter [本阶段学习] 1.1 LoadRunner HP LoadRunner是一种工业级标准性能测试负载工具,可以模拟上万用户实施测试,并在测试时可实时检测应用服务器及服务器硬件各种数据,来确认和查找存在的瓶颈支持多协议&am…...
《理解 Java 泛型中的通配符:extends 与 super 的使用场景》
大家好呀!👋 今天我们要聊一个让很多Java初学者头疼的话题——泛型通配符。别担心,我会用最通俗易懂的方式,带你彻底搞懂这个看似复杂的概念。准备好了吗?Let’s go! 🚀 一、为什么我们需要泛型通配符&…...
C#学习第17天:序列化和反序列化
什么是序列化? 定义:序列化是指把对象转换为一种可以轻松存储或传输的格式,如JSON、XML或二进制格式。这个过程需要捕获对象的类型信息和数据内容。用途:使得对象可以持久化到文件、发送至网络、或存储在数据库中。 什么是反序列…...
FlaskRestfulAPI接口的初步认识
FlaskRestfulAPI 介绍 记录学习 Flask Restful API 开发的过程 项目来源:【Flask Restful API教程-01.Restful API介绍】 我的代码仓库:https://gitee.com/giteechaozhi/flask-restful-api.git 后端API接口实现功能:数据库访问控制…...
CSS预处理工具有哪些?分享主流产品
目前主流的CSS预处理工具包括:Sass、Less、Stylus、PostCSS等。其中,Sass是全球使用最广泛的CSS预处理工具之一,以强大的功能、灵活的扩展性以及完善的社区生态闻名。Sass通过增加变量、嵌套、混合宏(mixin)等功能&…...
微信小程序中,将搜索组件获取的值传递给父页面(如 index 页面)可以通过 自定义事件 或 页面引用 实现
将搜索组件获取的值传递给父页面(如 index 页面)可以通过 自定义事件 或 页面引用 实现 方法 1:自定义事件(推荐) 步骤 1:搜索组件内触发事件 在搜索组件的 JS 中,当获取到搜索值时,…...
深度学习预训练和微调
目录 1. 预训练(Pre-training)是什么? 2. 微调(Fine-tuning)是什么? 3. 预训练和微调的对象 4. 特征提取如何实现? 预训练阶段: 微调阶段: 5. 这样做的作用和意义 …...
AI 速读 SpecReason:让思考又快又准!
在大模型推理的世界里,速度与精度往往难以兼得。但今天要介绍的这篇论文带来了名为SpecReason的创新系统,它打破常规,能让大模型推理既快速又准确,大幅提升性能。想知道它是如何做到的吗?快来一探究竟! 论…...
