【Java】泛型详解

【Java】泛型详解

Java 泛型详解

参数化类型、类型擦除、边界与通配符完整教程

目录


一、泛型概述

1.1 什么是泛型

泛型(Generics)是JDK 5引入的特性,实现了参数化类型,使代码可以适用于多种类型。

核心优势:

  • ✅ 类型安全:编译期检查类型错误
  • ✅ 消除强制转换:自动类型转换
  • ✅ 代码复用:一套代码适用多种类型
  • ✅ 更好的可读性:明确表达意图

1.2 为什么需要泛型

没有泛型的问题:

// JDK 5之前:使用Object存储classBox{privateObject obj;publicvoidset(Object obj){this.obj = obj;}publicObjectget(){return obj;}}// 使用时的问题Box box =newBox(); box.set("Hello");String str =(String) box.get();// 需要强制转换 box.set(123);String error =(String) box.get();// 运行时错误!ClassCastException

使用泛型的解决方案:

// JDK 5之后:使用泛型classBox<T>{privateT obj;publicvoidset(T obj){this.obj = obj;}publicTget(){return obj;}}// 使用时的优势Box<String> box =newBox<>(); box.set("Hello");String str = box.get();// 无需强制转换// box.set(123); // 编译错误!类型安全

二、泛型类

2.1 基本语法

// 单个类型参数classGenericClass<T>{privateT data;publicGenericClass(T data){this.data = data;}publicTgetData(){return data;}}// 多个类型参数classPair<K,V>{privateK key;privateV value;publicPair(K key,V value){this.key = key;this.value = value;}publicKgetKey(){return key;}publicVgetValue(){return value;}}

2.2 常用类型参数命名

符号含义示例
EElement(元素)List
KKey(键)Map<K, V>
VValue(值)Map<K, V>
TType(类型)Class
NNumber(数字)-
?通配符List<?>

2.3 实战示例:元组类

// 二元组publicclassTuple2<A,B>{publicfinalA first;publicfinalB second;publicTuple2(A first,B second){this.first = first;this.second = second;}@OverridepublicStringtoString(){return"("+ first +", "+ second +")";}}// 三元组publicclassTuple3<A,B,C>extendsTuple2<A,B>{publicfinalC third;publicTuple3(A first,B second,C third){super(first, second);this.third = third;}@OverridepublicStringtoString(){return"("+ first +", "+ second +", "+ third +")";}}// 使用示例publicclassTupleExample{publicstaticvoidmain(String[] args){// 返回多个值Tuple2<String,Integer> person =newTuple2<>("张三",25);System.out.println(person);// (张三, 25)// 不同类型组合Tuple3<String,Integer,Boolean> data =newTuple3<>("Java",95,true);System.out.println(data);// (Java, 95, true)}}

三、泛型方法

3.1 基本语法

泛型方法的类型参数放在返回值之前:

publicclassGenericMethod{// 泛型方法public<T>voidprint(T item){System.out.println(item.getClass().getName()+": "+ item);}// 多个类型参数public<K,V>voidprintPair(K key,V value){System.out.println(key +" = "+ value);}// 返回泛型类型public<T>TgetFirst(T[] array){return array.length >0? array[0]:null;}}

3.2 类型推断

publicclassTypeInference{publicstatic<T>Tidentity(T item){return item;}publicstaticvoidmain(String[] args){// 显式指定类型String s1 =GenericMethod.<String>identity("Hello");// 类型推断(推荐)String s2 =identity("Hello");// 编译器自动推断为StringInteger i =identity(123);// 自动推断为Integer}}

3.3 实战示例:通用工具方法

importjava.util.*;importjava.util.function.Function;publicclassGenericUtils{// 交换数组元素publicstatic<T>voidswap(T[] array,int i,int j){T temp = array[i]; array[i]= array[j]; array[j]= temp;}// 查找最大值publicstatic<TextendsComparable<T>>Tmax(T[] array){if(array ==null|| array.length ==0){returnnull;}T max = array[0];for(int i =1; i < array.length; i++){if(array[i].compareTo(max)>0){ max = array[i];}}return max;}// 转换Listpublicstatic<T,R>List<R>map(List<T> list,Function<T,R> mapper){List<R> result =newArrayList<>();for(T item : list){ result.add(mapper.apply(item));}return result;}publicstaticvoidmain(String[] args){// 交换示例String[] names ={"Alice","Bob","Charlie"};swap(names,0,2);System.out.println(Arrays.toString(names));// [Charlie, Bob, Alice]// 查找最大值Integer[] numbers ={3,7,2,9,1};System.out.println("最大值: "+max(numbers));// 9// 转换ListList<String> words =Arrays.asList("hello","world");List<Integer> lengths =map(words,String::length);System.out.println(lengths);// [5, 5]}}

四、类型擦除

4.1 什么是类型擦除

Java泛型采用**类型擦除(Type Erasure)**机制实现,这是Java泛型与C++模板的本质区别。编译器在编译时检查类型安全,但在生成字节码时会擦除所有泛型类型信息。

擦除规则:

  • 无边界类型参数(<T>)→ 擦除为 Object
  • 有边界类型参数(<T extends Number>)→ 擦除为第一个边界类型 Number
  • List<String>List<Integer> → 运行时都是原始类型 List
  • 编译器会自动插入类型转换代码,保证类型安全
importjava.util.*;publicclassErasureExample{publicstaticvoidmain(String[] args){List<String> list1 =newArrayList<>();List<Integer> list2 =newArrayList<>();// 运行时类型相同System.out.println(list1.getClass()== list2.getClass());// trueSystem.out.println(list1.getClass());// class java.util.ArrayList// 泛型信息在运行时不存在System.out.println(list1.getClass().getTypeParameters().length);// 1System.out.println(list1.getClass().getTypeParameters()[0].getName());// E}}

4.2 擦除的影响

类型擦除带来的限制:

❌ 限制1:无法创建泛型数组

classContainer<T>{// ❌ 编译错误:Cannot create a generic array of T// private T[] array = new T[10];}

❌ 限制2:无法使用 instanceof 检查泛型类型

classContainer<T>{// ❌ 编译错误:Cannot perform instanceof check against type parameter T// public boolean check(Object obj) {// return obj instanceof T;// }}

❌ 限制3:无法创建泛型类型实例

classContainer<T>{// ❌ 编译错误:Cannot instantiate the type T// public T create() {// return new T();// }}

❌ 限制4:静态字段不能使用类型参数

classContainer<T>{// ❌ 编译错误:Cannot make a static reference to the non-static type T// private static T staticItem;}

✅ 解决方案:传递 Class 对象

通过传递 Class<T> 对象,可以在运行时获取类型信息:

importjava.lang.reflect.Array;classContainer<T>{privateClass<T> type;publicContainer(Class<T> type){this.type = type;}// ✅ 使用反射创建数组@SuppressWarnings("unchecked")publicT[]createArray(int size){return(T[])Array.newInstance(type, size);}// ✅ 使用 Class.isInstance() 检查类型publicbooleancheck(Object obj){return type.isInstance(obj);}// ✅ 使用反射创建实例publicTcreate()throwsException{return type.getDeclaredConstructor().newInstance();}}// 使用示例Container<String> container =newContainer<>(String.class);String[] array = container.createArray(5);System.out.println(container.check("Hello"));// trueSystem.out.println(container.check(123));// falseString instance = container.create();// 创建新的String实例

4.3 边界与擦除的关系

类型参数的边界决定了擦除后的类型:

// 示例1:无边界 → 擦除为 ObjectclassBox<T>{privateT item;// 擦除后:private Object item;publicvoidset(T item){this.item = item;}// 擦除后:public void set(Object item) { ... }}// 示例2:单个边界 → 擦除为边界类型classNumberBox<TextendsNumber>{privateT item;// 擦除后:private Number item;publicvoidprocess(){// ✅ 可以调用 Number 的方法double value = item.doubleValue();int intValue = item.intValue();}}// 示例3:多个边界 → 擦除为第一个边界interfaceReadable{Stringread();}interfaceWritable{voidwrite(String data);}classDataBox<TextendsNumber&Readable&Writable>{privateT item;// 擦除后:private Number item;// 注意:第一个边界必须是类(如果有类的话),接口放在后面publicvoidprocess(){// ✅ 可以调用 Number 的方法 item.doubleValue();// ✅ 也可以调用接口方法(编译器会插入强制转换) item.read(); item.write("data");}}

重要规则:

  • 多个边界时,类必须放在第一位
  • 正确:<T extends Number & Serializable>
  • 错误:<T extends Serializable & Number>(编译错误)

五、边界与通配符

5.1 类型参数边界(extends)

使用 extends 关键字限制类型参数的范围,使其必须是某个类的子类或实现某个接口。

// 上界:T 必须是 Number 或其子类classNumberProcessor<TextendsNumber>{privateT number;publicNumberProcessor(T number){this.number = number;}publicdoublegetDoubleValue(){return number.doubleValue();// 可以调用 Number 的方法}publicintcompare(T other){returnDouble.compare(number.doubleValue(), other.doubleValue());}}// 使用示例NumberProcessor<Integer> p1 =newNumberProcessor<>(100);NumberProcessor<Double> p2 =newNumberProcessor<>(3.14);System.out.println(p1.getDoubleValue());// 100.0System.out.println(p2.compare(2.5));// 1// ❌ 编译错误:String 不是 Number 的子类// NumberProcessor<String> p3 = new NumberProcessor<>("error");

5.2 通配符详解

通配符(Wildcard)用于表示未知类型,主要用于方法参数,提供更灵活的类型匹配。

通配符含义读操作写操作使用场景
<?>任意类型✅ 只能读取为 Object❌ 不能写入(null除外)只读取,不关心具体类型
<? extends T>T 或 T 的子类✅ 可以读取为 T 类型❌ 不能写入(null除外)从集合中读取数据(生产者)
<? super T>T 或 T 的父类❌ 只能读取为 Object✅ 可以写入 T 及其子类向集合中写入数据(消费者)

PECS 原则:Producer Extends, Consumer Super

  • 如果你需要从集合中读取数据(生产者),使用 <? extends T>
  • 如果你需要向集合中写入数据(消费者),使用 <? super T>
  • 如果既要读又要写,不要使用通配符
importjava.util.*;publicclassWildcardExample{// 示例1:使用 extends 读取数据(生产者)publicstaticdoublesum(List<?extendsNumber> list){double total =0;for(Number num : list){ total += num.doubleValue();// ✅ 可以读取为 Number}// list.add(1); // ❌ 编译错误:不能写入return total;}// 示例2:使用 super 写入数据(消费者)publicstaticvoidaddNumbers(List<?superInteger> list){ list.add(1);// ✅ 可以写入 Integer list.add(2); list.add(3);// Integer num = list.get(0); // ❌ 编译错误:只能读取为 ObjectObject obj = list.get(0);// ✅ 可以读取为 Object}// 示例3:无界通配符publicstaticvoidprintList(List<?> list){for(Object obj : list){// ✅ 只能读取为 ObjectSystem.out.println(obj);}// list.add("test"); // ❌ 编译错误:不能写入(null 除外) list.add(null);// ✅ 可以写入 null}publicstaticvoidmain(String[] args){// extends 示例:可以接受 Number 的任何子类List<Integer> integers =Arrays.asList(1,2,3);List<Double> doubles =Arrays.asList(1.1,2.2,3.3);System.out.println("整数求和: "+sum(integers));// 6.0System.out.println("浮点数求和: "+sum(doubles));// 6.6// super 示例:可以接受 Integer 的任何父类List<Number> numbers =newArrayList<>();List<Object> objects =newArrayList<>();addNumbers(numbers);// ✅ Number 是 Integer 的父类addNumbers(objects);// ✅ Object 是 Integer 的父类System.out.println("numbers: "+ numbers);// [1, 2, 3]System.out.println("objects: "+ objects);// [1, 2, 3]// 无界通配符示例printList(integers);printList(Arrays.asList("A","B","C"));}}

5.3 多重边界

一个类型参数可以有多个边界,用 & 连接。类型必须同时满足所有边界条件。

interfaceFlyable{voidfly();}interfaceSwimmable{voidswim();}// 多重边界:T 必须同时实现 Flyable 和 SwimmableclassAnimal<TextendsFlyable&Swimmable>{privateT creature;publicAnimal(T creature){this.creature = creature;}publicvoidmove(){ creature.fly();// ✅ 可以调用 Flyable 的方法 creature.swim();// ✅ 可以调用 Swimmable 的方法}}// 实现类必须同时实现两个接口classDuckimplementsFlyable,Swimmable{@Overridepublicvoidfly(){System.out.println("鸭子飞行");}@Overridepublicvoidswim(){System.out.println("鸭子游泳");}}classFishimplementsSwimmable{@Overridepublicvoidswim(){System.out.println("鱼游泳");}}// 使用示例Animal<Duck> duck =newAnimal<>(newDuck()); duck.move();// 输出:鸭子飞行 \n 鸭子游泳// ❌ 编译错误:Fish 只实现了 Swimmable,没有实现 Flyable// Animal<Fish> fish = new Animal<>(new Fish());

多重边界的规则:

  1. 最多只能有一个类边界,且必须放在第一位
  2. 可以有多个接口边界
  3. 格式:<T extends 类 & 接口1 & 接口2 & ...>
// ✅ 正确:类在前,接口在后classExample<TextendsNumber&Comparable<T>&Serializable>{}// ❌ 错误:接口不能放在类前面// class Example<T extends Comparable<T> & Number> {}// ❌ 错误:不能有多个类边界// class Example<T extends Number & Integer> {}

六、常见问题与陷阱

6.1 基本类型不能作为类型参数

Java 泛型不支持基本类型(primitive types),必须使用对应的包装类。

// ❌ 错误:不能使用基本类型// List<int> list = new ArrayList<>();// Map<double, boolean> map = new HashMap<>();// ✅ 正确:使用包装类List<Integer> list =newArrayList<>();Map<Double,Boolean> map =newHashMap<>();// 自动装箱和拆箱 list.add(1);// 自动装箱:int → Integerint value = list.get(0);// 自动拆箱:Integer → int// 注意:频繁装箱拆箱会影响性能List<Integer> numbers =newArrayList<>();for(int i =0; i <1000; i++){ numbers.add(i);// 每次循环都会装箱,性能开销}

性能优化建议:

  • 对于大量基本类型数据,考虑使用数组 int[] 而不是 List<Integer>
  • 或使用第三方库如 Trove、FastUtil 提供的基本类型集合

6.2 不能实现同一泛型接口的不同变体

由于类型擦除,一个类不能同时实现同一个泛型接口的不同参数化版本。

interfaceProcessor<T>{voidprocess(T item);}// ✅ 正确:实现单一类型classStringProcessorimplementsProcessor<String>{@Overridepublicvoidprocess(String item){System.out.println("处理字符串: "+ item);}}// ❌ 错误:不能同时实现 Processor<String> 和 Processor<Integer>// 因为类型擦除后,两个接口都变成了 Processor,方法签名冲突// class MultiProcessor implements Processor<String>, Processor<Integer> {// @Override// public void process(String item) { } // 擦除后:process(Object)//// @Override// public void process(Integer item) { } // 擦除后:process(Object) - 冲突!// }// ✅ 解决方案:使用不同的方法名或包装类classMultiProcessor{publicvoidprocessString(String item){System.out.println("处理字符串: "+ item);}publicvoidprocessInteger(Integer item){System.out.println("处理整数: "+ item);}}

6.3 不能创建泛型数组

由于类型擦除,Java 不允许直接创建泛型数组。

importjava.lang.reflect.Array;importjava.util.*;// ❌ 错误:不能创建泛型数组// T[] array = new T[10];// List<String>[] lists = new ArrayList<String>[10]; // 编译错误// List<Integer>[] intLists = new List<Integer>[5]; // 编译错误// ✅ 解决方案1:使用 ArrayList 代替数组List<List<String>> lists =newArrayList<>(); lists.add(newArrayList<>()); lists.add(newArrayList<>());// ✅ 解决方案2:使用原始类型数组(会有警告)@SuppressWarnings("unchecked")List<String>[] lists2 =(List<String>[])newList[10];for(int i =0; i < lists2.length; i++){ lists2[i]=newArrayList<>();}// ✅ 解决方案3:使用 Array.newInstance()(需要 Class 对象)classGenericArray<T>{privateT[] array;@SuppressWarnings("unchecked")publicGenericArray(Class<T> type,int size){ array =(T[])Array.newInstance(type, size);}publicvoidset(int index,T value){ array[index]= value;}publicTget(int index){return array[index];}}// 使用示例GenericArray<String> stringArray =newGenericArray<>(String.class,10); stringArray.set(0,"Hello");System.out.println(stringArray.get(0));// Hello

为什么不能创建泛型数组?

  • 数组在运行时需要知道元素的确切类型(用于类型检查)
  • 泛型类型在运行时被擦除,无法提供这个信息
  • 如果允许创建泛型数组,会破坏类型安全

6.4 泛型方法重载问题

由于类型擦除,某些看似合理的方法重载实际上会导致编译错误。

importjava.util.*;// ❌ 错误:擦除后方法签名相同// class Overload {// void method(List<String> list) { } // 擦除后:method(List)// void method(List<Integer> list) { } // 擦除后:method(List) - 冲突!// }// ✅ 解决方案1:使用不同的方法名classOverload{voidprocessStrings(List<String> list){for(String s : list){System.out.println("字符串: "+ s);}}voidprocessIntegers(List<Integer> list){for(Integer i : list){System.out.println("整数: "+ i);}}}// ✅ 解决方案2:使用泛型方法classGenericOverload{<T>voidprocess(List<T> list,Class<T> type){System.out.println("处理类型: "+ type.getSimpleName());for(T item : list){System.out.println(item);}}}// 使用示例GenericOverload processor =newGenericOverload(); processor.process(Arrays.asList("A","B"),String.class); processor.process(Arrays.asList(1,2,3),Integer.class);

6.5 原始类型的危险

使用原始类型(Raw Type)会绕过泛型的类型检查,导致运行时错误。

importjava.util.*;// ❌ 危险:使用原始类型List rawList =newArrayList();// 原始类型,没有类型参数 rawList.add("String"); rawList.add(123); rawList.add(newObject());// 运行时错误!String s =(String) rawList.get(1);// ClassCastException// ✅ 正确:始终使用泛型List<String> stringList =newArrayList<>(); stringList.add("String");// stringList.add(123); // 编译错误,类型安全// ⚠️ 警告:原始类型与泛型混用List<String> genericList =newArrayList<>();List rawList2 = genericList;// 警告:unchecked conversion rawList2.add(123);// 编译通过,但破坏了类型安全String str = genericList.get(0);// 可能导致 ClassCastException

最佳实践:

  • 永远不要使用原始类型(除非与遗留代码交互)
  • 如果不确定类型,使用 List<?> 而不是 List
  • 使用 @SuppressWarnings("unchecked") 时要格外小心

七、实战示例

7.1 泛型栈

importjava.util.*;publicclassGenericStack<T>{privateList<T> items =newArrayList<>();publicvoidpush(T item){ items.add(item);}publicTpop(){if(isEmpty()){thrownewEmptyStackException();}return items.remove(items.size()-1);}publicTpeek(){if(isEmpty()){thrownewEmptyStackException();}return items.get(items.size()-1);}publicbooleanisEmpty(){return items.isEmpty();}publicintsize(){return items.size();}}// 使用GenericStack<String> stack =newGenericStack<>(); stack.push("A"); stack.push("B");System.out.println(stack.pop());// B

7.2 泛型缓存

importjava.util.*;publicclassCache<K,V>{privateMap<K,V> cache =newHashMap<>();privateint maxSize;publicCache(int maxSize){this.maxSize = maxSize;}publicvoidput(K key,V value){if(cache.size()>= maxSize){// 简单的LRU:删除第一个K firstKey = cache.keySet().iterator().next(); cache.remove(firstKey);} cache.put(key, value);}publicVget(K key){return cache.get(key);}publicbooleancontainsKey(K key){return cache.containsKey(key);}}// 使用Cache<String,User> userCache =newCache<>(100); userCache.put("user1",newUser("张三"));User user = userCache.get("user1");

7.3 泛型构建器

importjava.util.function.BiConsumer;publicclassBuilder<T>{privateT object;publicBuilder(Class<T> clazz)throwsException{ object = clazz.getDeclaredConstructor().newInstance();}public<V>Builder<T>set(BiConsumer<T,V> setter,V value){ setter.accept(object, value);returnthis;}publicTbuild(){return object;}}// 使用classPerson{privateString name;privateint age;publicvoidsetName(String name){this.name = name;}publicvoidsetAge(int age){this.age = age;}}Person person =newBuilder<>(Person.class).set(Person::setName,"张三").set(Person::setAge,25).build();

快速参考

泛型语法速查表

语法说明示例使用场景
<T>类型参数class Box<T>定义泛型类或方法
<T extends X>上界约束<T extends Number>限制类型必须是 X 或其子类
<T super X>下界约束<T super Integer>限制类型必须是 X 或其父类
<?>无界通配符List<?>只读取,不关心具体类型
<? extends X>上界通配符List<? extends Number>读取数据(生产者)
<? super X>下界通配符List<? super Integer>写入数据(消费者)
<T, U>多个类型参数Map<K, V>需要多个类型参数
<T extends A & B>多重边界<T extends Number & Comparable<T>>类型必须满足多个约束

常用类型参数命名约定

符号含义常见用法
EElement(元素)List<E>, Set<E>
KKey(键)Map<K, V>
VValue(值)Map<K, V>
TType(类型)Class<T>, 通用类型
NNumber(数字)数值类型
RResult(结果)返回类型
S, U第2、3个类型多个类型参数时使用

核心原则与最佳实践

PECS 原则(Producer Extends, Consumer Super)

// Producer(生产者):从集合中读取数据 → 使用 extendspublicstaticdoublesum(List<?extendsNumber> numbers){double total =0;for(Number n : numbers){ total += n.doubleValue();// ✅ 可以读取}return total;}// Consumer(消费者):向集合中写入数据 → 使用 superpublicstaticvoidaddIntegers(List<?superInteger> list){ list.add(1);// ✅ 可以写入 list.add(2);}

使用建议

  • ✅ 优先使用泛型而非原始类型
  • ✅ 优先使用泛型方法而非泛型类(更灵活)
  • ✅ 使用 PECS 原则选择通配符
  • ✅ 避免使用原始类型(Raw Type)
  • ✅ 传递 Class<T> 对象解决类型擦除问题
  • ✅ 使用 @SuppressWarnings("unchecked") 时要谨慎,并添加注释说明原因
  • ❌ 不要忽略编译器的泛型警告
  • ❌ 不要在新代码中使用原始类型

常见错误与解决方案

问题原因解决方案
不能创建泛型数组类型擦除使用 ArrayListArray.newInstance()
不能使用基本类型泛型只支持引用类型使用包装类(Integer, Double 等)
不能 instanceof 泛型类型擦除传递 Class<T> 对象
方法重载冲突擦除后签名相同使用不同方法名或泛型方法
静态字段不能用类型参数类型参数属于实例使用静态泛型方法

参考资源

Read more

【优选算法】双指针算法:专题二

【优选算法】双指针算法:专题二

目录 【611.有效三角形个数】 1、题目描述 2、实现核心及思路 解题步骤: 思路可视化: 代码实现: 【179.查找总价格为目标值的两个商品】 1、题目描述: 2、实现核心及思路: 代码实现: 【15.三数之和】 1、题目描述: 2、实现核心及思路: 解题步骤: 思路可视化: 代码实现: 【18.四数之和】 1、题目描述: 编辑2、实现核心即思路: 解题步骤: 代码实现: 【611.有效三角形个数】 1、题目描述 2、实现核心及思路 构成三角形的条件:设三角形三边长分别为a(最长边),b(最短边),c。 则有 a + b >

By Ne0inhk

MinIO介绍(分布式对象存储系统 object storage)传统文件名和目录结构,大文件存储为主,Amazon S3(AWS S3)替代方案(MinIO和SeaweedFS区别)mc命令行

https://sealos.run/docs/guides/object-storage 文章目录 * MinIO:轻量、高性能的开源对象存储,打造你的私有云存储基石 * 🌟 什么是 MinIO? * 🔑 为什么开发者爱它?五大核心优势 * ✅ 100% S3 兼容,无缝迁移 * ⚡ 极致性能,为高并发而生 * 🛡️ 企业级可靠性与安全 * 🐳 云原生友好,部署如呼吸般简单 * 🌍 活跃生态与全球社区 * 🚀 典型应用场景 * 💡 一个小实践:用 mc 上传你的第一张图片 * ❓ 常见疑问解答 * 🌱 结语:简单,是终极的复杂 * MinIO vs SeaweedFS:核心差异解析 * 🏗️ 架构设计差异 * 📦 小文件处理能力 * 🔌 API与接口支持 * 🔄 数据存储策略 * 🌐 扩展性与部署 * ☁️ 云集成能力 * 📜 开源协议 * 🎯 适用场景推荐 * 💡 总结 MinIO:轻量、高性

By Ne0inhk
Flutter 三方库 image_compare 鸿蒙图像治理算法域双向适配解析:突破千万级相册视觉感知哈希运算指纹比对墙,大体量空间冗余清扫提供高精雷达矩阵-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 image_compare 鸿蒙图像治理算法域双向适配解析:突破千万级相册视觉感知哈希运算指纹比对墙,大体量空间冗余清扫提供高精雷达矩阵-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 image_compare 鸿蒙图像治理算法域双向适配解析:突破千万级相册视觉感知哈希运算指纹比对墙,为大体量空间冗余清扫提供高精雷达矩阵 前言 在 OpenHarmony 应用的内容社交或相册管理开发中,由于重复下载或连拍,用户的磁盘空间极易被重复图像挤占。image_compare 为 Flutter 开发者提供了一套高性能、专注于图像指纹算法的对比方案。本文将介绍如何在鸿蒙端打造极致的视觉资产治理底座。 一、原理解析 / 概念介绍 1.1 基础原理/概念介绍 image_compare 的核心逻辑是基于 感知哈希(Perceptual Hashing, pHash)与颜色直方图空间映射 (Visual-Entropy Map)。它并非简单的逐像素二进制对比,而是通过将图像进行灰度化、离散余弦变换(DCT)降噪,提取反映图像“骨架结构”的

By Ne0inhk
【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、带环链表 * 1.1题目 * 1.2 算法原理 * 1.3 代码 * 1.4 数学证明 * 1.4.1 为什么带环slow与fast必定能相遇? * 1.4.2 fast一定只能走2步吗?可以是2步甚至更多吗? * 1.4.2.1 以3步为例 * 1.4.3结论 * 二、环形链表(寻找相遇点) * 2.1 题目

By Ne0inhk