Java中的Chaining方法是否缓慢? [英] Is Chaining methods in Java slow?
问题描述
假设我有一个对象A可以调用getB()调用getC()调用getD()调用doSomething ...
现在我想在我的应用程序中使用D多重时间的一些方法,即
Suppose I have an object A which can call getB() call getC() call getD() call doSomething... Now I want to use some methods of the D multipe times in my application i.e
A.getB().getC().getD().doSomething1();
A.getB().getC().getD().doSomething2();
A.getB().getC().getD().doSomething3();
。我知道最好创建一个变量 d = A.getB()。getC()。getD()
然后使用它。但是我想知道链接这样的多种方法是否有任何性能问题。
. I know it's best to create a variable d = A.getB().getC().getD()
and then use it. But I wonder if there's any performance issue with chaining multiple methods like this.
class A {
private B b;
B getB() {
return b;
}
}
class B {
private C c;
C getC() {
return c;
}
}
class C {
private D d;
D getD() {
return d;
}
}
class D {
void doSomething1(){};
void doSomething2(){};
void doSomething3(){};
}
推荐答案
虽然你提到你已经知道它:你应该避免这么长的方法链,原因如下:
Although you mentioned that you already know it: You should avoid such long method chains, for several reasons:
- 这表明OO设计不好(更准确地说,它看起来如违反德米特法则)
- 这是容易出错。如果一行像
a.getB()。getC()。getD()。doSomething()
抛出NullPointerException
调试这个你没有乐趣...... - 让我在这里有点主观:它看起来很可怕。
- It's an indication of bad OO design (more precisely, it looks like a violation of the Law Of Demeter)
- It's error-prone. If a line like
a.getB().getC().getD().doSomething()
throws aNullPointerException
, you'll have no fun with debugging this... - Let me be a bit subjective here: It looks horrible.
当然,您必须考虑实际上方法正在做什么。虽然 get
-method通常只返回一个值,但你不知道它是否真的这样做了。甚至类似
Of course, you have to consider what the methods are doing actually. Although a get
-method should usually just return a value, you don't know whether it really does this. Even something like
List<T> getList() {
// Return an unmodifiable view to the caller
return Collections.unmodifiableList(internalList);
}
(当然 是一个好习惯)改变结果。
(which certainly is a good practice) may change the outcome.
话虽如此,考虑到这些 get
-methods实际上只是简单,愚蠢的 Getters :
That being said, and considering that these get
-methods are really only plain, stupid Getters:
它在实践中对性能没有影响。方法调用将由JIT内联。
It does not have an impact on performance in practice. The method calls will be inlined by the JIT.
例如,请考虑以下程序:
As an example, consider the following program:
class ChainA {
private ChainB b = new ChainB();
ChainB getB() {
return b;
}
}
class ChainB {
private ChainC c = new ChainC();
ChainC getC() {
return c;
}
}
class ChainC {
private ChainD d = new ChainD();
ChainD getD() {
return d;
}
}
class ChainD {
private int result = 0;
int getResult() { return result; }
void doSomething1(){ result += 1; }
void doSomething2(){ result += 2; }
void doSomething3(){ result += 3; }
}
class Chaining
{
public static void main(String args[])
{
for (int n=100; n<10000; n+=100)
{
ChainA a0 = new ChainA();
runChained(a0, n);
System.out.println(a0.getB().getC().getD().getResult());
ChainA a1 = new ChainA();
runUnChained(a1, n);
System.out.println(a1.getB().getC().getD().getResult());
}
}
private static void runChained(ChainA a, int n)
{
for (int i=0; i<n; i++)
{
a.getB().getC().getD().doSomething1();
a.getB().getC().getD().doSomething2();
a.getB().getC().getD().doSomething3();
}
}
private static void runUnChained(ChainA a, int n)
{
ChainD d = a.getB().getC().getD();
for (int i=0; i<n; i++)
{
d.doSomething1();
d.doSomething2();
d.doSomething3();
}
}
}
它表示像你所描述的那样的呼叫,一次是链式呼叫,一次是一个非链接的版本。
It peforms the calls like you described them, once as a chained call, and once as an unchained version.
运行它
java -server -XX:+ UnlockDiagnosticVMOptions -XX :+ TraceClassLoading -XX:+ LogCompilation -XX:+ PrintAssembly Chaining
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly Chaining
在启用HotSpot-Disassembler的JVM上产生以下输出(无需读取它,仅供参考)
on a HotSpot-Disassembler-enabled JVM results in the following output (no need to read it, just for reference)
runChained:
Decoding compiled method 0x0000000002885d50:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x0000000055360550} 'runChained' '(LChainA;I)V' in 'Chaining'
# parm0: rdx:rdx = 'ChainA'
# parm1: r8 = int
# [sp+0x30] (sp of caller)
0x0000000002885e80: mov %eax,-0x6000(%rsp)
0x0000000002885e87: push %rbp
0x0000000002885e88: sub $0x20,%rsp ;*synchronization entry
; - Chaining::runChained@-1 (line 47)
0x0000000002885e8c: mov %rdx,%r9
0x0000000002885e8f: mov %r8d,%ebx
0x0000000002885e92: test %r8d,%r8d
0x0000000002885e95: jle 0x0000000002885f8a ;*if_icmpge
; - Chaining::runChained@4 (line 47)
0x0000000002885e9b: mov 0xc(%rdx),%r11d ;*getfield b
; - ChainA::getB@1 (line 4)
; - Chaining::runChained@8 (line 49)
; implicit exception: dispatches to 0x0000000002885f96
0x0000000002885e9f: mov 0xc(%r11),%r10d ;*getfield c
; - ChainB::getC@1 (line 10)
; - Chaining::runChained@11 (line 49)
; implicit exception: dispatches to 0x0000000002885f96
0x0000000002885ea3: mov 0xc(%r10),%edx ;*getfield d
; - ChainC::getD@1 (line 16)
; - Chaining::runChained@14 (line 49)
; implicit exception: dispatches to 0x0000000002885f96
0x0000000002885ea7: mov 0xc(%rdx),%r11d ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runChained@17 (line 49)
; implicit exception: dispatches to 0x0000000002885f96
0x0000000002885eab: xor %r10d,%r10d
0x0000000002885eae: xor %esi,%esi
0x0000000002885eb0: xor %r8d,%r8d
0x0000000002885eb3: xor %ecx,%ecx ;*aload_0
; - Chaining::runChained@7 (line 49)
0x0000000002885eb5: add %r11d,%esi ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runChained@17 (line 49)
0x0000000002885eb8: add %ecx,%r8d
0x0000000002885ebb: mov %esi,%eax
0x0000000002885ebd: add $0x6,%eax ;*iadd
; - ChainD::doSomething3@6 (line 25)
; - Chaining::runChained@43 (line 51)
0x0000000002885ec0: mov %eax,0xc(%rdx) ;*putfield result
; - ChainD::doSomething3@7 (line 25)
; - Chaining::runChained@43 (line 51)
0x0000000002885ec3: mov %r10d,%edi
0x0000000002885ec6: inc %edi ;*iinc
; - Chaining::runChained@46 (line 47)
0x0000000002885ec8: cmp $0x1,%edi
0x0000000002885ecb: jge 0x0000000002885eee ;*if_icmpge
; - Chaining::runChained@4 (line 47)
0x0000000002885ecd: mov %r10d,%ecx
0x0000000002885ed0: shl %ecx
0x0000000002885ed2: mov %r8d,%esi
0x0000000002885ed5: add $0x6,%esi
0x0000000002885ed8: mov %ecx,%r8d
0x0000000002885edb: add $0x2,%r8d
0x0000000002885edf: shl $0x2,%r10d
0x0000000002885ee3: mov %r10d,%ecx
0x0000000002885ee6: add $0x4,%ecx
0x0000000002885ee9: mov %edi,%r10d
0x0000000002885eec: jmp 0x0000000002885eb5
0x0000000002885eee: mov %ebx,%r11d
0x0000000002885ef1: add $0xfffffff1,%r11d
0x0000000002885ef5: mov $0x80000000,%r9d
0x0000000002885efb: cmp %r11d,%ebx
0x0000000002885efe: cmovl %r9d,%r11d
0x0000000002885f02: cmp %r11d,%edi
0x0000000002885f05: jge 0x0000000002885f3c
0x0000000002885f07: sub %r8d,%esi
0x0000000002885f0a: nopw 0x0(%rax,%rax,1) ;*aload_0
; - Chaining::runChained@7 (line 49)
0x0000000002885f10: mov %edi,%r8d
0x0000000002885f13: shl %r8d
0x0000000002885f16: mov %edi,%r9d
0x0000000002885f19: shl $0x2,%r9d
0x0000000002885f1d: add %r9d,%r8d
0x0000000002885f20: add %esi,%r8d ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runChained@17 (line 49)
0x0000000002885f23: mov %r8d,%eax
0x0000000002885f26: add $0x60,%eax ;*iadd
; - ChainD::doSomething3@6 (line 25)
; - Chaining::runChained@43 (line 51)
0x0000000002885f29: add $0x5a,%r8d
0x0000000002885f2d: mov %r8d,0xc(%rdx)
0x0000000002885f31: mov %eax,0xc(%rdx) ;*putfield result
; - ChainD::doSomething3@7 (line 25)
; - Chaining::runChained@43 (line 51)
0x0000000002885f34: add $0x10,%edi ;*iinc
; - Chaining::runChained@46 (line 47)
0x0000000002885f37: cmp %r11d,%edi
0x0000000002885f3a: jl 0x0000000002885f10 ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runChained@17 (line 49)
0x0000000002885f3c: cmp %ebx,%edi
0x0000000002885f3e: jge 0x0000000002885f8a
0x0000000002885f40: mov %edi,%r10d
0x0000000002885f43: shl $0x2,%r10d
0x0000000002885f47: mov %edi,%r11d
0x0000000002885f4a: shl %r11d
0x0000000002885f4d: mov %r11d,%r8d
0x0000000002885f50: add %r10d,%r8d
0x0000000002885f53: sub %r8d,%eax
0x0000000002885f56: xchg %ax,%ax ;*aload_0
; - Chaining::runChained@7 (line 49)
0x0000000002885f58: add %r11d,%r10d
0x0000000002885f5b: add %eax,%r10d
0x0000000002885f5e: add $0x6,%r10d
0x0000000002885f62: mov %r10d,0xc(%rdx) ;*putfield result
; - ChainD::doSomething3@7 (line 25)
; - Chaining::runChained@43 (line 51)
0x0000000002885f66: mov %edi,%r8d
0x0000000002885f69: inc %r8d ;*iinc
; - Chaining::runChained@46 (line 47)
0x0000000002885f6c: cmp %ebx,%r8d
0x0000000002885f6f: jge 0x0000000002885f8a
0x0000000002885f71: mov %edi,%r10d
0x0000000002885f74: shl $0x2,%r10d
0x0000000002885f78: shl %edi
0x0000000002885f7a: add $0x4,%r10d
0x0000000002885f7e: mov %edi,%r11d
0x0000000002885f81: add $0x2,%r11d
0x0000000002885f85: mov %r8d,%edi
0x0000000002885f88: jmp 0x0000000002885f58 ;*if_icmpge
; - Chaining::runChained@4 (line 47)
0x0000000002885f8a: add $0x20,%rsp
0x0000000002885f8e: pop %rbp
0x0000000002885f8f: test %eax,-0x26c5f95(%rip) # 0x00000000001c0000
; {poll_return}
0x0000000002885f95: retq
0x0000000002885f96: mov $0xffffff86,%edx
0x0000000002885f9b: mov %r9,%rbp
0x0000000002885f9e: mov %r8d,(%rsp)
0x0000000002885fa2: nop
0x0000000002885fa3: callq 0x00000000027b7320 ; OopMap{rbp=Oop off=296}
;*aload_0
; - Chaining::runChained@7 (line 49)
; {runtime_call}
0x0000000002885fa8: int3 ;*aload_0
; - Chaining::runChained@7 (line 49)
0x0000000002885fa9: hlt
0x0000000002885faa: hlt
0x0000000002885fab: hlt
0x0000000002885fac: hlt
0x0000000002885fad: hlt
0x0000000002885fae: hlt
0x0000000002885faf: hlt
0x0000000002885fb0: hlt
0x0000000002885fb1: hlt
0x0000000002885fb2: hlt
0x0000000002885fb3: hlt
0x0000000002885fb4: hlt
0x0000000002885fb5: hlt
0x0000000002885fb6: hlt
0x0000000002885fb7: hlt
0x0000000002885fb8: hlt
0x0000000002885fb9: hlt
0x0000000002885fba: hlt
0x0000000002885fbb: hlt
0x0000000002885fbc: hlt
0x0000000002885fbd: hlt
0x0000000002885fbe: hlt
0x0000000002885fbf: hlt
[Exception Handler]
[Stub Code]
0x0000000002885fc0: jmpq 0x00000000028694a0 ; {no_reloc}
[Deopt Handler Code]
0x0000000002885fc5: callq 0x0000000002885fca
0x0000000002885fca: subq $0x5,(%rsp)
0x0000000002885fcf: jmpq 0x00000000027b6f40 ; {runtime_call}
0x0000000002885fd4: hlt
0x0000000002885fd5: hlt
0x0000000002885fd6: hlt
0x0000000002885fd7: hlt
runUnChained:
Decoding compiled method 0x00000000028893d0:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x0000000055360628} 'runUnChained' '(LChainA;I)V' in 'Chaining'
# parm0: rdx:rdx = 'ChainA'
# parm1: r8 = int
# [sp+0x30] (sp of caller)
0x0000000002889500: mov %eax,-0x6000(%rsp)
0x0000000002889507: push %rbp
0x0000000002889508: sub $0x20,%rsp ;*synchronization entry
; - Chaining::runUnChained@-1 (line 57)
0x000000000288950c: mov 0xc(%rdx),%r11d ;*getfield b
; - ChainA::getB@1 (line 4)
; - Chaining::runUnChained@1 (line 57)
; implicit exception: dispatches to 0x0000000002889612
0x0000000002889510: mov 0xc(%r11),%r10d ;*getfield c
; - ChainB::getC@1 (line 10)
; - Chaining::runUnChained@4 (line 57)
; implicit exception: dispatches to 0x000000000288961d
0x0000000002889514: mov 0xc(%r10),%ebx ;*getfield d
; - ChainC::getD@1 (line 16)
; - Chaining::runUnChained@7 (line 57)
; implicit exception: dispatches to 0x0000000002889629
0x0000000002889518: mov %r8d,%esi
0x000000000288951b: test %r8d,%r8d
0x000000000288951e: jle 0x0000000002889606 ;*if_icmpge
; - Chaining::runUnChained@15 (line 58)
0x0000000002889524: mov 0xc(%rbx),%r10d ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runUnChained@19 (line 60)
; implicit exception: dispatches to 0x0000000002889635
0x0000000002889528: xor %r8d,%r8d
0x000000000288952b: xor %edx,%edx
0x000000000288952d: xor %r9d,%r9d
0x0000000002889530: xor %r11d,%r11d ;*aload_2
; - Chaining::runUnChained@18 (line 60)
0x0000000002889533: add %r10d,%edx ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runUnChained@19 (line 60)
0x0000000002889536: add %r11d,%r9d
0x0000000002889539: mov %edx,%edi
0x000000000288953b: add $0x6,%edi ;*iadd
; - ChainD::doSomething3@6 (line 25)
; - Chaining::runUnChained@27 (line 62)
0x000000000288953e: mov %edi,0xc(%rbx) ;*putfield result
; - ChainD::doSomething3@7 (line 25)
; - Chaining::runUnChained@27 (line 62)
0x0000000002889541: mov %r8d,%ecx
0x0000000002889544: inc %ecx ;*iinc
; - Chaining::runUnChained@30 (line 58)
0x0000000002889546: cmp $0x1,%ecx
0x0000000002889549: jge 0x000000000288956e ;*if_icmpge
; - Chaining::runUnChained@15 (line 58)
0x000000000288954b: mov %r8d,%r11d
0x000000000288954e: shl %r11d
0x0000000002889551: mov %r9d,%edx
0x0000000002889554: add $0x6,%edx
0x0000000002889557: mov %r11d,%r9d
0x000000000288955a: add $0x2,%r9d
0x000000000288955e: shl $0x2,%r8d
0x0000000002889562: mov %r8d,%r11d
0x0000000002889565: add $0x4,%r11d
0x0000000002889569: mov %ecx,%r8d
0x000000000288956c: jmp 0x0000000002889533
0x000000000288956e: mov %esi,%r10d
0x0000000002889571: add $0xfffffff1,%r10d
0x0000000002889575: mov $0x80000000,%r11d
0x000000000288957b: cmp %r10d,%esi
0x000000000288957e: cmovl %r11d,%r10d
0x0000000002889582: cmp %r10d,%ecx
0x0000000002889585: jge 0x00000000028895bc
0x0000000002889587: sub %r9d,%edx
0x000000000288958a: nopw 0x0(%rax,%rax,1) ;*aload_2
; - Chaining::runUnChained@18 (line 60)
0x0000000002889590: mov %ecx,%r11d
0x0000000002889593: shl %r11d
0x0000000002889596: mov %ecx,%r8d
0x0000000002889599: shl $0x2,%r8d
0x000000000288959d: add %r8d,%r11d
0x00000000028895a0: add %edx,%r11d ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runUnChained@19 (line 60)
0x00000000028895a3: mov %r11d,%edi
0x00000000028895a6: add $0x60,%edi ;*iadd
; - ChainD::doSomething3@6 (line 25)
; - Chaining::runUnChained@27 (line 62)
0x00000000028895a9: add $0x5a,%r11d
0x00000000028895ad: mov %r11d,0xc(%rbx)
0x00000000028895b1: mov %edi,0xc(%rbx) ;*putfield result
; - ChainD::doSomething3@7 (line 25)
; - Chaining::runUnChained@27 (line 62)
0x00000000028895b4: add $0x10,%ecx ;*iinc
; - Chaining::runUnChained@30 (line 58)
0x00000000028895b7: cmp %r10d,%ecx
0x00000000028895ba: jl 0x0000000002889590 ;*getfield result
; - ChainD::doSomething1@2 (line 23)
; - Chaining::runUnChained@19 (line 60)
0x00000000028895bc: cmp %esi,%ecx
0x00000000028895be: jge 0x0000000002889606
0x00000000028895c0: mov %ecx,%r11d
0x00000000028895c3: shl $0x2,%r11d
0x00000000028895c7: mov %ecx,%r8d
0x00000000028895ca: shl %r8d
0x00000000028895cd: mov %r8d,%r9d
0x00000000028895d0: add %r11d,%r9d
0x00000000028895d3: sub %r9d,%edi
0x00000000028895d6: xchg %ax,%ax ;*aload_2
; - Chaining::runUnChained@18 (line 60)
0x00000000028895d8: add %r8d,%r11d
0x00000000028895db: add %edi,%r11d
0x00000000028895de: add $0x6,%r11d
0x00000000028895e2: mov %r11d,0xc(%rbx) ;*putfield result
; - ChainD::doSomething3@7 (line 25)
; - Chaining::runUnChained@27 (line 62)
0x00000000028895e6: mov %ecx,%edx
0x00000000028895e8: inc %edx ;*iinc
; - Chaining::runUnChained@30 (line 58)
0x00000000028895ea: cmp %esi,%edx
0x00000000028895ec: jge 0x0000000002889606
0x00000000028895ee: mov %ecx,%r11d
0x00000000028895f1: shl $0x2,%r11d
0x00000000028895f5: shl %ecx
0x00000000028895f7: add $0x4,%r11d
0x00000000028895fb: mov %ecx,%r8d
0x00000000028895fe: add $0x2,%r8d
0x0000000002889602: mov %edx,%ecx
0x0000000002889604: jmp 0x00000000028895d8 ;*if_icmpge
; - Chaining::runUnChained@15 (line 58)
0x0000000002889606: add $0x20,%rsp
0x000000000288960a: pop %rbp
0x000000000288960b: test %eax,-0x26c9611(%rip) # 0x00000000001c0000
; {poll_return}
0x0000000002889611: retq
0x0000000002889612: mov $0xfffffff6,%edx
0x0000000002889617: callq 0x00000000027b7320 ; OopMap{off=284}
;*invokevirtual getB
; - Chaining::runUnChained@1 (line 57)
; {runtime_call}
0x000000000288961c: int3 ;*invokevirtual getB
; - Chaining::runUnChained@1 (line 57)
0x000000000288961d: mov $0xfffffff6,%edx
0x0000000002889622: nop
0x0000000002889623: callq 0x00000000027b7320 ; OopMap{off=296}
;*invokevirtual getC
; - Chaining::runUnChained@4 (line 57)
; {runtime_call}
0x0000000002889628: int3 ;*invokevirtual getC
; - Chaining::runUnChained@4 (line 57)
0x0000000002889629: mov $0xfffffff6,%edx
0x000000000288962e: nop
0x000000000288962f: callq 0x00000000027b7320 ; OopMap{off=308}
;*invokevirtual getD
; - Chaining::runUnChained@7 (line 57)
; {runtime_call}
0x0000000002889634: int3 ;*invokevirtual getD
; - Chaining::runUnChained@7 (line 57)
0x0000000002889635: mov $0xffffff86,%edx
0x000000000288963a: mov %ebx,%ebp
0x000000000288963c: mov %r8d,(%rsp)
0x0000000002889640: data32 xchg %ax,%ax
0x0000000002889643: callq 0x00000000027b7320 ; OopMap{rbp=NarrowOop off=328}
;*aload_2
; - Chaining::runUnChained@18 (line 60)
; {runtime_call}
0x0000000002889648: int3 ;*aload_2
; - Chaining::runUnChained@18 (line 60)
0x0000000002889649: hlt
0x000000000288964a: hlt
0x000000000288964b: hlt
0x000000000288964c: hlt
0x000000000288964d: hlt
0x000000000288964e: hlt
0x000000000288964f: hlt
0x0000000002889650: hlt
0x0000000002889651: hlt
0x0000000002889652: hlt
0x0000000002889653: hlt
0x0000000002889654: hlt
0x0000000002889655: hlt
0x0000000002889656: hlt
0x0000000002889657: hlt
0x0000000002889658: hlt
0x0000000002889659: hlt
0x000000000288965a: hlt
0x000000000288965b: hlt
0x000000000288965c: hlt
0x000000000288965d: hlt
0x000000000288965e: hlt
0x000000000288965f: hlt
[Exception Handler]
[Stub Code]
0x0000000002889660: jmpq 0x00000000028694a0 ; {no_reloc}
[Deopt Handler Code]
0x0000000002889665: callq 0x000000000288966a
0x000000000288966a: subq $0x5,(%rsp)
0x000000000288966f: jmpq 0x00000000027b6f40 ; {runtime_call}
0x0000000002889674: hlt
0x0000000002889675: hlt
0x0000000002889676: hlt
0x0000000002889677: hlt
通过比较输出,可以看出它们基本相同。在这两种情况下都有内联调用。
By comparing the outputs, it can be seen that they are essentially equal. The calls have been inlined in both cases.
现在可以说 doSomething
方法是如此微不足道他们也被内联,并且对于更复杂的 doSomething
方法,结果可能会有所不同。这可能是真的。但是使用像
One could now argue that the doSomething
methods are so trivial that they have been inlined as well, and the results may be different for more complex doSomething
methods. This may be true. But a quick test with a method like
int doSomething(int i)
{
List<Integer> list = new ArrayList<Integer>(
Arrays.asList(1,2,3,4,5,6,7,8,9,10));
Collections.sort(list);
return list.get(i);
}
表明链接调用的实际内联仍然会发生,并且当内部方法变得更加复杂,由于链式调用而导致可能发生的任何潜在开销与 doSomething
中所做的相比将变得可以忽略不计方法。
shows that the actual inlining of the chained calls still happen, and when the "inner" methods become even more complex, any potential overhead that might occur due to the chained calls will become negligible compared to what is done in the doSomething
method.
这篇关于Java中的Chaining方法是否缓慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!