`
lvwenwen
  • 浏览: 932122 次
  • 性别: Icon_minigender_1
  • 来自: 魔都
社区版块
存档分类
最新评论

关于 Java 中 finally 语句块的深度辨析

阅读更多
文章连接:http://www.ibm.com/developerworks/cn/java/j-lo-finally/index.html

关于 Java 中 finally 语句块的深度辨析

 

魏 成利 (cwei@adobe.com), 软件工程师, Adobe

 

简介: 乍看这个题目,是不是有人会问,这个谁不知道啊,大凡熟悉 Java 编程的人都知道 finally 语句块的作用和用法。有什么可深度辨析的呢?事实并非如此,我发现即使写了很多年 Java 程序的人,也不一定能够透彻的理解 finally 语句块。本篇将以生动形象的案例来带您由浅入深的来分析一下这个小小的 finally,希望这篇文章能够让您真正的理解 finally 语句块的本质,至少阅读完本篇文章后,没有觉得浪费了时间。

 

 

发布日期: 2011 年 7 月 13 日 
级别: 中级 
访问情况 : 10438 次浏览 
评论: 12 (查看 | 添加评论 - 登录)

平均分 4 星 共 53 个评分 平均分 (53个评分)
为本文评分

 

可不能小看这个简单的 finally,看似简单的问题背后,却隐藏了无数的玄机。接下来我就带您一步一步的揭开这个 finally 的神秘面纱。

问题分析

首先来问大家一个问题:finally 语句块一定会执行吗?

很多人都认为 finally 语句块是肯定要执行的,其中也包括一些很有经验的 Java 程序员。可惜并不像大多人所认为的那样,对于这个问题,答案当然是否定的,我们先来看下面这个例子。


清单 1.
				
 public class Test { 
 public static void main(String[] args) { 
 System.out.println("return value of test(): " + test()); 
	 } 

 public static int test() { 
 int i = 1; 
		
 // 		 if(i == 1) 
 // 			 return 0; 
 System.out.println("the previous statement of try block"); 
 i = i / 0; 
		
 try { 
    System.out.println("try block"); 
      return i; 
     }finally { 
     System.out.println("finally block"); 
		 } 
	 } 
 } 

清单 1 的执行结果如下:

 the previous statement of try block 
 Exception in thread "main" java.lang.ArithmeticException: / by zero 
 at com.bj.charlie.Test.test(Test.java:15) 
 at com.bj.charlie.Test.main(Test.java:6) 

另外,如果去掉上例中被注释的两条语句前的注释符,执行结果则是:

 return value of test(): 0 

在以上两种情况下,finally 语句块都没有执行,说明什么问题呢?只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行。以上两种情况,都是在 try 语句块之前返回(return)或者抛出异常,所以 try 对应的 finally 语句块没有执行。

那好,即使与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块一定会执行吗?不好意思,这次可能又让大家失望了,答案仍然是否定的。请看下面这个例子(清单 2)。


清单 2.
				
 public class Test { 
 public static void main(String[] args) { 
 System.out.println("return value of test(): " + test()); 
	 } 

 public static int test() { 
 int i = 1; 

 try { 
 System.out.println("try block"); 
 System.exit(0); 
 return i; 
 }finally { 
 System.out.println("finally block"); 
		 } 
	 } 
 } 

清单 2 的执行结果如下:

 try block 

finally 语句块还是没有执行,为什么呢?因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。那有人说了,在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的。OK !没有问题,我们不调用 System.exit(0) 这个方法,那么 finally 语句块就一定会执行吗?

再一次让大家失望了,答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。

finally 语句剖析

说了这么多,还是让我们拿出些有说服力的证据吧!还有什么证据比官方的文档更具说服力呢?让我们来看看官方网站上的《The Java Tutorials》中是怎样来描述 finally 语句块的吧!

以下位于 **** 之间的内容原封不动的摘自于《 The Java Tutorials 》文档。

*******************************************************************************

The finally Block

The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return,continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.

Note:  If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.

*******************************************************************************

请仔细阅读并认真体会一下以上两段英文,当你真正的理解了这两段英文的确切含义,你就可以非常自信的来回答“finally 语句块是否一定会执行?”这样的问题。看来,大多时候,并不是 Java 语言本身有多么高深,而是我们忽略了对基础知识的深入理解。

接下来,我们看一下 finally 语句块是怎样执行的。在排除了以上 finally 语句块不执行的情况后,finally 语句块就得保证要执行,既然 finally 语句块一定要执行,那么它和 try 语句块与 catch 语句块的执行顺序又是怎样的呢?还有,如果 try 语句块中有 return 语句,那么 finally 语句块是在 return 之前执行,还是在 return 之后执行呢?带着这样一些问题,我们还是以具体的案例来讲解。

关于 try、catch、finally 的执行顺序问题,我们还是来看看权威的论述吧!以下 **** 之间的内容摘自 Java 语言规范第四版( The Java™ Programming Language, Fourth Edition )中对于 try,catch,和 finally 的描述。

*******************************************************************************

12.4. Try, catch, and finally

You catch exceptions by enclosing code in Try blocks. The basic syntax for a Try block is:

try {

statements

} catch (exception_type1 identifier1) {

statements

} catch (exception_type2 identifier2) {

statements

...

} finally {

statements

}

where either at least one catch clause, or the finally clause, must be present. The body of the try statement is executed until either an exception is thrown or the body finishes successfully. If an exception is thrown, each catch clause is examined in turn, from first to last, to see whether the type of the exception object is assignable to the type declared in the catch. When an assignable catch clause is found, its block is executed with its identifier set to reference the exception object. No other catch clause will be executed. Any number of catch clauses, including zero, can be associated with a particular TRy as long as each clause catches a different type of exception. If no appropriate catch is found, the exception percolates out of the try statement into any outer try that might have a catch clause to handle it.

If a finally clause is present with a try, its code is executed after all other processing in the try is complete. This happens no matter how completion was achieved, whether normally, through an exception, or through a control flow statement such as return or break.

*******************************************************************************

上面这段文字的大体意思是说,不管 try 语句块正常结束还是异常结束,finally 语句块是保证要执行的。如果 try 语句块正常结束,那么在 try 语句块中的语句都执行完之后,再执行 finally 语句块。如果 try 中有控制转移语句(return、break、continue)呢?那 finally 语句块是在控制转移语句之前执行,还是之后执行呢?似乎从上面的描述中我们还看不出任何端倪,不要着急,后面的讲解中我们会分析这个问题。如果 try 语句块异常结束,应该先去相应的 catch 块做异常处理,然后执行 finally 语句块。同样的问题,如果 catch 语句块中包含控制转移语句呢? finally 语句块是在这些控制转移语句之前,还是之后执行呢?我们也会在后续讨论中提到。

其实,关于 try,catch,finally 的执行流程远非这么简单,有兴趣的读者可以参考 Java 语言规范第三版( The Java™ Language Specification, Third Edition )中对于 Execution of try-catch-finally 的描述,非常复杂的一个流程。限于篇幅的原因,本文不做摘录,请感兴趣的读者自行阅读。

finally 语句示例说明

下面,我们先来看一个简单的例子(清单 3)。


清单 3.
				
 public class Test { 
 public static void main(String[] args) {  
 try {  
 System.out.println("try block");  

 return ;  
 } finally {  
 System.out.println("finally block");  
		 }  
	 }  
 } 

清单 3 的执行结果为:

 try block 
 finally block 

清单 3 说明 finally 语句块在 try 语句块中的 return 语句之前执行。我们再来看另一个例子(清单 4)。


清单 4.
				
 public class Test { 
 public static void main(String[] args) {  
 System.out.println("reture value of test() : " + test()); 
	 } 
	
 public static int test(){ 
 int i = 1; 
		
 try {  
 System.out.println("try block");  
			 i = 1 / 0; 
 return 1;  
 }catch (Exception e){ 
 System.out.println("exception block"); 
 return 2; 
 }finally {  
 System.out.println("finally block");  
		 } 
	 } 
 } 

清单 4 的执行结果为:

 try block 
 exception block 
 finally block 
 reture value of test() : 2 

清单 4 说明了 finally 语句块在 catch 语句块中的 return 语句之前执行。

从上面的清单 3 和清单 4,我们可以看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。请大家先记住它们的区别,在后续的分析中我们还会谈到。

还是得来点有说服力的证据,下面这段摘自 Java 语言规范第四版( The Java™ Programming Language, Fourth Edition ),请读者自己体会一下其含义。

*******************************************************************************

Afinallyclause can also be used to clean up forbreak,continue, andreturn, which is one reason you will sometimes see atryclause with nocatchclauses. When any control transfer statement is executed, all relevantfinallyclauses are executed. There is no way to leave atryblock without executing itsfinallyclause.

*******************************************************************************

好了,看到这里,是不是有人认为自己已经掌握了 finally 的用法了?先别忙着下结论,我们再来看两个例子 – 清单 5 和清单 6。


清单 5.
				
 public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
	 } 

 public static int getValue() { 
        try { 
                 return 0; 
        } finally { 
                 return 1; 
	        } 
	 } 
 } 

清单 5 的执行结果:

 return value of getValue(): 1 


清单 6.
				
 public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
	 } 

 public static int getValue() { 
        int i = 1; 
        try { 
                 return i; 
        } finally { 
                 i++; 
        } 
	 } 
 } 

清单 6 的执行结果:

 return value of getValue(): 1 

利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。 由此,可以轻松的理解清单 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。那为什么清单 6 的返回值不是 2,而是 1 呢?按照清单 5 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?

关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

是不是不太好理解,那我们就用具体的例子来做形象的说明吧!

为了能够解释清单 6 的执行结果,我们来分析一下清单 6 的字节码(byte-code):

 Compiled from "Test.java"
 public class Test extends java.lang.Object{ 
 public Test(); 
  Code: 
   0: 	 aload_0 
   1:invokespecial#1; //Method java/lang/Object."<init>":()V 
   4: 	 return 

  LineNumberTable: 
   line 1: 0 

 public static void main(java.lang.String[]); 
  Code: 
   0: 	 getstatic 	 #2; //Field java/lang/System.out:Ljava/io/PrintStream; 
   3: 	 new 	 #3; //class java/lang/StringBuilder 
   6: 	 dup 
   7: 	 invokespecial 	 #4; //Method java/lang/StringBuilder."<init>":()V 
   10: 	 ldc 	 #5; //String return value of getValue(): 
   12: 	 invokevirtual 	 
   #6; //Method java/lang/StringBuilder.append:(
       Ljava/lang/String;)Ljava/lang/StringBuilder; 
   15: 	 invokestatic 	 #7; //Method getValue:()I 
   18: 	 invokevirtual 	 
   #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 
   21: 	 invokevirtual 	 
   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
   24: 	 invokevirtual 	 #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
   27: 	 return 

 public static int getValue(); 
  Code: 
   0: 	 iconst_1 
   1: 	 istore_0 
   2: 	 iload_0 
   3: 	 istore_1 
   4: 	 iinc 	 0, 1 
   7: 	 iload_1 
   8: 	 ireturn 
   9: 	 astore_2 
   10: 	 iinc 	 0, 1 
   13: 	 aload_2 
   14: 	 athrow 
  Exception table: 
   from   to  target type 
     2     4     9   any 
     9    10     9   any 
 } 

对于 Test()构造方法与 main()方法,在这里,我们不做过多解释。让我们来分析一下 getValue()方法的执行。在这之前,先让我把 getValue()中用到的虚拟机指令解释一下,以便读者能够正确的理解该函数的执行。

1.	iconst_ 
Description: Push the int constant  (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.
Forms: iconst_m1 = 2 (0x2)  iconst_0 = 3 (0x3)  iconst_1 = 4 (0x4)  
iconst_2 = 5 (0x5) iconst_3 = 6 (0x6)  iconst_4 = 7 (0x7)  iconst_5 = 8 (0x8)

2.	istore_ 
Description: Store int into local variable. The  must be an index into the 
local variable array of the current frame. 
Forms: istore_0 = 59 (0x3b)  istore_1 = 60 (0x3c)  istore_2 = 61 (0x3d)  
istore_3 = 62 (0x3e)

3.	iload_ 
Description: Load int from local variable. The  must be an index into the 
local variable array of the current frame. 
Forms: iload_0 = 26 (0x1a)  iload_1 = 27 (0x1b)  iload_2 = 28 (0x1c)  iload_3 = 29 (0x1d)

4.	iinc index, const 
Description: Increment local variable by constant. The index is an unsigned byte that 
must be an index into the local variable array of the current frame. The const is an 
immediate signed byte. The local variable at index must contain an int. The value 
const is first sign-extended to an int, and then the local variable at index is 
incremented by that amount.
Forms:  iinc = 132 (0x84)

Format:
iinc 	
index 	
const 	

5.	ireturn 
Description: Return int from method.
Forms:  ireturn = 172 (0xac)

6.	astore_ 
Description: Store reference into local variable. The  must be an index into the 
local variable array of the current frame.
Forms: astore_0 = 75 (0x4b) astore_1 = 76 (0x4c) astore_2 =77 (0x4d) astore_3 =78 (0x4e)

7.	aload_ 
Description: Load reference from local variable. The  must be an index into the 
local variable array of the current frame.
Forms: aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d)

8.	athrow 
Description: Throw exception or error.
Forms: athrow = 191 (0xbf)



有了以上的 Java 虚拟机指令,我们来分析一下其执行顺序:分为正常执行(没有 exception)和异常执行(有 exception)两种情况。我们先来看一下正常执行的情况,如图 1 所示:


图 1. getValue()函数正常执行的情况
图 1. getValue()函数正常执行的情况 

由上图,我们可以清晰的看出,在 finally 语句块(iinc 0, 1)执行之前,getValue()方法保存了其返回值(1)到本地表量表中 1 的位置,完成这个任务的指令是 istore_1;然后执行 finally 语句块(iinc 0, 1),finally 语句块把位于 0 这个位置的本地变量表中的值加 1,变成 2;待 finally 语句块执行完毕之后,把本地表量表中 1 的位置上值恢复到操作数栈(iload_1),最后执行 ireturn 指令把当前操作数栈中的值(1)返回给其调用者(main)。这就是为什么清单 6 的执行结果是 1,而不是 2 的原因。

再让我们来看看异常执行的情况。是不是有人会问,你的清单 6 中都没有 catch 语句,哪来的异常处理呢?我觉得这是一个好问题,其实,即使没有 catch 语句,Java 编译器编译出的字节码中还是有默认的异常处理的,别忘了,除了需要捕获的异常,还可能有不需捕获的异常(如:RunTimeException 和 Error)。

从 getValue()方法的字节码中,我们可以看到它的异常处理表(exception table), 如下:

Exception table:

from to target type

2 4 9 any

它的意思是说:如果从 2 到 4 这段指令出现异常,则由从 9 开始的指令来处理。


图 2. getValue()函数异常执行的情况
图 2. getValue()函数异常执行的情况 

先说明一点,上图中的 exception 其实应该是 exception 对象的引用,为了方便说明,我直接把它写成 exception 了。

由上图(图 2)可知,当从 2 到 4 这段指令出现异常时,将会产生一个 exception 对象,并且把它压入当前操作数栈的栈顶。接下来是 astore_2 这条指令,它负责把 exception 对象保存到本地变量表中 2 的位置,然后执行 finally 语句块,待 finally 语句块执行完毕后,再由 aload_2 这条指令把预先存储的 exception 对象恢复到操作数栈中,最后由 athrow 指令将其返回给该方法的调用者(main)。

通过以上的分析,大家应该已经清楚 try-catch-finally 语句块的执行流程了吧!

为了更具说服力,我们还是来引经据典吧!大家可以不相信我,难道还不相信“高司令”(Gosling)吗?下面这段仍然摘自 Java 语言规范第四版  The Java™ Programming Language, Fourth Edition ,请读者自己体会吧!

*******************************************************************************

a finally clause is always entered with a reason. That reason may be that the try code finished normally, that it executed a control flow statement such as return, or that an exception was thrown in code executed in the Try block. The reason is remembered when the finally clause exits by falling out the bottom. However, if the finally block creates its own reason to leave by executing a control flow statement (such as break or return) or by throwing an exception, that reason supersedes the original one, and the original reason is forgotten. For example, consider the following code:
try {
// … do something … 
return 1;
} finally {
return 2;
}
When the Try block executes its return, the finally block is entered with the “reason” of returning the value 1. However, inside the finally block the value 2 is returned, so the initial intention is forgotten. In fact, if any of the other code in the try block had thrown an exception, the result would still be to return 2. If the finally block did not return a value but simply fell out the bottom, the “return the value 1 ″ reason would be remembered and carried out.

*******************************************************************************

好了,有了以上的知识,让我们再来看以下 3 个例子。


清单 7.
				
 public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
	 } 

 @SuppressWarnings("finally") 
 public static int getValue() { 
        int i = 1; 
        try { 
                 i = 4; 
        } finally { 
                 i++; 
                 return i; 
        } 
	 } 
 } 

清单 7 的执行结果:

 return value of getValue(): 5 


清单 8.
				
 public class Test { 
 public static void main(String[] args) { 
        System.out.println("return value of getValue(): " + getValue()); 
	 } 

 public static int getValue() { 
        int i = 1; 
        try { 
                 i = 4; 
        } finally { 
                 i++; 
        } 
        
        return i; 
	 } 
 } 

清单 8 的执行结果:

 return value of getValue(): 5 

清单 7 和清单 8 应该还比较简单吧!利用我们上面讲解的知识,很容易分析出其结果。让我们再来看一个稍微复杂一点的例子 – 清单 9。我建议大家最好先不要看执行结果,运用学过的知识来分析一下,看是否能推断出正确的结果。


清单 9.
				
 public class Test { 
 public static void main(String[] args) {  
 System.out.println(test());  
	 }  

 public static String test() {  
 try {  
 System.out.println("try block");  
 return test1();  
 } finally {  
 System.out.println("finally block");  
		 }  
	 }  
 public static String test1() {  
 System.out.println("return statement");  
 return "after return";  
	 }  
 } 

清单 9 的结果:

 try block 
 return statement 
 finally block 
 after return 

你分析对了吗?其实这个案例也不算很难,return test1();这条语句等同于 :

 String tmp = test1(); 
 return tmp; 

这样,就应该清楚为什么是上面所示的执行结果了吧!

好了,就写到这吧!希望大家看完这篇文章能够有所收获!

总结

没想到吧!一个小小的、看似简单的 finally 语句块背后居然隐藏了这么多玄机。看来,我们平时还是应该认真的阅读 Java 相关的基础文档,比如:Java 语言规范、Java 虚拟机规范等,很多棘手的问题都可以从中得到答案。只有真正的吃透了基础知识,才能达到运用自如的境界!


参考资料

学习

分享到:
评论

相关推荐

    TCABEE 008-2020 轨道交通车站高效空调系统技术标准.docx

    TCABEE 008-2020 轨道交通车站高效空调系统技术标准.docx

    node-v5.12.0-x86.msi

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于阿里云的 ECS 部署 Ghost 博客内含源码以及说明书可以自己运行复现.zip

    基于阿里云的 ECS 部署 Ghost 博客内含源码以及说明书可以自己运行复现.zip

    2023-04-06-项目笔记 - 第一百二十六阶段 - 4.4.2.124全局变量的作用域-124 -2024.05.07

    2023-04-06-项目笔记-第一百二十六阶段-课前小分享_小分享1.坚持提交gitee 小分享2.作业中提交代码 小分享3.写代码注意代码风格 4.3.1变量的使用 4.4变量的作用域与生命周期 4.4.1局部变量的作用域 4.4.2全局变量的作用域 4.4.2.1全局变量的作用域_1 4.4.2.124全局变量的作用域_124 - 2024-05-07

    node-v10.4.1-linux-armv7l.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    66297787762147sf22346mod.apk

    66297787762147sf22346mod.apk

    对k8s的client的封装

    对k8s的client的封装

    node-v7.7.3-linux-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Java学习路线逐步成为资深的Java专家.pdf

    上文对于初学者学习Java具有非常重要的指导作用。以下是对初学者学习Java的具体作用: 明确学习方向: 学习路线为初学者提供了一个清晰的学习方向,使他们能够系统地掌握Java从基础到进阶再到企业级开发的核心知识和技能。这有助于初学者避免在学习过程中迷失方向,减少无效学习的时间。 阶梯式学习: 学习路线按照从易到难的顺序,将Java知识划分为不同的阶段,每个阶段都有明确的学习目标和内容。这种阶梯式的学习方式有助于初学者逐步建立起对Java的深入理解和应用能力,提高学习效率。 理论与实践相结合: 学习路线不仅包含了理论知识的学习,还强调了实践项目的重要性。通过实践项目,初学者可以将所学知识应用到实际开发中,加深对知识的理解和掌握。这种理论与实践相结合的学习方式有助于培养初学者的动手能力和解决问题的能力。 技能拓展与提升: 学习路线不仅涵盖了Java语言的基础和进阶知识,还涉及了Java Web开发、企业级开发、性能优化与分布式系统等高级主题。这些高级主题的学习有助于初学者拓展自己的技能范围,提升在Java领域的竞争力。 激发学习兴趣: 通过不断挑战自己,尝试构建更复杂的项目或系

    2024-2030中国SDS-PAGE蛋白质分析市场现状研究分析与发展前景预测报告.docx

    2024-2030中国SDS-PAGE蛋白质分析市场现状研究分析与发展前景预测报告

    node-v9.0.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    遥感图像滤波处理(去除条带噪声、PCA变换、数据融合、图像平滑、图像锐化)

    1.1 实习目的 1) 掌握图像滤波的基本定义及目的。 2) 掌握进行图像的空间域滤波、频率域滤波的基本原理及方法。 3) 掌握傅立叶变换及逆变换的基本原理方法。 4) 掌握主成分变换的基本原理方法。 5) 学习图像融合和图像锐化基本处理。 1.2 实习内容 1) 使用频率域滤波去除spot周期性条带。 2) 使用波段运算计算大气校正结果的NDVI、NDWI、NDBI。 3) 使用L7数据做PCA变换,查看各主成分信息,使用前三波段进行数据压缩。 4) 使用L7和pan做数据融合。 5) 使用IKONOS数据做高斯噪声和椒盐噪声的去除,比较不同滤波方法的差异,不同尺寸的影响。 6) 使用IKONOS数据做图像锐化,分别使用Robert、SOBEL、Laplacian算子和方向滤波进行处理。

    node-v7.10.0-sunos-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    JDBC 入门教程内含源码以及说明书可以自己运行复现.zip

    JDBC 入门教程内含源码以及说明书可以自己运行复现.zip

    node-v8.11.3-linux-armv7l.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Microsoft365-E5-Renew-Plus-with-runtime.rar

    Microsoft365-E5-Renew-Plus-with-runtime.rar

    基于java的个人博客项目,支持文章发布和管理

    一个个人博客项目。适合用于个人或小型团队分享知识和经验。特点是可以提供一个简洁的博客平台,支持文章发布和管理。 数据库文件目录为static-files/my_blog_db.sql; 部署后你可以根据自己需求修改版权文案、logo 图片、备案记录等网站基础信息; My Blog 后台管理系统的默认登陆账号为 admin 默认登陆密码为 123456;

    为工控设备增加温度、时钟显示电路源程序.rar

    单片机学习代码资料

    VB开发的VIN实用的计算器项目+源码

    VB开发的VIN实用的计算器项目+源码. VB开发的VIN实用的计算器项目+源码. VB开发的VIN实用的计算器项目+源码. VB开发的VIN实用的计算器项目+源码. VB开发的VIN实用的计算器项目+源码. VB开发的VIN实用的计算器项目+源码.

    2024年中国超声波凝固和切割装置行业研究报告.docx

    2024年中国超声波凝固和切割装置行业研究报告

Global site tag (gtag.js) - Google Analytics