当前位置: 首页 > news >正文

JavaSE笔记(四)

Java泛型与集合类

在前面我们学习了最重要的类和对象,了解了面向对象编程的思想,注意,非常重要,面向对象是必须要深入理解和掌握的内容,不能草草结束。在本章节,我们会继续深入了解,从我们的泛型开始,再到我们的数据结构,最后再开始我们的集合类学习。

走进泛型

为了统计学生成绩,要求设计一个Score对象,包括课程名称、课程号、课程成绩,但是成绩分为两种,一种是以优秀、良好、合格 来作为结果,还有一种就是 60.0、75.5、92.5 这样的数字分数,那么现在该如何去设计这样的一个Score类呢?现在的问题就是,成绩可能是String类型,也可能是Integer类型,如何才能很好的去存可能出现的两种类型呢?

public class Score {String name;String id;Object score;  //因为Object是所有类型的父类,因此既可以存放Integer也能存放Stringpublic Score(String name, String id, Object score) {this.name = name;this.id = id;this.score = score;}
}

以上的方法虽然很好地解决了多种类型存储问题,但是Object类型在编译阶段并不具有良好的类型判断能力,很容易出现以下的情况:

public static void main(String[] args) {Score score = new Score("数据结构与算法基础", "EP074512", "优秀");  //是String类型的//....Integer number = (Integer) score.score;  //获取成绩需要进行强制类型转换,虽然并不是一开始的类型,但是编译不会报错
}//运行时出现异常!
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integerat com.test.Main.main(Main.java:14)

使用Object类型作为引用,取值只能进行强制类型转换,显然无法在编译期确定类型是否安全,项目中代码量非常之大,进行类型比较又会导致额外的开销和增加代码量,如果不经比较就很容易出现类型转换异常,代码的健壮性有所欠缺!(此方法虽然可行,但并不是最好的方法)

为了解决以上问题,JDK1.5新增了泛型,它能够在编译阶段就检查类型安全,大大提升开发效率。

public class Score<T> {   //将Score转变为泛型类<T>String name;String id;T score;  //T为泛型,根据用户提供的类型自动变成对应类型public Score(String name, String id, T score) {   //提供的score类型即为T代表的类型this.name = name;this.id = id;this.score = score;}
}
public static void main(String[] args) {//直接确定Score的类型是字符串类型的成绩Score<String> score = new Score<String>("数据结构与算法基础", "EP074512", "优秀");Integer i = score.score;  //编译不通过,因为成员变量score类型被定为String!
}

泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型!如果类型不符合,将无法通过编译!

泛型本质上也是一个语法糖(并不是JVM所支持的语法,编译后会转成编译器支持的语法,比如之前的foreach就是),在编译后会被擦除,变回上面的Object类型调用,但是类型转换由编译器帮我们完成,而不是我们自己进行转换(安全)

//反编译后的代码
public static void main(String[] args) {Score score = new Score("数据结构与算法基础", "EP074512", "优秀");String i = (String)score.score;   //其实依然会变为强制类型转换,但是这是由编译器帮我们完成的}

像这样在编译后泛型的内容消失转变为Object的情况称为类型擦除(重要,需要完全理解),所以泛型只是为了方便我们在编译阶段确定类型的一种语法而已,并不是JVM所支持的。

综上,泛型其实就是一种类型参数,用于指定类型。

泛型的使用

泛型类

上一节我们已经提到泛型类的定义,实际上就是普通的类多了一个类型参数,也就是在使用时需要指定具体的泛型类型。泛型的名称一般取单个大写字母,比如T代表Type,也就是类型的英文单词首字母,当然也可以添加数字和其他的字符。

public class Score<T> {   //将Score转变为泛型类<T>String name;String id;T score;  //T为泛型,根据用户提供的类型自动变成对应类型public Score(String name, String id, T score) {   //提供的score类型即为T代表的类型this.name = name;this.id = id;this.score = score;}
}

在一个普通类型中定义泛型,泛型T称为参数化类型,在定义泛型类的引用时,需要明确指出类型:

 Score<String> score = new Score<String>("数据结构与算法基础", "EP074512", "优秀");

此时类中的泛型T已经被替换为String了,在我们获取此对象的泛型属性时,编译器会直接告诉我们类型:

Integer i = score.score;   //编译不通过,因为成员变量score明确为String类型

注意,泛型只能用于对象属性,也就是非静态的成员变量才能使用:

static T score;   //错误,不能在静态成员上定义

由此可见,泛型是只有在创建对象后编译器才能明确泛型类型,而静态类型是类所具有的属性,不足以使得编译器完成类型推断。

泛型无法使用基本类型,如果需要基本类型,只能使用基本类型的包装类进行替换!

Score<double> score = new Score<double>("数据结构与算法基础", "EP074512", 90.5);  //编译不通过

那么为什么泛型无法使用基本类型呢?回想上一节提到的类型擦除,其实就很好理解了。由于JVM没有泛型概念,因此泛型最后还是会被编译器编译为Object,并采用强制类型转换的形式进行类型匹配,而我们的基本数据类型和引用类型之间无法进行类型转换,所以只能使用基本类型的包装类来处理。

类的泛型方法

泛型方法的使用也很简单,我们只需要把它当做一个未知的类型来使用即可:

public T getScore() {    //若方法的返回值类型为泛型,那么编译器会自动进行推断return score;
}public void setScore(T score) {   //若方法的形式参数为泛型,那么实参只能是定义时的类型this.score = score;
}
Score<String> score = new Score<String>("数据结构与算法基础", "EP074512", "优秀");
score.setScore(10);   //编译不通过,因为只接受String类型

同样地,静态方法无法直接使用类定义的泛型(注意是无法直接使用,静态方法可以使用泛型)

自定义泛型方法

那么如果我想在静态方法中使用泛型呢?首先我们要明确之前为什么无法使用泛型,因为之前我们的泛型定义是在类上的,只有明确具体的类型才能开始使用,也就是创建对象时完成类型确定,但是静态方法不需要依附于对象,那么只能在使用时再来确定了,所以静态方法可以使用泛型,但是需要单独定义:

public static <E> void test(E e){   //在方法定义前声明泛型System.out.println(e);
}

同理,成员方法也能自行定义泛型,在实际使用时再进行类型确定:

public <E> void test(E e){System.out.println(e);
}

其实,无论是泛型类还是泛型方法,再使用时一定要能够进行类型推断,明确类型才行。

注意一定要区分类定义的泛型和方法前定义的泛型!

泛型引用

可以看到我们在定义一个泛型类的引用时,需要在后面指出此类型:

Score<Integer> score;  //声明泛型为Integer类型

如果不希望指定类型,或是希望此引用类型可以引用任意泛型的Score类对象,可以使用?通配符,来表示自动匹配任意的可用类型:

Score<?> score;   //score可以引用任意的Score类型对象了!

那么使用通配符之后,得到的泛型成员变量会是什么类型呢?

Object o = score.getScore();   //只能变为Object

因为使用了通配符,编译器就无法进行类型推断,所以只能使用原始类型。

在学习了泛型的界限后,我们还会继续了解通配符的使用。

泛型的界限

现在有一个新的需求,现在没有String类型的成绩了,但是成绩依然可能是整数,也可能是小数,这时我们不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:

public class Score<T extends Number> {   //设定泛型上界,必须是Number的子类private final String name;private final String id;private T score;public Score(String name, String id, T score) {this.name = name;this.id = id;this.score = score;}public T getScore() {return score;}
}

通过extends关键字进行上界限定,只有指定类型或指定类型的子类才能作为类型参数。

同样的,泛型通配符也支持泛型的界限:

Score<? extends Number> score;  //限定为匹配Number及其子类的类型

同理,既然泛型有上限,那么也有下限:

Score<? super Integer> score;   //限定为匹配Integer及其父类

通过super关键字进行下界限定,只有指定类型或指定类型的父类才能作为类型参数。

图解如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么限定了上界后,我们再来使用这个对象的泛型成员,会变成什么类型呢?

Score<? extends Number> score = new Score<>("数据结构与算法基础", "EP074512", 10);
Number o = score.getScore();    //得到的结果为上界类型

也就是说,一旦我们指定了上界后,编译器就将范围从原始类型Object提升到我们指定的上界Number,但是依然无法明确具体类型。思考:那如果定义下限呢?

那么既然我们可以给泛型类限定上界,现在我们来看编译后结果呢:

//使用javap -l 进行反编译
public class com.test.Score<T extends java.lang.Number> {public com.test.Score(java.lang.String, java.lang.String, T);LineNumberTable:line 8: 0line 9: 4line 10: 9line 11: 14line 12: 19LocalVariableTable:Start  Length  Slot  Name   Signature0      20     0  this   Lcom/test/Score;0      20     1  name   Ljava/lang/String;0      20     2    id   Ljava/lang/String;0      20     3 score   Ljava/lang/Number;   //可以看到score的类型直接被编译为Number类public T getScore();LineNumberTable:line 15: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/test/Score;
}

因此,一旦确立上限后,编译器会自动将类型提升到上限类型。

钻石运算符

我们发现,每次创建泛型对象都需要在前后都标明类型,但是实际上后面的类型声明是可以去掉的,因为我们在传入参数时或定义泛型类的引用时,就已经明确了类型,因此JDK1.7提供了钻石运算符来简化代码:

Score<Integer> score = new Score<Integer>("数据结构与算法基础", "EP074512", 10);  //1.7之前Score<Integer> score = new Score<>("数据结构与算法基础", "EP074512", 10);  //1.7之后

泛型与多态

泛型不仅仅可以可以定义在类上,同时也能定义在接口上:

public interface ScoreInterface<T> {T getScore();void setScore(T t);
}

当实现此接口时,我们可以选择在实现类明确泛型类型或是继续使用此泛型,让具体创建的对象来确定类型。

public class Score<T> implements ScoreInterface<T>{   //将Score转变为泛型类<T>private final String name;private final String id;private T score;public Score(String name, String id, T score) { this.name = name;this.id = id;this.score = score;}public T getScore() {return score;}@Overridepublic void setScore(T score) {this.score = score;}
}
public class StringScore implements ScoreInterface<String>{   //在实现时明确类型@Overridepublic String getScore() {return null;}@Overridepublic void setScore(String s) {}
}

抽象类同理,这里就不多做演示了。

多态类型擦除

思考一个问题,既然继承后明确了泛型类型,那么为什么@Override不会出现错误呢,重写的条件是需要和父类的返回值类型、形式参数一致,而泛型默认的原始类型是Object类型,子类明确后变为Number类型,这显然不满足重写的条件,但是为什么依然能编译通过呢?

class A<T>{private T t;public T get(){return t;}public void set(T t){this.t=t;}
}class B extends A<Number>{private Number n;@Overridepublic Number get(){   //这并不满足重写的要求,因为只能重写父类同样返回值和参数的方法,但是这样却能够通过编译!return t;}@Overridepublic void set(Number t){this.t=t;}
}

通过反编译进行观察,实际上是编译器帮助我们生成了两个桥接方法用于支持重写:

@Override
public Object get(){return this.get();//调用返回Number的那个方法
}@Override
public void set(Object t ){this.set((Number)t ); //调用参数是Number的那个方法
}

数据结构基础

警告!本章最难的部分!

学习集合类之前,我们还有最关键的内容需要学习,同第一章一样,自底向上才是最佳的学习方向,比起直接带大家认识集合类,不如先了解一下数据结构,只有了解了数据结构基础,才能更好地学习集合类,同时,数据结构也是你以后深入学习JDK源码的必备条件!(学习不要快餐式!)当然,我们主要是讲解Java,数据结构作为铺垫作用,所以我们只会讲解关键的部分,其他部分可以下去自行了解。

在计算机科学中,数据结构是一种数据组织、管理和存储的格式,它可以帮助我们实现对数据高效的访问和修改。更准确地说,数据结构是数据值的集合,可以体现数据值之间的关系,以及可以对数据进行应用的函数或操作。

通俗地说,我们需要去学习在计算机中如何去更好地管理我们的数据,才能让我们对我们的数据控制更加灵活!

线性表

线性表是最基本的一种数据结构,它是表示一组相同类型数据的有限序列,你可以把它与数组进行参考,但是它并不是数组,线性表是一种表结构,它能够支持数据的插入、删除、更新、查询等,同时数组可以随意存放在数组中任意位置,而线性表只能依次有序排列,不能出现空隙,因此,我们需要进一步的设计。

顺序表

将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构,而以这种方式实现的线性表,我们称为顺序表

同样的,表中的每一个个体都被称为元素,元素左边的元素(上一个元素),称为前驱,同理,右边的元素(后一个元素)称为后驱

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们设计线性表的目标就是为了去更好地管理我们的数据,也就是说,我们可以基于数组,来进行封装,实现增删改查!既然要存储一组数据,那么很容易联想到我们之前学过的数组,数组就能够容纳一组同类型的数据。

相关文章:

JavaSE笔记(四)

Java泛型与集合类 在前面我们学习了最重要的类和对象,了解了面向对象编程的思想,注意,非常重要,面向对象是必须要深入理解和掌握的内容,不能草草结束。在本章节,我们会继续深入了解,从我们的泛型开始,再到我们的数据结构,最后再开始我们的集合类学习。 走进泛型 为…...

C语言基础——指针(5)

一&#xff0e; 函数指针变量 1. 函数指针变量的定义&#xff1a; 类比数组指针变量&#xff0c;数组指针变量是存放数组地址的变量&#xff0c;那么同理&#xff0c;函数指针变量就是存放函数地址的变量。 2. 创建函数指针变量&#xff1a; 函数是有地址的&#xff0…...

curl+openssl 踩坑笔记

curl编译&#xff1a;点击跳转 踩坑一 * SSL certificate problem: unable to get local issuer certificate * closing connection #0 curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.se/docs/sslcerts.html …...

Unity 实现Canvas显示3D物体

新建一个UI相机&#xff0c;选择渲染层为UI 将主相机的渲染层去掉UI层 、 将Canvas的RenderMode设置为Screen Space - Camera,将RenderCamera设置为UI相机 新建3D物体的UI父物体&#xff0c;并将3D物体的层级设置为UI层 适当的放缩3DObjParent&#xff0c;让3D物体能显示出来…...

【Docker命令】如何使用`docker exec`在容器内执行命令

大家好&#xff0c;今天我们来聊聊Docker容器管理中的一个非常有用的命令&#xff1a;docker exec。在日常工作中&#xff0c;我们经常需要在运行中的Docker容器内执行各种命令&#xff0c;docker exec正是帮助我们实现这一需求的利器。下面我将通过一个简单的例子&#xff0c;…...

NetSuite Formula(HTML)超链打开Transaction

当Saved Search作为Sublist应用在Form时&#xff0c;如果Document Number是Group过的&#xff0c;则会出现如下超链失效的情况。 解决办法&#xff1a; 可以利用Saved Search中的Formula&#xff08;HTML&#xff09;功能来构建超链&#xff0c;用于打开Transaction。 以下图…...

【React】- 跨域PDF预览、下载(改文件名)、打印

我们经常会碰到跨域来方位PDF&#xff0c;同时需要下载、打印的需求&#xff0c;通常由于浏览器的安全策略&#xff0c;可以预览&#xff0c;但是下载和打印可能会受限&#xff0c;这时候怎么办呢&#xff1f; 1.创建一个隐藏的标签 要下载 iframe 中的 PDF 文件&#xff0c;…...

git clone ssh 设置代理

Linux配置方法 编辑 ~/.ssh/config 文件 Host github.com Hostname github.com ProxyCommand nc -v -x 127.0.0.1:1080 %h %pwindows配置方法 编辑 C:\Users\当前用户名.ssh\config 文件 Host github.com Hostname github.com ProxyCommand connect -S 127.0.0.1:1080 %h %…...

RK3568平台(USB篇)USB网络共享

使用RK的USB网络共享,在内核里面已经有了,这不需要自己写驱动程序,只需要把内核自带的USB网络共享的驱动添加上去即可。 一.RNDIS 协议简介 RNDIS 是微软定义的一种协议,它允许通过 USB 接口实现网络连接。通过 RNDIS,USB 设备可以充当网络适配器,允许主机通过 USB 与设…...

vite 打包时:JavaScript heap out of memory(内存溢出)

出错原因分析&#xff1a; 执行命令 npm run build 时出现以下错误提示&#xff1a; vite v3.2.7 building for production... 11:22:34 transforming (3) src\main.tsWARN Browserslist: caniuse…...

【服务器学习专栏 1.2 -- 带外管理】

请阅读 嵌入式学习必备专栏 文章目录 Overview服务器带外管理BMC 介绍BMC 特点BMC 工作原理 Overview 从技术的角度&#xff0c;网络管理可分为带外管理&#xff08;out-of-band&#xff09;和带内管理&#xff08;in-band&#xff09;两种管理模式。 带内管理&#xff0c;是指…...

微服务のGeteWay

目录 概念&#xff1a; 三大核心&#xff1a; 工作流程&#xff1a; 9527网关如何做路由映射&#xff1a; GetWay高级特性&#xff1a; 按服务名动态路由服务&#xff1a; 断言Route Predicate Factories &#xff1a; 获取当前时区时间&#xff1a; After Route &…...

html+css+js网页设计 美食 美食家6个页面

htmlcssjs网页设计 美食 美食家6个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xf…...

IntelliJ Idea常用快捷键详解

文章目录 IntelliJ Idea常用快捷键详解一、引言二、文本编辑与导航1、文本编辑2、代码折叠与展开 三、运行和调试四、代码编辑1、代码补全 五、重构与优化1、重构 六、使用示例代码注释示例代码补全示例 七、总结 IntelliJ Idea常用快捷键详解 一、引言 在Java开发中&#xff…...

服务器虚拟化:它是什么以及有什么好处?

运行虚拟服务器有助于创建更高效的 IT 基础架构。 随着业务每天收集的数据量逐年激增&#xff0c;传统的物理服务器已经无法单独满足业务需求。 相反&#xff0c;许多组织正在转向虚拟化的力量。 这是我们创建物理实体的虚拟版本的过程&#xff0c;在计算中&#xff0c;通常指…...

Python爬虫完整代码拿走不谢

对于新手做Python爬虫来说是有点难处的&#xff0c;前期练习的时候可以直接套用模板&#xff0c;这样省时省力还很方便。 使用Python爬取某网站的相关数据&#xff0c;并保存到同目录下Excel。 直接上代码&#xff1a; import re import urllib.error import urllib.request…...

MLA:多头潜在注意力

MLA:多头潜在注意力 多头潜在注意力(MLA)机制是一种在深度学习模型中用于处理序列数据的注意力机制的改进形式,以下是对其原理和示例的详细介绍: 原理 低秩键值联合压缩:MLA机制利用低秩键值联合压缩来消除注意力模块中的某些计算,从而提高模型的运行速度和性能。在传…...

阿里云大模型ACP高级工程师认证模拟试题

阿里云大模型ACP高级工程师认证模拟试题 0. 引言1. 模拟试题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题多选题单选题单选题单选题多选题多选题单选题多选题单…...

游戏引擎学习第67天

reviewing “apron”概念以更新区域 我们正在进行模拟区域的扩展工作&#xff0c;目标是通过增加一个更大的区域来支持更丰富的互动&#xff0c;尤其是那些可能超出摄像机视野的内容。现有的模拟区域包括摄像机能看到的区域和其周围的环境区域&#xff0c;但为了保证更高效的游…...

Nginx知识详解(理论+实战更易懂)

目录 一、Nginx架构和安装 1.1 Nginx 概述 1.1.1 nginx介绍 1.1.2?Nginx 功能介绍 1.1.3?基础特性 1.1.4?Web 服务相关的功能 1.2?Nginx 架构和进程 1.2.1?Nginx 进程结构 1.2.2?Nginx 进程间通信 1.2.3?Nginx 启动和 HTTP 连接建立 1.2.4?HTTP 处理过程 1…...

# 【鸿蒙开发】多线程之Worker的使用

【鸿蒙开发】多线程之Worker的使用 文章目录 【鸿蒙开发】多线程之Worker的使用前言一、Worker的介绍二、注意事项三、Worker使用示例1.新建一个Worker2.主线程使用Worker3.子线程Worker的使用 四、效果展示 前言 本文主要介绍了多线程的方法之一&#xff0c;使用Worker开启多…...

TKG-DM – 基于Latent Diffusion模型的“原生”色度提取生成具有透明通道的图像

概述 原文地址&#xff1a;https://www.unite.ai/improving-green-screen-generation-for-stable-diffusion/ 论文地址&#xff1a;https://arxiv.org/pdf/2411.15580 尽管社区研究和投资者对图像生成人工智能充满热情&#xff0c;但此类系统的输出并不总是可以直接用于产品开…...

告别 Windows 迟缓!多维度优化策略开启流畅新体验

在日常使用 Windows 系统的过程中&#xff0c;随着时间推移和软件安装卸载&#xff0c;系统可能会出现运行缓慢、卡顿等问题。本文中简鹿办公将详细介绍一系列 Windows 系统优化方法&#xff0c;涵盖多个关键层面&#xff0c;助力您的电脑重焕生机。 一、磁盘清理与优化 磁盘…...

亚马逊国际站商品爬虫:Python实战指南

在数字化时代&#xff0c;数据的价值不言而喻。对于电商领域而言&#xff0c;获取竞争对手的商品信息、价格、评价等数据&#xff0c;对于市场分析和策略制定至关重要。本文将带你了解如何使用Python编写爬虫&#xff0c;以亚马逊国际站为例&#xff0c;按照关键字搜索并获取商…...

RabbitMQ基础篇之Java客户端快速入门

文章目录 需求 项目设置与依赖管理 配置RabbitMQ的连接信息创建队列与消息发送创建消费者&#xff08;消息接收&#xff09;环境准备与操作 需求 利用控制台创建队列 simple.queue在 publisher 服务中&#xff0c;利用 SpringAMQP 直接向 simple.queue 发送消息在 consumer 服…...

深度学习:基于MindSpore NLP的数据并行训练

什么是数据并行&#xff1f; 数据并行&#xff08;Data Parallelism, DP&#xff09;的核心思想是将大规模的数据集分割成若干个较小的数据子集&#xff0c;并将这些子集分配到不同的 NPU 计算节点上&#xff0c;每个节点运行相同的模型副本&#xff0c;但处理不同的数据子集。…...

Qt6之QML——枚举

在 QML 中&#xff0c;枚举 (Enumeration) 是一种用于定义一组固定值的功能。通过枚举&#xff0c;可以便捷地提供一组可选值&#xff0c;使用更加明确和精简。 一、枚举的特点 固定值定义&#xff1a; 枚举可以预先定义一组字面值&#xff0c;通常用于需要定义限制值范围的场景…...

ModiLeo交易平台:引领数字货币交易新未来

在当今数字化高速发展的时代&#xff0c;数字货币作为一种新兴的金融资产形式&#xff0c;正逐渐改变着全球金融格局。而此刻&#xff0c;由印度 ModiLeo 实验室联合全球顶级投行共同打造的全球领先的一站式数字货币交易平台——ModiLeo 即将上线&#xff0c;这无疑是数字货币领…...

[python SQLAlchemy数据库操作入门]-15.联合查询,跨表获取股票数据

哈喽,大家好,我是木头左! 在开始探讨如何利用SQLAlchemy实现复杂的联合查询之前,首先需要深入理解其核心组件——对象关系映射(ORM)。ORM允许开发者使用Python类来表示数据库中的表,从而以一种更直观、面向对象的方式来操作数据库。 SQLAlchemy中的JOIN操作详解 在SQLA…...

某网站手势验证码识别深入浅出(全流程)

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路 如有侵犯,请联系作者下架 本文识别已同步上线至OCR识别网站: http://yxlocr.nat300.top/ocr/other/20 本篇文章包含经验和教训总结,我采用了两种方法进行识别,两种方法都各有优劣,其中一…...