JavaSE基础知识(十七)--Java复用代码之关键字final的详细描述

本文主要是介绍JavaSE基础知识(十七)--Java复用代码之关键字final的详细描述,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java SE 是什么,包括哪些内容(十七)?

本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
从某个方面来讲,关键字final的作用与关键字protected的作用是相反的。
根据上下文环境,Java的关键字final的含义和作用存在着细微的差别。但通常它之的是:这个内容是无法改变的
不想被改变可能处于两个原因:设计和效率。由于这两个方面相差的比较远,所以关键字final可能会被误用。
下面从三个发面来描述关键字final的具体作用。

1、数据

许多的编程语言都会通过某种方式告知编译器某块数据区域是恒定不变的。有时,数据的恒定不变是很有用的,比如:
⑴、一个永不改变的编译时常量(也就是在编译器就确定了值)。
⑵、一个在运行期被初始化的值,而你希望它的值再也不能被改变(虽然值在运行期的时候才被确定,但是不想再被改变了)。
对于编译器常量这种情况,编译器可以将该常量值带入任何可能用到它的计算式中,也就是说,可以在编译期执行计算式(实际上就是这个计算式的结果有可能在编译期就已经得出了),这减轻了运行时的负担–在Java中,这类常量必须是基本数据类型,并且以关键字final修饰,同时,必须在声明这个常量的时候就赋值,否则它的值将永远是默认值
如果一个变量同时被static和final修饰(static必须在final前面!),那么它将会占据一段不能改变的存储空间(static表示与对象实例无关,那么就与类相关,类只有唯一的一个,它的存储空间就是唯一的,所以这个域的存储空间也是唯一的,final仅表示不变。)。
当你把对象引用而不是基本类型用final修饰的时候,其含义会有一些迷惑,因为对于基本类型,表示它的值不能被修改,而对于引用,它表示使引用恒定不变,也就是一个引用一旦被初始化指向了一个对象,它无法再被修改指向另一个对象(但是这个对象本身的内容是可以改变的,比如你可以给它的类域重新赋值等等,你需要记住这一点)。
实际上,Java并未提供任何使**对象(不是类!)**本身恒定不变的方法(但是你可以通过编写类来使对象保持恒定不变),这一限制同样适用于数组引用,因为它也是对象(final无法限制数组本身存储的内容保持不变,你仍然可以通过数组下标给数组重新赋值)。
下面通过一个示例来说明final修饰类域的情况:
PS:根据惯例,同时被static和final修饰的变量是一个常量(编译期常量),将用大写表示,如果有多个单词,单词之间用下划线分隔。
代码示例:

// final修饰变量//类Valueclass Value{//int类型的类变量iint i;//构造方法,带一个int类型的形式参数ipublic Value(int i){//初始化类变量ithis.i = i;}}//类FinalDatapublic class FinalData{//Java中的随机类Random,能生成随机数,创建对象的时候//需要一个int类型的数作为随机因子private static final Random rand = new Random(47);//String类型的类变量idprivate String id;//构造方法,带一个String类型的形式参数idprivate FinalData(String id){//初始化类变量idthis.id = id;}//final修饰的类变量valueOne,初始化值为9,那它的值就恒定不变为9,不能再被改变。//虽然它没有被static修饰,但是无论创建多少个对象,它的值都是9,不能改变。//因为它的值在编译期就确定了。private final int valueOne = 9;//final修饰的类变量VALUE_TWO,初始化值为99,那它的值就恒定不变为99,不能再被改变。//这里使用了关键字static,其实有没有它没什么很大的区别,有了static只是更加明确了//它的值与创建对象的多少都没关系,只与类相关。private static final int VALUE_TWO = 99;//同上public static final int VALUE_THREE = 39;//final修饰的类变量i4,但是在编译期不能得出i4的值,因为代码"rand.nextInt(20);"不会在//编译期运行,只有在运行期才会运行得出结果。所以这个变量的值是在运行期的时候被初始化的//所以它的值在每一个对象中都可能不同的,与对象相关,它在每个对象里面一旦被初始化就//不能再被改变了private final int i4 = rand.nextInt(20);//INT_5也是在运行期被初始化的,它与i4有重大区别,INT_5在初次被初始化之后,就不能再被//改变了,因为这里的static强调的是一份,所以INT_5与创建多少个对象无关,只与类相关。static final int INT_5 = rand.nextInt(20);//正常的组合,初始化了类Value的引用v1private Value v1 = new Value(11);//正常的组合,初始化了类Value的引用v2,只是因为有final的修饰,它不能再指向//另一个新对象了,但是在每一个FinalData对象里面都能被初始化一次!private final Value v2 = new Value(11);//正常的组合,初始化了类Value的引用VAL_3,只是因为有final的修饰,它不能再指向//另一个新对象了,同时又有static的修饰,它与类相关(与对象无关),只能初始化这一次!private static final Value VAL_3 = new Value(33);  //a也是对象的引用,同上private final int[] a = {1,2,3,4,5,6};//toString()方法public String toString(){//返回字符串"id"+":"+"i4="+i4+",INT_5="+INT_5;return "id"+":"+"i4="+i4+",INT_5="+INT_5;}//程序执行入口main方法public static void main(String[] args) {//创建FinalData类的对象,引用为fd1,初始化参数为"fd1"FinalData fd1 = new FinalData("fd1");//fd1.valueOne++;这句代码执行会报错,因为变量valueOne是final修饰的,在//定义的时候已经复制了,不能再改变。//下面这句代码是可以执行的,虽然v2是final修饰的,但是不妨碍修饰v2对象的内容。//仅仅只是v2不能再指向其它的对象了。fd1.v2.i++;//下面这句代码可以执行的,v1没有被final修饰,完全可以重新new一个对象赋值给它。fd1.v1 = new Value(9);//for循环for(int i = 0;i<fd1.a.length;i++){//fd1.a[i]代表的是数组的内容,虽然数组引用a被final修饰,但是//不妨碍给数组的内容重新赋值。fd1.a[i]++;}//下面这句代码不能执行,因为引用v2被final修饰了,不能重新赋值一个新对象给它。//fd1.v2 = new Value(0);//下面这句代码不能执行,因为引用VAL_3被final修饰了,不能重新赋值一个新对象给它。//fd1.VAL_3 = new Value(0);//下面这句代码不能执行,因为引用a被final修饰了,不能重新赋值一个新的数组对象给它。//fd1.a = new int[3];//打印引用fd1,实际上调用的是类FinalData的toString()方法。System.out.println(fd1);//打印字符串"Creating new FinalData"System.out.println("Creating new FinalData");//创建类FinalData的新对象fd2FinalData fd2 = new FinalData("fd2");//打印引用fd1,实际上调用的是类FinalData的toString()方法。System.out.println(fd1);//打印引用fd2,实际上调用的是类FinalData的toString()方法。System.out.println(fd2);}}

valueOneVALUE_TWO都是final修饰的基本数据类型数值,它们二者均为编译器常量,没有本质的区别。VALUE_THREE是一种更加典型的对常量的定义方式:因为:
1、定义为public,可以被用于包之外。
2、定义为static,则强调只有一份。
3、定义为final,则说明它是常量。
PS:带有恒定初始值的static final修饰的基本类型的命名,全部都用大写字母!如果有多个单词,单词之间用"_"分隔。
不能因为变量是final修饰的就认为在编译时可以知道它的值,因为,我们还可以在运行时对变量进行初始化操作(变量i4和INT_5就是在运行时通过随机对象Random进行了初始化),这种变量在编译期是无法确定它的值的。
同时i4INT_5两个变量的初始化还说明了将final数值定义为静态和非静态的区别(有static修饰就是静态的,没有就是非静态的),此区别只有在运行时初始化数值的时候才能显现(被static修饰的数值不论初始化多少次都不会变,比如INT_5),这是因为编译器对编译时数值一视同仁(但这种差异也有可能通过优化而消失),当程序运行的时候,你就注意到了这个区别。
PS:在对象fd1和fd2中,i4的值都是唯一的,但是INT_5的值是不能通过创建第二个FinalData对象而改变的,因为它是static的,在类加载的时候就已经被初始化了,而不是每次创建对象的时候被初始化。
static final修饰与final修饰的区别!
对象引用v1VAL_3说明了final修饰对象引用的意义,正如上面示例代码中看到的,虽然v2是被final修饰的,但是v2指向的那个对象的内容还是可以被改变的,由于v2是一个对象引用,final修饰它的意义在于不能让它重新再指向另一个对象,这对数组也是同样的意义。
暂时还没有什么办法能让数组对象本身成为final的!
从实际效果上看,用final修饰基本类型比用final修饰引用的作用要更大,效果也更直接和明显。
空白final
Java允许生成"空白final",所谓"空白final"是指被final修饰,但是未在声明处就初始化的变量,无论在什么情况下,编译器都会确保空白final在使用前被初始化!
空白final在关键字final的使用上提供了更大的灵活性,所以,一个类中的final域就可以做到根据对象的不同而有所不同,却又能保持其恒定不变的特性。
代码示例:

// 空白final//类Poppetclass Poppet{//int类型的类变量iprivate int i;//构造方法,带一个int类型的形式参数iiPoppet(int ii){//为类变量i赋值。i = ii;}}//类BlankFinalpublic class BlankFinal{//final修饰的类变量i,初始化为0private final int i = 0;//这就是空白final类变量j,用final修饰它,但是不初始化。private final int j;//这就是空白final类变量p,用final修饰它,但是不初始化。private final Poppet p;//构造方法一public BlankFinal(){//空白final变量一定要在构造方法里面进行初始化!//如果你不在这里初始化,那么Java的编译器就会通过默认初始化给它赋值了!//一般情况下这是没有什么意义的。i = 1;//同上。但是引用更繁琐一些,因为在这里不给对象的引用进行初始化,//它将被Java编译器默认初始化成null值,更没有意义。p = new Poppet(1);}//构造方法二,带一个int类型的形式参数public BlankFinal(int x){//空白final变量一定要在构造方法里面进行初始化!i = x;p = new Poppet(x);}//拥有空白final,就可以在多个构造方法里面进行初始化,同时使用它恒定不变的特性。//程序执行入口main方法public static void main(String[] args) {new BlankFinal();new BlankFinal(43);}}

如何保证final修饰的域能在使用前一定被初始化,因为你必须在声明final域的地方或者类的构造方法中堆它进行初始化!
final参数
Java允许在形式参数列表中将参数声明为final的!这意味着你无法在方法体内更改参数的值或者它引用的对象。
代码示例:

// final参数//类Gizmoclass Gizmo{//方法spin()public void spin(){}}//类FinalArgumentspublic class FinalArguments{//方法with(final Gizmo g),带一个final修饰的类Gizmo的形式参数gvoid with(final Gizmo g){//下面的代码不能执行,因为g是final修饰的,在方法体内不能r让//它再指向其他的Gizmo类对象//g = new Gizmo();}//方法with(final Gizmo g),带一个类Gizmo的形式参数gvoid with(Gizmo g){//下面的代码可以执行,因为g不是final的。g = new Gizmo();//调用方法spin()g.spin();}//方法f(final int i),带一个final修饰的int类型的形式参数ivoid f(final int i){//下面的代码不能执行,因为i是final的,它本身不能重新被赋值//但是可以参与某些运算.//i++;}//方法int(int i),带一个int类型的形式参数iint f(int i){//返回参数i+1的值。return i+1;}}

以上代码示例分别展示了当基本类型的参数被指明为final的结果,你可以读取参数,但是却无法修改参数,这里暂时作为一种了解,这种方式多用于向匿名内部类传递参数。

2、方法

将方法定义为final的意义有两个:
⑴、将方法锁定,让继承的子类不能再修改这个方法的实现。也就是说,如果你写了一个类是专门提供给客户端程序员使用的,但是你认为你的类中有那么几个方法的实现好的不能再好了,那么你就用final修饰这些方法,那么客户端程序员如果继承了你的类,他将不能修改这些方法的实现,只能直接用。
PS:多半是出于设计的考虑:想确保方法在继承关系中不被覆盖!
⑵、效率因素,在早期的Java实现中,如果将一个方法用final修饰,就是通知编译器将这个方法的调用改为内嵌调用。
当编译器发现一个final方法时,它会根据谨慎的判断,跳过插入程序代码这种正常方式,而执行方法调用机制(将参数压入栈,跳至方法代码处并执行,然后跳回并清理栈中的参数,处理返回值),并且以方法体中的实际代码副本来替代方法调用。以上的过程将消除方法调用的开销。
如果一个方法很大,程序代码必将会膨胀,因而可能看不到任何内嵌调用带来的性能提高,因为所带来的性能提高会因为花费于方法内的时间量而被缩减。
PS:第二点存在于Java SE版本5-6左右,现在都在使用Java SE10了,所以,只需要明确清楚第一点就行!
在最近的Java版本中,虚拟机(特别是hotspot技术)可以探测到这些情况,并优化去掉这些效率反而降低的额外的内嵌调用,因此不再需要通过final修饰方法来进行优化了,实际上,现在这样的做法应该受到劝阻。在使用Java的时候,应该让编译器和JVM去处理效率的问题。
还是那句话,现在使用final去修饰方法只有一个目的:明确方法禁止被覆盖!

final和private关键字
类中的所有private方法都隐式的指定为是final的,由于private方法在子类中根本就是不可见的,所以根本无法覆盖它,等同于是final的了。如果你在private方法的基础上增加关键字final,这并没有任何的实际意义。
但是在现实中,如果你试图去**"覆盖"一个private**方法(等同于是final的),似乎是奏效的,编译器也不会给出任何错误的提示信息。
代码示例:

// 覆盖一个private方法//类WithFinalsclass WithFinals{//private final方法f()private final void f(){//打印字符串"WithFinals.f()"System.out.println("WithFinals.f()");}//private方法g()private void g(){//打印字符串"WithFinals.g()"System.out.println("WithFinals.g()");}}//类OverriddingPrivate继承类WithFinalsclass OverriddingPrivate extends WithFinals{//private final方法f()private final void f(){//打印字符串"OverriddingPrivate.f()"//你需要知道的是这里并不是覆盖(重新实现)了父类的方法f(),//它是另外一个方法f(),与父类中的方法f()无关。System.out.println("OverriddingPrivate.f()");}private void g(){//打印字符串"OverriddingPrivate.g()"//你需要知道的是这里并不是覆盖(重新实现)了父类的方法g(),//它是另外一个方法f(),与父类中的方法g()无关。System.out.println("OverriddingPrivate.g()");}}//类OverriddingPrivate2继承类OverriddingPrivateclass OverriddingPrivate2 extends OverriddingPrivate{//public final方法f()public final void f(){//打印字符串"OverriddingPrivate2.f()"//你需要知道的是这里并不是覆盖(重新实现)了父类的方法f(),//它是另外一个方法f(),与父类中的方法f()无关。System.out.println("OverriddingPrivate2.f()");}//方法g()public void g(){//打印字符串"OverriddingPrivate2.g()"//你需要知道的是这里并不是覆盖(重新实现)了父类的方法g(),//它是另外一个方法f(),与父类中的方法g()无关。System.out.println("OverriddingPrivate2.g()");}}//类FinalOverriddingIllusionpublic class FinalOverriddingIllusion{//程序执行入口main方法public static void main(String[] args) {//创建类OverriddingPrivate2对象实例OverriddingPrivate2 op2 = new OverriddingPrivate2();//调用方法f();op2.f();//调用方法g();op2.g();//因为类OverriddingPrivate是类OverriddingPrivate2的父类,所以可以通过//引用的传递将op2指向的对象传递给类OverriddingPrivate的引用opOverriddingPrivate op = op2;//下面的代码不能执行,因为在类OverriddingPrivate中,方法f()是private的//op.f();//下面的代码不能执行,因为在类OverriddingPrivate中,方法g()是private的//op.g();//因为类WithFinals是类OverriddingPrivate2的父类,所以可以通过//引用的传递将op2指向的对象传递给类WithFinals的引用wWithFinals w = op2;//下面的代码不能执行,因为在类WithFinals中,方法f()是private的//w.f();//下面的代码不能执行,因为在类WithFinals中,方法g()是private的//w.g();}}

PS:上面的代码形式好像是子类覆盖了父类的方法,但是实际上并不是那么回事!仅仅是代码形式像而已!
继承关系中,方法的覆盖只有在某方法是类的接口的一部分时才会发生,即,必须能将一个对象向上转型为它的父类型,并调用相同名称的方法(上面的示例代码中,向上转型后,并不能调用同名的方法),如果某方法为private,它根本就不是类的接口的一部分,它仅仅只是隐藏在类中的一些代码而已(碰巧方法的名称相同而已),此时你并没有覆盖父类中的方法,而是产生了一个新方法。
由于private方法无法触及而且能有效隐藏,除了把它看成是类的一部分以外,其他内容都与它无关,不需要考虑!
如果类中的方法是public、protected、或者是包访问权限,那么它在子类中就能被覆盖!参考代码示例中类OverriddingPrivate2的public方法。

3、类

当你用final去修饰整个类的时候(将关键字final置于关键字class之前),就表明你不打算让这个类被继承。
PS:可能是处于某种考虑,你认为这个类的设计永远不需要做出任何改动,或者出于安全的需要,你认为这个类不应该有子类!
代码示例:

// final修饰类//类SmallBrainclass SmallBrain{}//final修饰的类Dinosaurfinal class Dinosaur{//int类型的类变量i,初始化值为7int i = 7;//int类型的类变量j,初始化值为1int j = 1;//创建类SmallBrain的对象SmallBrain x = new SmallBrain();//方法f()void f(){}}//下面的代码不能执行,因为final修饰的类Dinosaur不能被继承//class Further extends Dinosaur{}public class Jurassic{//程序执行入口main方法public static void main(String[] args) {//创建类Dinosaur的对象实例Dinosaur n = new Dinosaur();//调用方法f()n.f();//虽然类是final的,但是类变量的值还是可以改变的.n.i = 40;//虽然类是final的,但是类变量的值还是可以改变的.n.j++;}}

实际上final类的类变量你可以继续根据需要来设置它是否是final,无论类是否是final的,这都不影响类变量成为final的。
final类是不能被继承的,这也就等同于这个类的所有方法都不能被覆盖了。所以,如果你想给final类的方法添加final关键字,是没有任何实际意义的!

4、有关关键字final的忠告

在设计类的时候,将方法指定为final是明智的,你可能会觉得没人会想去覆盖你的方法,这可能是对的。
但是在实际中,要遇见类是如何被复用的是很困难的,特别是对于一个通用的类来说更是如此!如果将一个方法指定为final的,可能会妨碍其他程序员在项目中通过继承复用你的类。
Java的标准程序库就是一个很好的例子,特别是Java1.0/1.1中的Vector类被广泛运用,如果从效率的角度考虑,如果将所有的方法都被指定为final,它可能会更加有用,很容易想到,人们可能会想要继承并覆盖如此基础而有用的类,但是设计者可能会觉得如此做不太合适,有两个原因:
⑴、类Stack继承自Vector,就说Stack是一个Vector,这从逻辑的观点看是不正确的,尽管如此,Java的设计者们仍旧继承了Vector,在以这种方式创建Stack时,他们应该意识到final方法过于严苛了。
上面这句话可能会有点费解,可能是翻译的时候的逻辑问题,我来通顺的解释下:
类Stack是继承自类Vector,但是从逻辑上来讲,不能说类Stack就是一个Vector,因为这两个类的特点以及设计的出发点都不同,就目前为止看来,通过继承类Vector,类Stack曾今一度很好用,如果类Vector的方法全都是final的,也就没有类Stack了。所以将类Vector的所有方法都设置为final就过于严苛了,虽然它是一个基础好用的类,虽然你能遇见到它将被很多其他类继承,但是这样还是值得的。
类Stack继承自类Vector!
、Vector的许多重要方法–如addElement()和elementAt()是同步的(这里的同步指的是多线程控制的问题,开销特别大,有兴趣,待我写完了有关多线程的博文后,你们可以自行了解),这将导致很大的执行开销,基本就抵消了final带来的好处,这种情况增强了人们猜测程序员无法正确猜测优化应该发生在何处的观点。如此蹩脚的设计,却出现在了我们人人都要使用的标准程序库中,这是很糟糕的。
幸运的是,现代Java的容器库使用类ArraList代替了类Vector,ArrayList的行为要合理的多。(实际上也就是代码的实现逻辑更合理了!但是遗憾的是仍然存在使用旧容器库代码编写新程序代码的情况。)
还有另一个类:HashTable,这个例子同样有趣,它也是一个重要的Java1.0/1.1标准类库,它不含任何的final方法,实际上你去研究它的源码你会发现,整个类可能是由互不相关的人一起设计的(HashTable中的方法相对于Vector中的方法要简洁的多),这个类本不该如此轻率,这种情况只能让使用类的程序员需要付出更多的努力,简直就是粗糙的代码,粗糙的设计。
因为HashTable类设计以及代码都存在瞎搞的嫌疑,所以现代Java容器库用HashMap代替了HashTable(不要以为大神级别的人物就不会瞎搞)。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正。

这篇关于JavaSE基础知识(十七)--Java复用代码之关键字final的详细描述的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/236923

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面