Java的现代回顾


58

我从事编程已经有几年了,我从Java开始。在我那段时间里,我发现许多不同的来源声称Java在某种程度上是一种劣等语言。我很清楚每种语言都有其优点和缺点,但是我所读过的有关Java的很多东西似乎都是过时的。

Java劣势最常被引用的原因是它比其他本地编译语言(例如C ++)慢得多。许多人批评游戏设计师Notch(开发了Minecraft)使用Java,因为它显然缺乏性能部门。我知道Java的运行速度要慢得多,但是此后有了很多改进,尤其是JIT编译。

我今天想就Java作为一种语言获得一些客观的见解。所以我的问题分为四个部分。

  1. 性能。

    一种。今天的Java速度与C ++相比如何?

    b。是否可以使用Java创建现代AAA标题?

    C。如果有的话,在哪些方面Java比C ++慢?(即数字运算,图形或周围所有图形)

  2. Java现在被视为编译语言还是解释语言?

  3. 自早期以来,已经解决了Java的一些主要缺点?

  4. Java有哪些主要缺点需要解决?

编辑:

只是为了澄清起见,我并没有制作Java vs C ++,显然平均而言,c ++的速度要比Java快一点。现在,我只需要比较一下Java作为语言的成熟度就可以了。由于c ++永远存在,我想我将是一个比较点。



4
我喜欢10岁的东西不再是现代的事实。
cwallenpoole 2011年

4
当您将Java 视为框架/平台而不仅仅是一种语言时,Java看起来有很大不同。也许问题在于两者的名称本质上都是“ Java”。
Joe Internet,

1
恰恰相反,《我的世界》最近的销量达到了300万。我认为Java所谓的缺点并没有严重影响游戏的销售。
Michael K'8

3
绝对任何 一种语言都“以一种或另一种方式”是次等的。根据定义。
SK-logic

Answers:


62

一种。今天的Java速度与C ++相比如何?

难以测量。值得注意的是,实现速度的主要部分,即内存分配器,是Java和C ++中非常不同的算法。与C ++的确定性内存管理相比,收集器的非确定性本质使获取有意义的性能数据变得极为困难,因为您永远无法确定收集器所处的状态。这意味着编写基准非常困难。可能会对它们进行有意义的比较。有些内存分配模式在GC上运行得快得多,有些在本地分配器上运行得快得多。

但是,我要说的是Java GC必须在每种情况下都能快速运行。但是,可以将本机分配器换成更合适的分配器。我最近在SO上提出了一个问题,即Dictionary与同等条件相比,为什么C#可以在(我的计算机上为0.45毫秒)内执行std::unordered_map执行时间为10ms(在我的计算机上)。但是,通过简单地将分配器和哈希交换出更合适的分配器,我将我的机器上的执行时间减少到0.34ms,这是原始运行时间的三十分之一。您永远都不会希望使用Java执行这种自定义优化。可以真正发挥作用的一个很好的例子是线程。当处理许多线程上的许多分配时,诸如TBB之类的本地线程库提供了线程缓存分配器,该分配器比传统分配器快得多。

现在,许多人将谈论JIT的改进以及JIT如何获得更多信息。当然可以。但是,它距离C ++编译器所能提供的功能还差得很远,因为从最终程序运行时的角度来看,编译器具有无限的运行时间和空间。JIT花费在思考如何最好地优化程序上的每个周期和每个字节,就是程序不花时间执行并且不能用于自身内存需求的周期。

此外,在某些情况下,编译器和JIT优化总是无法证明某些优化,尤其是在诸如转义分析之类的情况下。在C ++中,然后作为值是在堆栈上无论如何,编译器不需要执行它。此外,还有一些简单的事情,例如连续内存。如果您使用C ++分配数组,则分配一个连续的数组。如果您使用Java分配数组,那么它根本就不是连续的,因为该数组仅填充有指向任何地方的指针。这不仅是双重间接访问的内存和时间开销,而且也是缓存开销。这种事情就是Java语言语义仅仅要求它必须比等效的C ++代码慢的地方。

最终,我的个人经验是,Java的速度平均只能达到C ++的一半。但是,由于所涉及的算法根本不同,实际上如果没有非常全面的基准套件就无法备份任何性能声明。

b。是否可以使用Java创建现代AAA标题?

我认为您的意思是“游戏”,而不是机会。首先,由于几乎所有现有的库和基础结构都以C ++为目标,因此您必须从头开始编写所有内容。尽管它本身并没有使它不可能,但是它肯定会为不可行做出坚实的贡献。其次,即使C ++引擎也几乎无法适应现有控制台的微小内存限制-如果这些控制台甚至存在JVM-并且PC游戏玩家期望它们的内存更多。在C ++中创建高效的AAA游戏已经足够困难了,我不知道如何用Java来实现。没有人曾经花费大量时间用非编译语言编写AAA游戏。不仅如此,它极容易出错。确定性销毁在处理GPU资源等方面至关重要,而在Java中,

C。如果有的话,在哪些方面Java比C ++慢?(即数字运算,图形或周围所有图形)

我肯定会全力以赴。所有Java对象的强制引用性质意味着Java中比C ++具有更多的间接引用和引用-例如,我之前使用数组给出的示例,但也适用于所有成员对象。C ++编译器可以在恒定时间内查找成员变量,而Java运行时必须遵循另一个指针。您进行的访问越多,获取的速度就越慢,JIT对此无能为力。

在Java中,C ++几乎可以立即释放并重用一块内存,在Java中,您必须等待该内存,并且我希望那块内存不会超出缓存,并且固有地需要更多内存就意味着较低的缓存和分页性能。然后查看装箱和拆箱之类的语义。在Java中,如果要引用一个int,则必须动态分配它。与C ++语义相比,这是固有的浪费。

然后,您将遇到泛型问题。在Java中,只能通过运行时继承对通用对象进行操作。在C ++中,模板的开销实际上为零,这是Java所无法比拟的。这意味着Java中的所有通用代码本质上都比C ++中的通用代码慢。

然后您进入未定义的行为。每个人都在程序展示UB时讨厌它,并且每个人都希望它不存在。但是,UB从根本上实现了Java中永远不会存在的优化。看看这篇描述基于UB的优化的帖子。不定义行为意味着实现可以做更多的优化,并减少检查C ++中未定义但Java中定义的条件所需的代码。

从根本上讲,Java的语义表明它是一种比C ++慢的语言。

Java现在被视为编译语言还是解释语言?

它实际上不适合这些组中的任何一个。我会说托管实际上是一个单独的类别,尽管我说它绝对更像是一种解释语言,而不是编译语言。更重要的是,只有两个主要的受管系统,即JVM和CLR,当您说“受管”时,它已经足够明确了。

自早期以来,已经解决了Java的一些主要缺点?

我只知道自动装箱和拆箱。泛型解决了一些问题,但远未解决。

Java有哪些主要缺点需要解决?

它们的泛型非常非常弱。C#的泛型要强大得多-尽管当然也不是模板。确定性破坏是另一个主要缺陷。任何形式的lambda / closure也是一个主要问题-您可以忘记Java中的功能性API。当然,对于那些需要性能的领域,始终存在性能问题。


10
您似乎对现代JIT的工作方式有一些误解。否则很好的信息。
肖恩·麦克米兰

7
“更重要的是,只有两个主要的受管系统,即JVM和CLR”-嗯,Python吗?红宝石?短暂聊天?LISP?它们全部使用垃圾回收器,缺少指针算法,并且AFAIK至少具有一种基于字节码的实现。
Michael Borgwardt

3
@Michael:我上一次检查时,至少Python和Ruby严重陷入了“解释”阵营。它们最常见的实现既没有在单独的阶段中预编译为字节码,也没有包含JIT。没有使用过Smalltalk或LISP,但是我不确定要将它们放入“主要”阵营-而且我也从未听说过Smalltalk或LISP JIT。
DeadMG 2011年

19
+1个不错的答案。最后有人明白为什么Java总是比C ++慢。
jeffythedragonslayer

2
这些观点中的任何一个是否存在现实世界中的任何性能问题(传闻或基准)?大多数用户注意到吗?说语言X比语言Y快0.25%,并不表示语言Y慢。使用视频游戏时,您是在谈论激烈的游戏机还是包括PC游戏?
TheLQ 2011年

34

我将从附带条件开始,即几乎不可能有人对编程语言给出真正中立的意见。如果您对两种语言都足够了解,足以对它们进行有意义的评论,那么您将不可避免地会首选一种语言。作为合理的警告,相对于Java,我更喜欢C ++,它无疑至少会在一定程度上影响我的注释。

1a。速度:从C ++或Java获得的速度通常比语言或语言实现的依赖程度要小,而不是程序员使用它的技能。最终,C ++可能以更快的速度赢得胜利,但是您编写的代码中的差异才是真正重要的。
1b。应该是。同时,C ++已经很成熟,我怀疑大多数游戏工作室是否会看到足够的优势来烦恼转向Java。
1c。对此的彻底解答可能会占很大空间。通常,使用有限的资源,C ++会做得更好。Java受益于(例如)拥有大量“备用”内存。
2.缓慢的执行速度和缓慢的垃圾收集可能是最明显的两个。早期的窗口库(AWT)非常笨拙-Swing是一项重大改进。
3.详细。缺少操作员超载。使用垃圾收集。缺乏多重继承。与C ++模板相比,Java泛型极为有限。

我应该补充一点,其中的一些缺点(尤其是使用垃圾回收,但也有其他缺点)被许多人视为Java的优点。唯一可能的例外是它的详细程度。详细情况正在慢慢改善,但是您肯定不会经常看到Java赢得代码竞赛,而且在普通代码中,它往往也会使用很多代码。我怀疑至少有人认为它更具可读性和可理解性,因此它也可能被视为一种优势。


12
Java泛型甚至无法与C ++模板媲美。Java模板是语法糖,可帮助进行编译时类型检查。C ++模板是图灵完备的代码生成系统。
凯文·克莱恩

10
+1表示冗长。它与COBOL一起使用,可以解决无意义的语法。对于所有“ try”,“ catch”以及所有其他代码,ExtremelylyLongClassName ExtremelyLongObjectName = new ExteremlyLongClassName()类型代码,要弄清楚一段代码实际上是想做什么,可能是一个很大的挑战。
詹姆斯·安德森

1
@Mark:就我个人而言,我发现此答案令人难以理解,并且不想再看到这种情况。答案应该是答案,而不是讨论。
Michael Borgwardt

2
+1对于操作员超载,许多人认为这是次要的缺点,但对我来说是主要的缺点。当然还有模板,但是几乎每个人都将它们视为主要的。
Christian Rau

2
C ++模板并非旨在图灵完备-由于其设计而偶然发生。但是,它有时还是有用的:查找C ++模板元编程。
即将

11
  1. 关于性能;
    1. 在纯代码执行速度方面,Java大约等同于简单的C ++。但是Java倾向于使用更多的内存-部分是因为它基于GC,部分是因为其设计更加注重简单性和安全性而不是效率。由于缓存问题,更多的内存会导致速度降低。甲很多相比高度调谐C ++时更低。
    2. 如果您认为AAA标题必须在使用当前硬件的可能性范围之内起作用,则不会。至少不在客户端。我愿意打赌,某些AAA标题已经在部分后端基础结构中使用Java。
    3. 您可以对使用大型数据集和C ++的任何内容进行优化,以友好的缓存方式访问它们。
  2. 它在运行时被编译为字节码并进行JIT编译。编译与解释是错误的,过时的二分法。
  3. &4.太多的事情无法一一列举,大多数都会有分歧。

3
说Java使用大量内存是因为它是基于GC的,这几乎就像说18轮摩托车使用很多汽油是因为它有18个轮子。我对Java几乎一无所知,但是我怀疑问题是运行时膨胀,太多东西被缓存,语义垃圾的可能性降低,垃圾收集方法本身根本没有缺陷。
乔伊·亚当斯

3
在最明显的级别上,垃圾回收意味着对象停止使用与垃圾回收器实际回收其空间之间存在延迟。在手动管理的环境中,当对象不再使用时,可以立即释放空间。延迟意味着垃圾回收环境使用更多的内存。通常,它可以使用的内存越多,性能越好,因为这样可以减少GC开销。
Michael Borgwardt

1
@MichaelBorgwardt您可能要提到速度需要时间,因为大多数JVM每次都需要从头开始。先前运行的分析信息不会重复使用。

11

首先,在某些情况下,我的C ++非常生锈,因此我在Java方面的大部分经验与我最近在C#方面的经验有关,无论如何,这都是一个苹果与苹果的比较。

1.速度

一种。今天的Java速度与C ++相比如何?

我认为这可以最好地用SO问题来回答,为什么Java的名声很慢?但我也认为,整个问题都由杰夫·阿特伍德(Jeff Atwood)的博客文章《大猩猩与鲨鱼》所掩盖感谢Péter&Christopher。

b。是否可以使用Java创建现代AAA标题?

这取决于开发人员的优先级和开发人员的技能。再加上情况并非如此,标题的不同部分可能需要使用不同的语言实现方式,从而导致异构的语言环境。

我已经看到了一些游戏最近提的是,他们正在装载Python环境,而他们正在装载我怀疑马场是一个强烈的动机,如果你想获得你的标题出时间的假期(例如) 。

C。如果有的话,在哪些方面Java比C ++慢?(即数字运算,图形或周围所有图形)

您可以用任何一种语言编写性能不佳的代码,但是某些语言可以使您做出更好的选择更加容易,而另一些语言则更有可能让自己被自己的皮卡提升。Java属于前一类,C ++肯定属于后者。

正如他们所说的那样,强大的力量将带来巨大的责任(更不用说完全破坏堆* 8'的能力了)。

2. Java现在被视为编译语言还是解释语言?

我不能说大多数人认为它是什么,但是许多人知道编译语言和解释语言之间的区别,并且在过去的20年中没有住在洞穴中,所以他们也知道JIT(Just-in -Time)编译器是Java生态系统的重要组成部分,因此这些天更可能被视为已编译。

3.自早期以来,已经解决了Java的一些主要缺点?

我是最近才转换为Java的人,因此对于它的发展方式,我几乎没有任何了解。但是有趣的是,有一些像Java这样的书:The Good Parts试图引导人们朝着如今应该被优先使用的语言部分的方向发展,并引导人们远离那些曾经或应该存在的领域。不推荐使用。

4. Java有哪些主要缺点需要解决?

在我看来,Java的一个问题是新功能的缓慢采用。

从C#接触Java之后,浏览Wikipedia比较页面,这些对我来说很突出:

与C#相比,我在Java中缺少的东西

  • 属性,尤其是自动属性。它们使建立和维护界面更加容易。
  • 闭包/ lambdas。当我听说Java支持被再次推迟时,我真的很失望。最终,我们在Java 8中有了Closures / lambdas,但是花费的时间证明了我关于缓慢采用的说法。
  • 类型推断var)看起来像语法糖,但是当您具有复杂的泛型类型时,它可以通过删除许多毫无用处的重复来使代码清晰。
  • 局部类确实有助于将自动生成的代码(例如来自GUI生成器)与程序员编写的代码分开。
  • 值类型,有时会在struct整个类上使用轻量级参数。
  • 扩展方法,如果在使用可以使系统复杂,但都是伟大的,用于指示一类的东西实现的规范的方法,如果它是需要的。
  • 无符号类型,有时多余的部分可以发挥作用。* 8')

与C#相比,我在Java中不会错过的东西

  • 如果正确使用运算符,过载会很大,但如果使用不当,则会导致难以发现错误,而且运算符显然应该执行的操作与实际执行的操作之间存在脱节。
  • 可空值类型似乎总是造成比其价值更大的麻烦。
  • 访问unsafe代码。您必须非常小心,以至于我很少发现值得付出额外的努力。

因此,即使将苹果与苹果进行比较,Java也被认为落后了。

我看到的Java的另外两个大问题是启动延迟过长,以及(对于某些JVM)必须微管理堆甚至永久生成堆的事实。使用C#应用程序时,总是立即启动,而且我什至从未考虑过堆,因为它是从系统内存池中分配出来的,而不是从分配给虚拟机的预分配池中分配出来的。


1
您链接的那个SO问题,被接受的答案令人难以置信,荒谬可笑。
DeadMG


@Mark:也许吧。再说一遍,完全删除它可能也一样。我已经对相同的问题发表了自己的看法,因此在评论中添加更多内容实际上并不会增加很多新知识。
杰里·科芬

8

我可以为您指出一个可以帮助您回答问题第一部分的资料。编程语言大放异彩http://shootout.alioth.debian.org/u64q/which-programming-languages-are-fastest.php是了解各种语言之间比较速度的很好的资料。甚至可以按不同的类别对它们进行过滤,以查看哪些语言在哪些方面比其他语言要好。Java比几年前快得多。



是的,对不起,我应该直接链接到该页面。
bschaffer13 2011年

4

1)严格谈论我用Java获得的UX,感觉很慢。我不能告诉你为什么。我还没有遇到过基于Java的桌面应用程序,它感觉并不慢,并且具有更快的非Java替代方案。话虽如此,Java的纯计算速度可能非常快,并且互联网上有充分的基准来证明这一点。但是,Java应用程序的启动时间及其GUI的响应能力尚未改善恕我直言。也许您可以做到;)
最后,速度并不是什么大问题。不仅硬件变得越来越快,而且只要软件能够做到,大多数人仍然很少关心它,它应该做什么以及交互所花费的时间与等待所花费的时间之比是合理的。

2)最近,这种区分变得如此模糊,以至于它几乎没有价值。

3 + 4)实际上,对Java进行了很多更改。有人已经争辩说,这些变化通过依赖外来功能而污染了Java的纯粹简化哲学。很难客观地说出缺点是什么,优点是什么。对我来说,Java不必要地冗长,严格且功能欠佳,而其他人则将这些特质视为令人愉悦的明确性,安全性和清晰度。
因此,尽管这些都是我个人不使用Java的东西,但我认为不仅仅在Java中添加我想念的东西也不是一个好主意。我喜欢在JVM上运行许多语言,而将Java弯曲到与它们更接近的语言只会破坏Java的目的。

这是一个偏好问题

Java的用处在于,它旨在防止您用脚射击。这是一个崇高的事业,但由于种种限制,您跳过一只安全的脚是不可能的,为了自己的安全,双手被绑在背上无法支撑自己,最​​后死亡,因为你摔断了头骨 :D
在某种程度上,Java是对C ++的一种回应,它为您提供了足够的绳索,不仅可以使您自己吊死,而且还可以吊销整个世界。这就是所有的绳子,这对牛仔很有吸引力。所有的自由和力量。

简而言之,这实际上只是一个偏好问题。

但是,有一点是,使用C ++替代Java,您可以自由选择自己的限制。或者真正对您拥有的所有控制权发疯,有可能使同行完全困惑:

我看到“ cout”在“ Hello world”的位置向左移动,并在此处停下来。
—史蒂夫·戈内德斯

因此,Java选择不提供运算符重载。当然,这可以防止人们通过将函数指针与列表相乘来混淆代码。但是同时,它阻止其他人使用常规运算符执行几何/代数计算。(v1 * v2 / scale) + (v3 * m)真的是很多比更清晰v1.multiply(v2).divide(scale).add(v3.multiply(m))。我知道为什么这可以推迟处理3D图形和计算的人员。

Java选择强加垃圾回收,而在C ++中可以选择。您确实可以一直深入挖掘并接近硬件。您可以将数据密集地打包到结构中。您可以执行黑魔法,例如快速反平方根。您可以使用模板执行一些世界上最复杂和隐秘的元编程。但这也意味着,您可能会迷失方向,并花费数小时来调试您创建的所有混乱或查看绝对无用的编译器错误。
但是,如果您拥有只使用真正掌握的语言部分的专业知识,则可以像编写Java代码一样安全地编写C ++代码,但是可以选择逐步推进。

因此,尽管从技术上讲,没有什么可以阻止您使用Java编写最先进的软件,但是您会发现,许多开发人员确实对编写出色的软件充满热情,并在开发过程中既有趣又不断发展,从而超越了Java作为语言提供的功能。

但是,这个世界不仅是由那些下定决心要创造下一件大事的人组成的,或者不是只有在他们控制的范围内限制赋予他们权力的人组成的世界。IMHO Java是想要以舒适的方式产生稳定结果的人们的完美选择。


+1事实是,在C ++中,没有什么可以阻止您编写类似Java的代码,同样,没有什么可以阻止您执行更多操作。是程序员使语言变得不安全或困难。
Christian Rau

0

垃圾收集是一件大事。GC每隔一段时间就会将其他所有内容锁定数百毫秒(取决于堆的大小),并进行大量收集。如果您没有任何时间限制,那很好,但是如果迟到则意味着失败,这是一个表演障碍。您可以将钱花在实时Java和实时OS上,但是您可以简单地使用GCC和标准Linux,而不会遇到这些问题。

没有不可预测的随机暂停,如今的Java在大多数情况下可能足够快。如果您花费数月时间调整GC设置等,也许,也许,您就可以使它工作足够长的时间,以便客户削减支票。


大多数现代垃圾收集器不会阻止世界。

-1

3)已修复的缺陷。

几年前,Java引起了很多愤怒。大多数Java程序员都是Web /服务器程序员,他们为Java的冗长而发狂。因此,诸如Ruby之类的某些语言开始流行,而Java开始衰落。但是,有了像hibernate和Spring这样的新注释和框架,人们不再抱怨,而回到Java。

4)目前的缺点

硬件全部采用多核。尽管Java可以执行多线程,但它基于C(一种顺序语言),并且至少可以说,使它成为多线程的功能并不完美。顺便说一句,这不仅是对Java的批评,而且在所有语言中几乎都是如此。需要一些完全不同的代码思考方式。函数式编程也许是未来的方式。


1
生气了吗 几乎不这样认为。显然你还没有在Java中6,看看并发的东西

-1

我对这个问题有一定的反应,因为它会给人以误导和不相关的答案:

b。是否可以使用Java创建现代AAA标题?

每个人都可以同意,使用Java很难生成AAA标题,并且我不知道实际的示例。但是,考虑到AAA的性质,它会承担很多事情(因为它确实是来自营销的令人困惑的术语),因此最好提出以下要求:

是否有可能使用Java创建具有合理成功的现代标题?

答案是“ 可以。 ”。但是,等式的实际成功部分更多地取决于您的坚持和运气(或坚持时代精神),但这超出了此站点的范围。


-6

速度的提升取决于编译器与编译器。不是语言还是语言。JIT编译可能具有优势,因为它可以针对正在运行的计算机的规格进行优化。比较JIT编译的C ++与Java进行更多的“苹果对苹果”编译器比较。

但是在某些情况下,Java语言本身限制了它自己的性能。

  1. 在堆栈上分配。Java无法做到这一点。对于非递归解决方案中的小型固定大小类,这通常是理想的。您还可以避免堆碎片。

  2. 非虚拟功能。Java无法做到这一点。即使不计划覆盖所有方法调用,它们也会受到永久打击。

可能还有其他一些东西,但这就是我能想到的全部。


2
现代的JIT编译器可以优化这两种情况。Java(从6开始)具有堆栈分配:en.wikipedia.org/wiki/Escape_analysis。对于非虚拟函数,JIT编译器会将仅到达一个目标(有时甚至可以内联)的虚拟方法调用解析为非虚拟调用。
史蒂文·斯兰斯克

1
#2是伪造的:任何体面的JIT标记都基于当前是否被覆盖而充当虚拟或非虚拟标记。
阿马拉

-16

1)无关紧要,并且引人注目地引导。
不仅可以用Java创建主要软件,而且每天都可以交付这样的系统,并且可以运行当今世界上大多数主要公司。
2)同上。
阅读JVM规范,您就会知道。Java从来不是一种解释语言。
3)同上。
阅读15年发行说明。我们无法弄清您认为要解决的“主要缺陷”。
4)同上。
必须解决的主要缺陷是JCP,它很容易混入核心语言和库,除了其他明显的原因外,除了在JSR上获得somoene的名称外,他们还可以写一本有权威性的书,说“他们是JSR-666的负责人”。希望甲骨文对JCP的重组能够解决这一问题。
您似乎只想在这里激起一场语言大战,并让他人对Java产生偏见,因为您自己找不到任何真正的理由。


啊,我看到人们已经开始通过不投票反对Java的任何人来发动战争。干得好,人们!
jwenting 2011年

10
我认为不赞成投票的原因更多是您的答案并非真的。
blubb 2011年

6
这个答案只是拖钓。OP有一个很好的,经过深思熟虑的非担保问题。然后,您进入“让别人对Java的偏见得到确认,因为您自己找不到真正的理由”。是的,-1。哦,不,我不讨厌Java,它是我目前最喜欢的语言,用于很多方面
TheLQ 2011年

4
OP写了一个很好的拼写问题,并收到了措辞明确的答案。确实没有必要指责他煽动任何事情。
亚当李尔

啊,我看到人们已经开始发动战争了,他们对蚂蚁提出了严肃的问题(和答案),并感到自己遭到了人身攻击。太糟糕了,我还不能投票。
Christian Rau
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.