JVM基础(一)

1、Jvm 的主要组成部分?及其作用?

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

各组件的作用:首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

2、谈谈对运行时数据区的理解?

这道题是非常重要的题目,几乎问到 Java 虚拟机这块都是会被问到的。建议不要简单的只回答几个区域的名称,最好展开的讲解下,下面的答案是比较详细的,根据自己的理解回答其中某一段即可

在这里插入图片描述

2.1 程序计数器

​ 程序计数器(Program Counter Register):是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

​ 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。程序的分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

​ 由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的命令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间的计数器互不影响,独立存储,我们称这块内存区域为**“线程私有”的内存**。

​ 此区域是唯一 一个虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

2.2 Java虚拟机栈

​ Java 虚拟机栈(Java Virtual Machine Stacks):描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个帧栈(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。它的线程也是私有的,生命周期与线程相同。

​ 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和 returnAddress 类型(指向了一条字节码指令的地址)。

​ Java 虚拟机栈的局部变量表的空间单位是槽(Slot),其中 64 位长度的 double 和 long 类型会占用两个 Slot。局部变量表所需内存空间在编译期完成分配,当进入一个方法时,该方法需要在帧中分配多大的局部变量是完全确定的,在方法运行期间不会改变局部变量表的大小。

​ Java虚拟机栈有两种异常状况:如果线程请求的栈的深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常

2.3 本地方法栈

​ 本地方法栈(Native Method Stack):与虚拟机栈所发挥的作用是非常相似的,它们之间的区别只不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。

​ Java 虚拟机规范没有对本地方法栈中方法使用的语言、使用的方式和数据结构做出强制规定,因此具体的虚拟机可以自由地实现它。比如:Sun HotSpot 虚拟机直接把Java虚拟机栈和本地方法栈合二为一。

​ 与Java虚拟机栈一样,本地方法栈也会抛出StackOverflowError和 OutOfMemoryError 异常。

2.4 Java堆

​ Java堆(Java Heap):是被所有线程所共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是:存放对象实例,几乎所有的对象实例都在这里分配内存。

​ Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC”堆(Garbage Collected Heap)。从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以 Java 堆中还可以细分为:新生代和老年代。从内存分配角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。不过无论如何划分,都与存放的内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

​ Java 虚拟机规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。在实现时,可以是固定大小的,也可以是可扩展的。如果在堆中没有完成实例分配。并且堆也无法扩展时,将会抛出 OutOfMemoryError 异常。

2.5 方法区

​ 方法区(Method Area):与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

​ 虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),其目的应该就是与 Java 堆区分开来。

​ Java 虚拟机规范对方法区的限制非常宽松,除了和 Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

​ 根据Java虚拟机规范规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

运行时常量池:运行时常量池(Runtime Constant Pool):是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一些信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

​ Java 虚拟机对 Class 文件每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行。

直接内存:直接内存(Direct Memory):并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。但是这部分内存也频繁地使用,而且也可能导致 OutOfMemoryError 异常。

​ 本地直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存大小以及处理器寻址空间的限制。如果各个内存区域总和大于物理内存限制,从而导致动态扩展时出现 OutOfMemoryError 异常。

3、堆和栈的区别是什么?

  • 堆和栈(虚拟机栈)是完全不同的两块内存区域,一个是线程独享的,一个是线程共享的。二者之间最大的区别就是存储的内容不同:堆中主要存放对象实例。栈(局部变量表)中主要存放各种基本数据类型、对象的引用。
  • 从作用来说,栈是运行时的单位,而堆是存储的单位。栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。在 Java 中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

4、堆中存什么?栈中存什么?

  • 堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个 4btye 的引用(堆栈分离的好处)

  • 为什么不把基本类型放堆中呢?

    因为基本数据类型占用的空间一般是1~8个字节,需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况,长度固定,因此栈中存储就够了。如果把它存在堆中是没有什么意义的。基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,它们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java 中参数传递时的问题。

5、为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?

1、从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

2、堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

3、栈因为运行时的需要,比如:保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可、

6、Java中的参数传递是传值,还是传引用?

1、不要试图与 C 进行类比,Java 中没有指针的概念。

2、程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身

3、基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的

4、对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。

7、Java对象的大小是怎么计算的?

基本数据的类型的大小是固定的。对于非基本类型的 Java 对象,其大小就值得商榷。在 Java 中,一个空 Object 对象的大小是 8 byte,这个大小只是保存堆中一个没有任何属性的对象的大小。看下面语句:

Object ob = new Object();

这样在程序中完成了一个 Java 对象的生命,但是它所占的空间为:4 byte + 8 byte。4 byte 是上面部分所说的 Java 栈中保存引用的所需要的空间。而那 8 byte 则是 Java 堆中对象的信息。因为所有的 Java 非基本类型的对象都需要默认继承 Object 对象,因此不论什么样的 Java 对象,其大小都必须是大于 8 byte。有了 Object 对象的大小,我们就可以计算其他对象的大小了。

Class MaNong { 
    int count;
    boolean flag;
    Object obj; 
}

MaNong 的大小为:空对象大小(8 byte) + int 大小(4 byte) + Boolean 大小(1 byte) + 空 Object 引用的大小(4 byte) = 17byte。但是因为 Java 在对对象内存分配时都是以 8 的整数倍来分,因此大于 17 byte 的最接近 8 的整数倍的是 24,因此此对象的大小为 24 byte。

这里需要注意一下基本类型的包装类型的大小。因为这种包装类型已经成为对象了,因此需要把它们作为对象来看待。包装类型的大小至少是12 byte(声明一个空 Object 至少需要的空间),而且 12 byte 没有包含任何有效信息,同时,因为 Java 对象大小是 8 的整数倍,因此一个基本类型包装类的大小至少是 16 byte。这个内存占用是很恐怖的,它是使用基本类型的 N 倍(N > 2),有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。在 JDK5 以后,因为加入了自动类型装换,因此,Java 虚拟机会在存储方面进行相应的优化。

8、对象的访问定位的两种方式?

Java 程序通过栈上的引用数据来操作堆上的具体对象。目前主流的对象访问方式有:句柄 和 直接指针

  • 使用句柄

如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,引用中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

  • 直接指针

    如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中存储的直接就是对象的地址。

  • 各自优点

    1、使用句柄来访问的最大好处是引用中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而引用本身不需要修改

    2、使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

    9、判断垃圾可以回收的方法有哪些?

垃圾收集器在对堆区和方法区进行回收前,首先要确定这些区域的对象哪些可以被回收,哪些暂时还不能回收,这就要用到判断对象是否存活的算法。

  • 引用计数法

    • 基本思想

    引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为 1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则 b 引用的对象实例的计数器加 1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减 1。任何引用计数器为 0 的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减 1。

    • 优缺点

      优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

      缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为 0。

  • 可达性分析算法

    可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
    在这里插入图片描述

在java语言中,可作为GC Root的对象包括下面几种:

  1. 虚拟机栈中引用的对象(栈帧中的本地变量表);
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中 JNI(Native方法)引用的对象。

10、垃圾回收是从哪里开始的呢?

查找哪些对象是正在被当前系统使用的。上面分析的堆和栈的区别,其中栈是真正进行程序执行地方,所以要获取哪些对象正在被使用,则需要从 Java 栈开始。同时,一个栈是与一个线程对应的,因此,如果有多个线程的话,则必须对这些线程对应的所有的栈进行检查。
在这里插入图片描述

同时,除了栈外,还有系统运行时的寄存器等,也是存储程序运行数据的。这样,以栈或寄存器中的引用为起点,我们可以找到堆中的对象,又从这些对象找到对堆中其他对象的引用,这种引用逐步扩展,最终以 null 引用或者基本类型结束,这样就形成了一颗以 Java 栈中引用所对应的对象为根节点的一颗对象树。如果栈中有多个引用,则最终会形成多颗对象树。在这些对象树上的对象,都是当前系统运行所需要的对象,不能被垃圾回收。而其他剩余对象,则可以视为无法被引用到的对象,可以被当做垃圾进行回收。

11、被标记为垃圾的对象一定会被回收吗?

即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。

第一次标记:如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记;

第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。在 finalize() 方法中没有重新与引用链建立关联关系的,将被进行第二次标记。第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。在 finalize() 方法中没有重新与引用链建立关联关系的,将被进行第二次标记。

finalize()是Object里面的一个方法,当一个堆空间中的对象没有被栈空间变量指向的时候,这个对象会等待被java回收)。

12、谈谈对Java中引用的了解?

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。在Java语言中,将引用又分为强引用、软引用、弱引用、虚引用 4 种,这四种引用强度依次逐渐减弱。

  • 1、强引用

    在程序代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

  • 2、软引用

    用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内

  • 3、弱引用

    也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  • 4. 虚引用

    也叫幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。

13、谈谈对内存泄漏的理解?

  • 内存泄漏的基本概念

    在 Java 中,内存泄漏就是存在一些不会再被使用确没有被回收的对象,这些对象有下面两个特点:

    ​ 1、这些对象是可达的,即在有向图中,存在通路可以与其相连;

    ​ 2、这些对象是无用的,即程序以后不会再使用这些对象。

    如果对象满足这两个条件,这些对象就可以判定为 Java 中的内存泄漏,这些对象不会被 GC 所回收,然而它却占用内存。

14、内存泄露的根本原因是什么?

​ 长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是 Java 中内存泄漏的发生场景

15、举几个可能发生内存泄漏的情况?

1、各种连接:比如数据库连接(dataSourse.getConnection()),网络连接(socket) 和 IO 连接,除非其显式的调用了其 close() 方法将其连接关闭,否则是不会自动被 GC 回收的。

2、单例模式:单例对象在初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被 JVM 正常回收,导致内存泄漏。

16、尽量避免内存泄漏的方法?

1、尽量不要使用 static 成员变量,减少生命周期;

2、及时关闭资源;

3、不用的对象,可以手动设置为null.

17、常用的垃圾收集算法有哪些?

1、标记 - 清楚算法(Mark-Sweep)

​ 标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

2、复制算法(Copying)

​ 复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于 copying 算法的垃圾收集就从根集合(GC Roots)中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。

3. 标记-整理算法(Mark-compact)

​ 标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

4. 分代收集算法

​ 分代收集算法是目前大部分 JVM 的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)

​ 老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法

18、为什么要采用分代收集算法?

​ 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

​ 在 Java 程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如 Http 请求中的 Session 对象、线程、Socket 连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String 对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

​ 在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

19、分代收集下的年轻代和老年代应该采用什么样的垃圾回收算法?

1、年轻代(Young Generation)的回收算法 (主要以复制算法 - Copying 为主)

​ 1、所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象

​ 2、新生代内存按照 8:1:1 的比例分为一个 eden 区和两个 survivor(survivor0、 survivor1)区。大部分对象在 Eden 区中生成。回收时先将 Eden 区存活对象复制到一个 survivor0 区,然后清空 eden 区,当这个 survivor0 区也存放满了时,则将 eden 区和 survivor0 区存活对象复制到另一个 survivor1 区,然后清空 eden 区 和这个 survivor0 区,此时 survivor0 区是空的,然后将survivor0 区和 survivor1 区交换,即保持 survivor1 区为空, 如此往复。

​ 3、当 survivor1 区不足以存放 Eden 区 和 survivor0区 的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC(Major GC),也就是新生代、老年代都进行回收。

2、年老代(Old Generation)的回收算法(主要以标记-整理算法 - Mark-Compact 为主)

1、在年轻代中经历了 N 次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

2、内存比新生代也大很多(大概比例是1 : 2),当老年代内存满时触发 Major GC 即 Full GC,Full GC 发生频率比较低,老年代对象存活时间比较长,存活率标记高。

20、JVM 的一些参数?

1、堆设置

​ -Xms:初始堆大小

​ -Xmx:最大堆大小

​ -XX:NewSize=n:设置年轻代大小

​ -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代年老代和的 1/4

​ -XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。如:3,表示 Eden:Survivor=3:2,一个Survivor区占整个年轻代的 1/5

​ -XX:MaxPermSize=n:设置持久代大小

21、谈谈你对类加载机制的了解?

​ 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。

​ 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载 7 个阶段。其中验证、准备、解析 3 个部分统称为连接,这7个阶段发生的顺序如下图所示:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、进制转换 输入 10进制:直接输入,支持负数。 16进制:0x10,不支持负数 浮点数:直接收入,支持负数。 字符:'A',字符必须用两个单引号。 =================================================================================================== 二、表达式计算 1.支持大数运算。大数用数组表示,数组大小为1000个元素。最大能计算499的阶层。 大数表示方法: sign,intcount,decimalcount|num[PBigNum_ValueLength]。 sign: 符号。正数:sign=0; 负数:sign=1。 intcount: 整数个数。 decimalcount: 小数个数。 num: __int64数组,元素个数=PBigNum_ValueLength。 举例1: 0,3,0|0,0,0,0,0,0,0,1,2,3代表123。(假设PBigNum_ValueLength=10) 举例2: 1,3,2|0,0,0,0,0,1,2,3,4,5代表-123.45。 举例3: 0,1,0|0,0,0,0,0,0,0,0,0,0代表0。 举例4: 0,1,0|0,0,0,0,0,0,0,0,0,1代表1。 举例5: 0,0,1|0,0,0,0,0,0,0,0,0,1代表0.1。 举例6: 0,0,0|0,0,0,0,0,0,0,0,0,0 此数非法 特点: sign,intcount,decimalcount,num[]均不可能出现负数;sign取值0与1;intcount和decimalcount不可能同时是0。 --------------------------------------------------------------------------------------------------- 2.支持四则运算,支持括号,支持负数,支持双精度浮点数double。 支持+-*/。运算数以数组表示,并模拟+-*/,并没有直接调用C/C++当中的+-*/运算符对两个运算数进行运算。 3.支持以下字符串运算:"123+-456","123--456"。不支持以下字符串运算:"123++456","123-+456"。 4.小数点精度20位。 ------------------------------------------------- 5.测试用例: 1/6=0.16666666666666666667 3175/6=529.16666666666666666667 1/7=0.14285714285714285714 1+(2)=3 1+(-2)=-1 0xFF+1=256 0xFFFFFFFFFFFFFFFF*0xFFFFFFFFFFFFFFFF=340282366920938463426481119284349108225 -0x123*-0x123=84681 0xFFFFFF+0=16777215 0xFFFFFF*0=00000000 10.569*2.469=26.094861 12.5+13.5=26 12.5/13.5=0.92592592592592592593 56*0=0 (((952.5*400/25.4)*1024*2)/1024/1024)/8=3.662109375 100!=93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 0.7*0.15=0.105 ------------------------------------------------- 7.支持函数 1.fac 输入fac(449)。最大参数449。结果有998位。 2.pow

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值