java 异常处理_java中,如何把一个参数处理成文本行-程序员宅基地

技术标签: java  异常处理  java web  

【转载:http://blog.csdn.net/yanquan345/article/details/19633623

有一句这样话:一个衡量Java设计师水平和开发团队纪律性的好方法就是读读他们应用程序里的异常处理代码。

本文主要讨论开发Java程序时,如何设计异常处理的代码,如何时抛异常,捕获到了怎么处理,而不是讲异常处理的机制和原理。

在我自己研究Java异常处理之前,我查过很多资料,翻过很多书藉,试过很多搜索引擎,换过很多英文和中文关键字,但是关于异常处理设计的文章实在太少,在我研究完Java异常处理之后,我面试过很多人,也问过很多老员工,极少碰到对Java异常有研究的人,看来研究这个主题的人很少,本文内容本是个人研究异常时做的笔记,现整理一下与大家一起分享。

首先我们简单的回顾一下基础知识,Java中有两种异常,严格的说是三种,包含四个类,层次图如下:


Throwable是一个可抛类,只有其子类可以被关键字throw抛出,请勿直接继承本类,Error是表示系统级错误,如内存耗尽了,我们一般情况下不用管,Exception是所有异常的父类,所以他的子类,除了RuntimeException及其子类,是属于编译时异常,这种异常必须在代码里被显示的捕获语句包住,否则编译不过,而RuntimeException及其子类表示运行时异常,不强制要求写出显示的捕获代码,但如果没有被捕获到,则线程会被强制中断。

我们主要关注后两种,他们的特点已领教了,下面我们通过回答问题的方式来分析异常设计,在开始之前,请确保你已经知道使这两种异常:


捕获到了编译时异常怎么处理

这个话题恐怖是最古老的啦,网上的文章多数都是讨论这个话题,但这些文章大部分只是给了几条禁止的原则,他们是:1)不要直接忽略异常;2)不要用try-catch包住过多语句;3)不要用异常处理来处理程序的正常控制流;4)不要随便将异常迎函数栈向上传递,能处理尽量处理。他们都对,但是要做异常处理的设计,信息还是不够,比如第一条他只是告诉了不要忽略,但没有告诉我们怎么处理,所以很多人直接e.printStackTrace()了,这种处理比直接忽略是好一点,但还不够好。对于第二条,他的理由是避免耗资源很大,不过“过多语句”这句话描述的太模糊了,没说明到底多少才算过多,以致于很多人的try-catch语句只包住会抛编译时异常的那一行代码,如果一段代码中有多行代码会抛编译时异常,那这一段代码中可能有多个try-catch语句块,像这样:

  1. LLJTran llj = new LLJTran(file);  
  2. try {  
  3.     llj.read(LLJTran.READ_INFO,true);  
  4. catch (LLJTranException e) {  
  5.     // …  
  6. }  
  7.   
  8. // …  
  9.   
  10. OutputStream out = null;  
  11. try {  
  12.     File out = new File(file.getPath()+“_bak.jpg”);  
  13.     llj.xferInfo(null, out, LLJTran.REPLACE, LLJTran.REPLACE);  
  14. catch (IOException e) {  
  15.     // …  
  16. }  
  17.   
  18. // …  
  19.   
  20. try {  
  21.     out.close();  
  22. catch (IOException e) {  
  23.     // …  
  24. }  

这样有什么坏处呢,到处都是异常处理的代码,很容易给人造成困惑,很难找出哪些是正常流程的代码,而且还违背了Java异常机制的初衷,Java异常机制是为了把异常处理的代码与正常流程的代码分开,避免程序中出现过多的像传统程序那样的非法值判断语句,以致于扰乱了正常流程。但上述代段充斥着try-catch语句块,已经扰乱了主流程,并极大影响了可读性。

try-catch既不能包太多代码,又不能包太少,那应该包多少才适合呢,这个问题我查过的资料中都没有提,我的个人建议是包住逻辑关系紧密的代码,比如打开文件,读取文件,关闭文件,我认为就是逻辑关系紧密的代码,如果你发现包住的代码很多,可以封装一些方法,如读取文件的代码很长就应该封装成一个方法,这个方法可以申明IOException,(其实读文件的细节本来属于低层逻辑,打开,读取,关闭才属于同层逻辑,如果读取代码很短,初期为了省事才不封成读取细节的代码,不过后期可以重构并封装成方法,这是《重构·改善继有的代码设计》一书中的思想——软件应该不断的重构和加善)。这样才能达到把异常代码与正常流程代码分离的目的。

第3)条没问题,第4)条也有问题,“不要随便”很模糊,那什么时候才能向上传递呢。

吐槽完了,我们现在来说说到底该如何处理捕获到的编译时异常:

一、恢复并继续执行:这个结果是最完美的,也是编译时异常出生的目的——捕获异常,并恢复继续执行程序。所以如果你捕获了一个异常是先尽力恢复,这种情况其实就是在主方案行不通时,用备选方案,而且主方案能否行通不能事先知道,必须执行的时候才能知道,所以在一般情况下,备选方案比主方案要的运行结果要差。比如一个视频程序,它要调用一个下载节目列表的方法,可能如下:

  1. InputStream download() throws IOException {  
  2.     // …  
  3. }  
但服务器不保证总是可用,有可能被攻击了,有可能其它原因,因为是个意外事件,所以又不可能事先知道,于是异常就发生在执行过程中,幸好客户端有备选方案,它在本地保存了一个默认列表,当服务器不可用时,就加载本地列表,所以客户端对这个异常的处理可以如下:
  1. public void loadProgramList() {  
  2.     InputStream inputStream;  
  3.     try {  
  4.         inputStream = download();  
  5.     } catch (IOException e) {  
  6.         // Log this exception  
  7.         System.out.println(”The server occurred errors”);  
  8.         // Use the local file  
  9.         inputStream = openLocalFile();  
  10.     }  
  11.       
  12.     //…  
  13. }  
  14.   
  15. private InputStream download() throws IOException {  
  16.     // …  
  17. }  
  18.   
  19. private InputStream openLocalFile() {  
  20.     // …  
  21. }  
可惜的是,不是任何时候的异常都可以恢复,反而一般情况是不能恢复的。

二、向上传播异常:向上传播就是在本方法上用throws申明,本方法里的代码不对某异常做任何处理。如果不能用上述恢复措施,就检查能不能向上传播,什么情况下可以向上传播呢?有多种说法,一种说法是当本方法恢复不了时,这个说法显然是错误,因为上层也不一定能恢复。另外还有两种说法是:1.当上层逻辑可以恢复程序时;2.当本方法除了打印之外不能做任何处理,而且不确定上层能否处理。这种两种说法都是正确的,但还不够,因为也有的情况,明确知道上层恢复不了也需要上层处理,所以我认为正确的做法是:当你认为本异常应该由上层处理时,才向上传播。不过这得根据你程序的设计来灵活思考,比如你的类设计了一个上层方法集中处理异常,而下层有一些private方法只是简单的用throws申明。当上层方法捕获到异常时,虽然不能恢复执行,但可以做一些处理,如转换成便于阅读的文本,或者用下面讨论的转译。

三、转译异常:转译即把低层逻辑的异常转化成为高层逻辑的异常,因为有可能低层逻辑的异常在高层逻辑中不能被理解,主要实现是新写一个Exception的子类,然后在低层逻辑捕获异常,改抛这个新写的异常,比如刚刚那个视频程序,他的主流程可能是:1.加载节目列表,2.显示播放节目。而加载节目列表子流程又包含读取节目文件、解析节目文件、显示节目列表。而读取节目文件有可能出现IO异常(有可能本地和网上的文件都读不了了),解析节目文件可能出现解析异常,这时如果把这些异常,直接向上传播,变成这样,你觉得合理吗:

  1. public void mainFlow() {  
  2.     // 1.load program list  
  3.     try {  
  4.         loadProgramList();  
  5.     } catch (IOException e) {  
  6.         // I don’t understand what is this exception.  
  7.     } catch (ParseException e) {  
  8.         // I don’t understand what is this exception.  
  9.     }  
  10.       
  11.     // 2.play program  
  12.     // …  
  13. }  
  14.   
  15. public void loadProgramList() throws IOException, ParseException {  
  16.     // 1.Read program file  
  17.     InputStream inputStream;  
  18.     try {  
  19.         inputStream = download();  
  20.     } catch (IOException e) {  
  21.         // Log this exception  
  22.         System.out.println(”The server occurred errors”);  
  23.         // Use the local file  
  24.         inputStream = openLocalFile();    //Maybe throw IOException.  
  25.     }  
  26.       
  27.     // 2.Parse program file  
  28.     parserProgramFile(inputStream);        //Maybe throw ParseException.  
  29.       
  30.     // 3.Display program file  
  31.     //…  
  32. }  
由于loadProgramList将两个可能的异常向上传播,在mainFlow里,必须显示捕获这两个异常,但在mainFlow根本就不能理解这两个异常代表什么,mainFlow里只需要知道加载节目列表异常就可以了,所以我们可以写一个异常类LoadProgramException代表加载节目异常,并在loadProgramList里抛出,于是代码变成这样:
  1. public void mainFlow() {  
  2.     // 1.load program list  
  3.     try {  
  4.         loadProgramList();  
  5.     } catch (LoadProgramException e) {        // look at here  
  6.         // …  
  7.     }  
  8.       
  9.     // 2.play program  
  10.     // …  
  11. }  
  12.   
  13. public void loadProgramList() throws LoadProgramException {        // look at here  
  14.     // 1.Read program file  
  15.     InputStream inputStream = null;  
  16.     try {  
  17.         inputStream = download();  
  18.     } catch (IOException e) {  
  19.         // Log this exception  
  20.         System.out.println(”The server occurred errors”);  
  21.         // Use the local file  
  22.         try {  
  23.             inputStream = openLocalFile();  
  24.         } catch (IOException e1) {  
  25.             throw new LoadProgramException(“Read program file error.”, e1);        // look at here  
  26.         }  
  27.     }  
  28.       
  29.     // 2.Parse program file  
  30.     try {  
  31.         parserProgramFile(inputStream);  
  32.     } catch (ParseException e) {  
  33.         throw new LoadProgramException(“Parse program file error.”, e);            // look at here  
  34.     }  
  35.       
  36.     // 3.Display program file  
  37.     //…  
  38. }  
  39.   
  40. // …  
  41.   
  42. class LoadProgramException extends Exception {  
  43.     public LoadProgramException(String msg, Throwable cause) {  
  44.         super(msg, cause);  
  45.     }  
  46.     // …  
  47. }  
注意:LoadProgramException构造函数的第一个参数是代表原因,用于组成异常链,异常链是一种机制,异常转译时,保存原来的异常,这样当这个异常再被转译时,还会被保存,于是就成了一条链了,包含了所有的异常,所以你可以看到这样的异常打印:
  1. Exception in thread “main” java.lang.NoClassDefFoundError: graphics/shapes/Square  
  2.     at Main.main(Main.java:7)  
  3. Caused by: java.lang.ClassNotFoundException: graphics.shapes.Square  
  4.     at java.net.URLClassLoader1.run(URLClassLoader.java:366)&nbsp;&nbsp;</span></li><li class="alt"><span>&nbsp;&nbsp;&nbsp;&nbsp;at&nbsp;java.net.URLClassLoader1.run(URLClassLoader.java:355)  
  5.     at java.security.AccessController.doPrivileged(Native Method)  
  6.     at java.net.URLClassLoader.findClass(URLClassLoader.java:354)  
  7.     at java.lang.ClassLoader.loadClass(ClassLoader.java:424)  
  8.     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)  
  9.     at java.lang.ClassLoader.loadClass(ClassLoader.java:357)  
  10.     … 1 more  
这个异常链中就是包含了两个异常,最前面是顶级异常,后面再打印一个Cause by,然后再打印低一层异常,直到打印完所有的异常。

另外,主流程中还有一个播放流程也可以定义一个播放异常的类,再做这样的转译处理,但是,如果流程多,是不是得写多个异常类呢,有人建议是每个包定义一个异常类,但并不是绝对的,这个细粒度还要根据具体的程序逻辑来决定,这种把握能力就要靠经验了,这可能就是架构师的过人之处了。

四、改抛为运行时异常:这个很好玩,也是一条很方便的处理手法(我常用,我用这个还发现了一个Android系统的bug),即当你捕获到异常时,重新抛出,这跟转译很相似,有一点区别,这里抛的是运行时异常,而转译抛的是编译时异常。那什么时候使用这个手法呢?简单的说就是当某个异常出现时,你必须让程序挂掉。解释一下:如果某个异常情况一旦出现,程序便无法继续执行,而且你明确知道本方法和上层逻辑做不出任何有意义的处理,你只能让程序退出。所以你就抛一个运行时异常让程序挂掉。举个例子,比如在加密通信中,服务器捕获到了一个非法数据异常,这是无法恢复的,而且就是抛一运行异常,让线程挂掉,连接便会自动中断。

五、记录并消耗掉异常:这个手法就是把异常记录下来(到文件或控制台)然后忽略掉异常,有可能随后就让本方法返回null,这个手法一般用在不是很严重的异常,相当于是warning级别的错误,出现这个异常对程序的执行可能影响不太,比如程序的某个偏好设置文件(如窗口位置,最近文件等)损坏,但这个文件信息很少,程序只要使用默认配置即可。


有没必要显示捕获运行时异常

运行时异常一般是不需要捕获的,因为它的目的就是让程序在无法恢复时挂掉,但是也有特殊需求,比如你要收集所有的未捕获异常记录,可能用于统计,也可能用于将来调试。还有其它原因使你不想让程序直接挂掉,比如你想把友好信息告诉用户。


什么时候需要抛异常

马上就要讨论如何抛异常了,但在必须先知道,什么时候需要抛异常,简单的说就是遇到一个异常情况,这是一个模棱两可的问题,就像美不美这个问题一样,我几种说法,你看你能理解哪一种,一种是正常情况的反面,即非正常情况,那什么是非正常情况呢,这也是仁者见仁,智者见智,比如说读到文件尾,这个算正常还是异常呢,都说得过去,所以这里给一个判断方法做为参考,如果是一个典型情况,就不当成是异常,所以读到文件尾就没有被当成一个异常,返回了-1。还有一种说法是,程序执行的必要条件不能成立,使得本方法无法继续履行自己的职责。这两种说法都不错,你都可以用,而且覆盖了大部分情况。


何时选用编译时异常:编译时异常是Java特有的,其它语言没有,刚出来时很流行,所以你可以看到流处理包里充斥着IOException,但经过多年的使用,有人觉得编译时异常是一种实验性错误,应该完全丢弃,说这个话的人就是《Think In Java》的一书的作者Eckel,我认为这种说法太绝对了,关于这个是与否也有很大的争论。《Effective Java》一书的作者则认为应避免不必要的编译时异常,因为你抛编译时异常会给强制要求调用者捕获,这会增加他的负担,我是这一观点的支持者。那到底何时抛编译时异常呢?当你发现一个异常情况时,检查这两个条件,为真时选用编译时异常:一、如果调用者可以恢复此异常情况,二、如果调用者不能恢复,但能做出有意义的事,如转译等。如果你不确定调用者能否做出有意义的事,就别使编译时异常,免得被抱怨。还有一条原则,应尽最大可能使用编译时异常来代替错误码,这条也是编译时异常设计的目的。另外,必须注意使用编译时异常的目的是为了恢复执行,所以设计异常类的时候,应提供尽量多的异常数据,以便于上层恢复,比如一个解析错误,可以在设计的异常类写几个变量来存储异常数据:解析出错的句子的内容,解析出错句子的行号,解析出错的字符在行中的位置。这些信息可能帮助调用恢复程序。


何时选用运行时异常:首先,运行时异常肯定是不可恢复的异常,否则按上段方法处理。这个不可恢复指的是运行时期不可恢复,如果可以修改源代码来避免本异常的发生呢,那说明这是一个编程错误,对于编程错误,一定要抛运行时异常,编程错误一般可以通过修改代码来永久性避免该异常,所以这种情况应该让程序挂掉,相当于爆出一个bug,从而提醒程序员修改代码。这种编程错误可以总结一下,API是调用者与实现者之间的契约,调用者必须遵守契约,比如传入的参数不允许为空,这一点是隐含契约,没必要明确写出来的,如果违反契约,实现者就可以抛运行时异常,让程序挂掉以提醒调用者。

其它情况是否应使用运行时异常,上面提到过,就是谁都无能为力的异常情况,还有就是你不确定到底能不能恢复,除此之外,你可以这样判断:如果你希望程序挂掉,就用运行时异常。需要说明的是,请尽量使用系统自带异常,而不是新写。网上还有一条建议是使用运行时异常时, 一定要将所有可能的异常写进文档。这认为只要把不常用的写上即可,像NullPointException每个方法都有可能抛,但没必要每个方法都写说明。


将编译时异常重构成运行时异常

你可能手头上有一份以前的代码,大量的使有了编译时异常,但很多都是没有必要的编译时异常,导致调用上不方便,《Effective Java》里有一种方法可以将编译时异常转为运行时异常:将原来抛编译时异常的方法,拆成两个方法,其中一个是用来指示异常是否为发生,即将以下代码:

  1. // Invocation with checked exception  
  2. try {  
  3. obj.action(args);  
  4. catch(TheCheckedException e) {  
  5. // Handle exceptional condition  
  6. …  
  7. }  
改为这样:
  1. // Invocation with state-testing method and unchecked exception  
  2. if (obj.actionPermitted(args)) {  
  3. obj.action(args);  
  4. else {  
  5. // Handle exceptional condition  
  6. …  
  7. }  
步骤是:1)将原来方法foo的异常申明删掉,并在实现里面改抛为运行时异常;2)添加一个方法isFoo,返回一个布尔值指示是否会有异常情况出现;3)在foo调用前加一个if语句,判断isFoo的返回值,如果为真才调用foo,否则不调用;4)删掉调用处的try-catch。


UI层处理异常的注意点

UI层和其下逻辑层的区别是UI层的出错信息是被用户看,而其下层逻层出错信息是被程序员看到,用户可不希望看一个打印的异常栈,更不希望程序无缘无故挂掉,用户希望看到友好的提示信息。为到达这一目的,我们可以设一个屏障,屏障可以捕获所有遗漏的异常,从而阻止程序直接挂掉,屏障当然恢复不了运行,但可以记录错误便于日后调试,还可以输出友好信息给用户。Spring和Struts就有这样的处理。

还有一点需要注意,用户的传入参数出现非法的概率很高,所以控制层接受到参数时一定要校验,而不是原封不动的传到其低层模块。


经历了一周的熬夜,总算把异常处理总结归纳成文了,但由于文章太长,肯定有一些错误和语言不精炼的地方,我会仔细检察并及时改正,希望本文对大家有一定的帮助。


附录

在我查过的资料中,以《Effective Java》书中对异常处理设计的研究得最系统,本文很多思想来自于它,下面我把其中的几条原则翻译(非直译)并贴上:

第57条:只对异常情况使用异常。(说明:即不要用异常处理控制正常程序流)。

第58条:对可恢复异常使用编译时异常,对编程错误使用运行时异常。

第59条:应避免不必要的编译时异常:如果调用者即使合理的使用API也不能避免异常的发生,并且调用者可以对捕获的异常做出有意义的处理,才使用编译时异常。

第60条:应偏好使用自带异常

第61条:抛出的异常应适合本层抽象(就是上面说的转译)

第62条:把方法可能抛的所有异常写入文档,包括运行时异常

第63条:用异常类记录的信息要包含失败时的数据

第64条:力求失败是原子化的(解释:就是如果调用一个方法发生了异常,就应该使对象返回调用前的状态)

第65条:不要忽略异常



参考资料:

Effective Java, 2nd.Edition

Effective Java Exceptions, 译文可参考这里这里


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lvyanqin2013/article/details/76548451

智能推荐

LS1046A 环境搭建-LSDK源码获取编译_ls1046a移植uboot-程序员宅基地

文章浏览阅读1.6k次。博主环境为: 主机 Ubuntu18.04 虚拟机。如果用其他Ubuntu 版本,需要使用docker1.source环境$tar xvzf flexbuild_lsdk1903.tgz$cd flexbuild_lsdk1903$source setup.env自动编译命令:flex-builder -m ls1046ardb -a arm64独立编译$ flex-buil..._ls1046a移植uboot

在Linux系统中使用Shell脚本文件自动备份Oracle数据库_liunx 备份oracle shell脚本-程序员宅基地

文章浏览阅读647次。在Linux系统中使用Shell脚本文件自动备份Oracle数据库创建逻辑目录 create directory data_dir as ‘/home/oracle/’; data_dir 可自定义select * from dba_directories; 可查询用户创建目录。给用户授予访问逻辑目录的权限grant read,write on directory data_dir to sa; sa为用户名给用户授予导入和导出的权限grant exp_full_datab_liunx 备份oracle shell脚本

Tomcat虚拟目录配置[转]_当url中未明确指定文件名时,是否列出相关目录下所有文件的开关设置-程序员宅基地

文章浏览阅读364次。Tomcat6.0虚拟目录配置[转] 设虚拟目录 "site",通过 http://localhost:8080/site 访问物理路径 D:"site 文件夹里面的内容。设置过程如下: 1.复制 Tomcat6.0"webapps"ROOT 目录下的 WEB-INF 文件夹到 D:"site 目录下。 2.打开 D:"site"WEB-INF 目录下的 web.xml 文件,在 之_当url中未明确指定文件名时,是否列出相关目录下所有文件的开关设置

互联网软件架构微服务演进之路_尚硅谷宋永康-程序员宅基地

文章浏览阅读283次。前言日期:2021/05/27心情:????????????Description:JAVA构建网站架构的演进,以下内容均为java学习笔记。注:本文主要内容是图片资料来源于尚硅谷(宋永康老师)尚硅谷b站视频:https://www.bilibili.com/video/BV1Bv411Y7sQ?p=12互联网架构演进前言1. 单机集中构建网站2. 应用服务器配置集群2.1 nginx+应用服务器配置集群2.2 nginx+应用服务器配置集群+HA3. 负载均衡服务器配置集群4. CDN+Va_尚硅谷宋永康

网络端口的认识_物理网口-程序员宅基地

文章浏览阅读996次。端口分为物理端口和逻辑端口两种,物理端口又叫可见端口,都是实物比如ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。逻辑端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。逻辑端口又分为公认端口(0-1023),注册端口(1024-49151)和私有端口(49152-65535)。根据提供服务类型的不同,端口分为两种,一种是TCP端口,一种是UDP端口。计算机_物理网口

2步搞定Linux中搭建nginx文件服务器_nginxlixianditufuwuqi-程序员宅基地

文章浏览阅读1.2k次。2步搞定Linux搭建nginx图片服务器_nginxlixianditufuwuqi

随便推点

基于FreeRTOS的物联网智能车库管理系统(可进行车牌识别)_基于freertos的车辆状态监测系统-程序员宅基地

文章浏览阅读6.6k次,点赞4次,收藏52次。FreeRTOS操作系统与物联网项目实战学了一周的FreeRTOS实时操作系统,物联网也学过了,看到网上低配版的操作系统车库,于是便想着自己做个高配版的,便开始了我的一周项目计划。文章目录FreeRTOS操作系统与物联网项目实战前言一、功能介绍总结前言行动,只有行动,才能决定价值。只有项目才能巩固知识。一、功能介绍项目名称:基于FreeRTOS的物联网智能车库管理系统功能介绍:车辆靠近车库,由车牌检测系统检测车牌号,起落杆自动抬起,并通过语音播报车辆车牌号,提示通过,同时LCD显示屏界面现_基于freertos的车辆状态监测系统

Linux系统的内核态和用户态_linux分为几个态-程序员宅基地

文章浏览阅读3.4k次,点赞6次,收藏17次。一、 Unix/Linux的体系架构  如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供_linux分为几个态

嵌入式AI-K210篇-硬件-模型训练、部署_嵌入式 模型训练-程序员宅基地

文章浏览阅读3.7k次,点赞4次,收藏50次。K210的其他参数如下:双核 64-bit RISC-V RV64IMAFDC (RV64GC) CPU / 400MHz(可超频到600MHz)双精度 FPU8MiB 64bit 片上 SRAM(6MiB通用SRAM+2MiB的AI专用SRAM)神经网络处理器(KPU) / 0.8TFLOPS音频处理器(APU)可编程 IO 阵列 (FPIOA)双硬件512点16位复数FFTSPI, I2C, UART, I2S, RTC, PWM, 定时器支持AES, SHA256 加速器直接内._嵌入式 模型训练

送一堆书-程序员宅基地

文章浏览阅读258次。大家好!端午节马上就要快到了,为了感谢大家一直以来的陪伴和支持,我和其他几个数据领域的大咖联合清华大学出版社来给大家送福利啦!!话不多说,我们进入正题:怎么送书和送什么书。1.怎么送书本次送书由10 个大佬一起给大家送上 30 本技术书籍,包邮噢!送书规则如下1. 在 10个公众号的后台分别回复【531】二字,即可出来抽奖小程序。十个公众号,同时参与,中奖概率增大 9..._怎样在后台发送531

将一个excel表格的数据匹配到另一个表中_将一个表中的数据对应到另一个表中的名字-程序员宅基地

文章浏览阅读1.1w次。将一个excel表格的数据匹配到另一个表中第一步:我们打开一个excel表,需要在另一个表中匹配出相应学校的信息。第二步:我们把光标定位在需要展示数据的单元格中,在单元格中输入“=vl”,然后会自动提示出VLOOKUP函数,双击蓝色的函数部分。我们可以看到,单元格中显示出完整的VLOOKUP(参数1,参数2,参数3,参数4)函数。里面的参数按照自己的需求来。比如,按照我的需求:参数1:查询A4单元格内的学校的信息,所以参数1是A4;参数2:需要从‘预处理后学校信息-副表.xlsx’表中查找信息_将一个表中的数据对应到另一个表中的名字

RK3588平台开发系列讲解(SARADC篇)SARADC的工作流程_rockcip saradc-程序员宅基地

文章浏览阅读1.9k次,点赞32次,收藏26次。本篇将介绍SARADC的源码分析和工作流程。_rockcip saradc

推荐文章

热门文章

相关标签