编译型语言&解释型语言&混合型语言&
脚本语言
动态语言&静态语言&强类型定义语言&弱类型定义语言
计算机不能直接
理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序. 翻译的方式有两种:一个是编译,一个是解释。 编译型语言在程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,以后要运行的话就不用重复翻译了,直接使用编译的结果就行了。程序执行效率高,依赖编译器,
跨平台性差些。如C、C++、Delphi等。 解释型语言在运行程序的时候才翻译,专门有一个解释器去进行翻译,每个语句都是执行的时候才翻译。效率比较低,依赖解释器,跨平台性好,如Basic。 脚本语言介于HTML和C,C++,Java,C#等
编程语言之间。
HTML通常用于格式化和链结文本。而编程语言通常用于向机器发出一系列复杂的指令。 脚本语言与编程语言也有很多相似地方,其函数与编程语言比较相象一些,其也涉及到变量。与编程语言之间最大的区别是编程语言的语法和规则更为严格和复杂一些. 脚本与程序代码的关系:脚本也是一种语言,其同样由程序代码组成。 注:脚本语言一般都有相应的脚本引擎来解释执行。
他们一般需要解释器才能运行。JAVASCRIPT、ASP、PHP、PERL都是脚本语言。C/C++经编译后,可形成独立执行的exe文件。 脚本语言是一种解释性的语言,例如vbscript、
javascript、installshield script等等,它不象c\c++等可以编译成
二进制代码,以可执行文件的形式存在。 脚本语言不需要编译,可以直接用,由解释器来负责解释。 脚本语言一般都是以文本形式存在,类似于一种命令。 java程序也需要编译,但是没有直接编译称为机器语言,而是编译称为字节码,然后用解释方式执行字节码。通过编译器,可以把Java程序翻译成一种中间代码 - 称为字节码 - 可以被Java解释器解释的独立于平台的代码。通过解释器,每条Java字节码指令被分析,然后在计算机上运行。只需编译一次,程序运行时解释执行。
动态语言,例如:ECMAScript(JavaScript)、Ruby、Python、VBScript、php;与静态类型定义相反,一种在执行期间才去
发现数据类型的语言,动态语言是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。动态语言的类型检查是在运行时做的。它的优点是不需要写非常多的类型相关的代码;缺点是不方便调试,命名不规范时会造成读不懂,不利于理解等。目前java平台下的动态语言有Groovy、nice、BeanShell、Jython、JRuby、Rhino(JavaScript)、 Jacl(TCL)、Bistro(SmallTalk)、Kawa(Lisp/
Schema);
静态语言:例如:C、C++、Java 即一种在编译时,数据类型是固定的语言。大多数静态类型定义语言强制这一点,它要求你在使用所有变量之前要声明它们的数据类型。在使用数据之前,我们必须首先定义数据类型,这些数据类型包括int ,float,double等等。就相当于在使用它们之前,首先要为它们分配好
内存空间。静态类型语言的主要优点在于其结构非常规范,便于调试,类型安全;缺点是为此需要写更多的类型相关代码。
强类型定义语言:一种总是强制类型定义的语言。Java和Python是强制类型定义的。如果你有一个整数,如果不显示地进行转换,你不能将其视为一个字符串
弱类型定义语言:一种类型可以被忽略的语言,与强类型定义相反。VBScript是弱类型定义的。在VBScript中,可以将字符串 ’12′ 和整数 3 进行连接得到字符串 ’123′,然后可以把它看成整数 123,而不需要显示转换。
java编译器:javac.exe 15kb
java解释器:java.exe 175kb
ASCII,Unicode,UTF-8,GBK,GB2312字符
编码
ASCII码 :在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此一个字节 八个二进制位就可以组合出 256种状态。从 0000000到11111111。ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),占用了一个字节的后面7位,首位统一规定为0。
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。 于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使 用的编码体系,可以表示
最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码 中代表了é,在希伯来语编码中却代表了字母?,在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。 比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256×256=65536个符号。
Unicode:世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用
错误的编码方式解读,就会出现乱码。可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么
乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示某个阿拉伯字母p,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号 呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必 然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
UTF-8:互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。UTF-8是Unicode的实现方式之一。UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
GBK :《通用多八位编码字符集》,
台湾译为《广用多八位元编码字元集》,它与 Unicode 组织的 Unicode 编码完全兼容,共收录了21003个汉字。
GB2312:《信息交换用汉字编码字符集》是由
中国国家标准总局1980年发布,收录汉字6763个和非汉字图形字符682个,收录使用频率99.75%的汉字,对于人名、古汉语等方面出现的罕用字,GB 2312不能处理
Unicode与UTF-8之间的转换
“严”的Unicode码是4E25,UTF-8编码是E4B8A5,两者是不一样的。它们之间的转换可以通过程序实现。在Windows平台下,有一个最简单的转化方法,就是使用内置的记事本小程序Notepad.exe(txt)。打开文件后,点击“文件”菜单中的“另存为”命令,会跳出一个对话框,在最底部有一个“编码”的下拉条。
ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。
Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。
Unicode big endian编码与Unicode相对应。
UTF-8编码,也就是上一节谈到的编码方法。
Little endian和Big endian
Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节 是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。
这两个古怪的名称来自
英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论:吃鸡蛋时究竟是从大头(Big- Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
(@#$...123...abcd...)顺序在前的排在前面,就是”大头方式“(Big endian),顺序在后排在前面就是”小头方式“(Little endian)。
假如一个汉字用FEFF表示。这正好是两个字节,而且FE比FF顺序在前,就表示该汉字采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
JIT Compiler(Just-in-timeCompiler) 即时编译
最早的Java编译方案是由一套转译程式(interpreter),将每个Java指令都转译成对等的微
处理器指令,并根据转译后的指令先后次序依序执行,由于一个Java指令可能被转译成十几或数十几个对等的微处理器指令,这种模式执行的速度相当缓慢。
针对这个问题,开发出JIT(just in time)编译器。在jvm中,每遇到一个新的类别(Class Person),JIT编译器在此时就会针对这个类别进行编译(compile)作业。经过编译后的程式,被优化成相当精简的原生型指令码(native code),这种程式的执行速度相当快。花费少许的编译时间来节省稍后相当长的执行时间,JIT这种设计的确增加不少效率,但是它并未达到最顶尖的效能,因为某些极少执行到的Java指令在编译时所额外花费的时间可能比转译器在执行时的时间还长,针对这些指令而言,整体花费的时间并没有减少。
基于对JIT的经验,发展出动态编译器(dynamiccompiler),动态编译器仅针对较常被执行的程式码进行编译,其余部分仍使用转译程式来执行。也就是说,动态编译器会研判是否要编译每个类别。动态编译器拥有两项利器:一是转译器,另一则是JIT,它透过甄别机制针对每个类别进行分析,然后决定使用这两种利器的哪一种来达到最佳化的效果。动态编译器针对程式的特性或者是让程式执行几个
循环,再根据结果决定是否编译这段程式码。这个决定不见得绝对正确,但从统计数字来看,这个判断的机制正确的机会相当高。事实上,动态编译器会根据"历史经验"做决策,所以程式执行的时间愈长,判断正确的机率就愈高。以整个结果来看,动态编译器产生的程式码执行的速度超越以前的JIT技术,平均速度可提高至50%。
JIT运行:首先采用编译形式生成某种字节码(bytecode),然后在运行时将其最终转换成机器码,然后执行,转化的机器码可以被cache,以提高重复执行的效率
JVM浅谈
JVM的实现机制:Java虚拟机就是一个小的计算机,有自己的指令集,有自己的文件系统,管理内部的表和数据,负责读取
class文件里面字节码(所以他是一个解释器),然后转换成不同操作系统的CPU指令,从而使得Java程序在不同的操作系统上顺利的跑起来。所以Window的JVM能把字节码转换成Window系统的指令集,Linux的 JVM能把字节码转换成Linux系统的字节,同理还有Solaris,它们彼此之间是不能通用的。最早一款的原型虽然是Sun公司开发的,但发展到现在其实任何厂商都可以自己去实现一个虚拟机,用来读取字节码转换成OS指令。甚至我们可以认为JVM跟Java编程语言都没有关系,因为你自己哪怕用记事本写一串字节码,也可以让JVM来
解析运行,只要你的字节码能通过JVM的验证。
JVM怎么判断一个文件是否是class文件?JVM的做法是读取前4个字节转换成16进制数,判断是否等于0xCAFEBABE这个数。“cafebabe”,国外一种咖啡品牌,java创建者选择了这样一个16进制数作为标准class文件的头,所以任何class文件都必须具有这4个字节的头部。我们用 输入流+字节转换 来验证一下,读取任何一个class文件,转换成16进制前几位一定会是cafebabe。
Java代码 收藏代码
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class BinaryFile {
public static byte[] read(String file) throws IOException {
BufferedInputStream bf = new BufferedInputStream(new FileInputStream(
file));
try {
byte[] data = new byte[bf.available()];
bf.read(data);
return data;
} finally {
bf.close();
}
}
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
public static void main(String[] args) throws IOException{
byte[] a=BinaryFile.read("F:/Test/A.class");
System.out.println(bytesToHexString(a));
}
}
cafebabe00000033000f0a0003000c07 ... ...
java中进制转换,返回都是String类型:
十进制转成十六进制:
Integer.toHexString(int i)
十进制转成八进制
Integer.toOctalString(int i)
十进制转成二进制
Integer.toBinaryString(int i)
十六进制转成十进制
Integer.valueOf("FFFF",16).toString()
八进制转成十进制
Integer.valueOf("876",8).toString()
二进制转十进制
Integer.valueOf("0101",2).toString()
Java代码 收藏代码
int max=Integer.MAX_VALUE;
int overMax=max+1; //
String a=Integer.toBinaryString(max);
String b=Integer.toBinaryString(max);
System.out.println(max+":"+a);
System.out.println("max+1还是 :"+Integer.valueOf(b,2).toString()+"--- >"+b);
int min=Integer.MIN_VALUE;
int lowerMin=min-1;
String c=Integer.toBinaryString(min);
String d=Integer.toBinaryString(lowerMin);
System.out.println(min+":"+c);
System.out.println("min-1变成了最大的正数 :"+Integer.valueOf(d,2).toString()+"--- >"+d);
2147483647:1111111111111111111111111111111
max+1还是:2147483647--- >1111111111111111111111111111111
-2147483648:10000000000000000000000000000000
min-1变成了最大的正数 :2147483647--- >1111111111111111111111111111111
守护
线程;
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
Daemon的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者(User Thread),Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。
Java代码 收藏代码
Runnable tr=new TestRunnable(); //线程
接口
Thread
thread=new Thread(tr);
thread.setDaemon(true); //设置守护线程
thread.start(); //开始执行分进程
它的存在主要是为了理解虚拟机的生命周期。当我们运行java命令,从main函数进入的那一刻起,虚拟机就开始启动运行了。Main所在的主线程也会启动起来,它属于非守护线程。与之同时一些守护线程也会同时启动,最典型的守护线程代表就是GC(垃圾收集器)线程。JVM 虚拟机什么时候退出呢?是在所有的非守护线程结束的那一刻,JVM就exit。注意这个时候守护线程并未退出,很可能还要继续完成它的本职工作之后才会结束,但虚拟机的生命周期已经提前于它结束了。 [虚拟机在非守护线程结束之后,强制关闭守护线程,这点要特别注意]
ReturnAddress类型:
虽然Java虚拟机也把boolean看做基本类型,但是指令集对boolean只有很有限的支持,当编译器把Java源代码编译为字节码时,它会用int或者byte来表示boolean。在Java虚拟机中,false是由整数0来表示的,所有非0整数都表示true,涉及boolean值的操作则会使用int。另外,boolean数组是当做byte数组来访问的,但是在“堆”区,它也可以被表示为位域。
Java虚拟机还有一个只在内部使用的基本类型:returnAddress,Java
程序员不能使用这个类型,这个基本类型被用来实现Java程序中的finally子句。该类型是jsr, ret以及jsr_w指令需要使用到的,它的值是JVM指令的操作码的指针。returnAddress类型不是简单
意义上的数值,不属于任何一种基本类型,并且它的值是不能被运行中的程序所修改的。
Java虚拟机的引用类型被统称为“引用(reference)”,有三种引用类型:类类型、接口类型、以及数组类型,它们的值都是对动态
创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在Java虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是null,它表示该引用变量没有引用任何对象。
returnAddress是Java虚拟机内部使用的基本类型,这个类型被用来实现Java程序中的finally子句。
JVM有自己的Heap,能被所有线程共享,存储着所有的对象,内存是动态被分配的。对于每个线程,拥有自己的Stack,栈里面存储的单位叫做 Frame(桢)。桢里面就记录着初始变量、对象引用地址、方法返回值等数据。JVM还有一个叫做Method Area(方法块)的地方,存储着一段一段的可执行代码,每一段就是一个方法体,也能被所有线程共享。所以我们说一个线程其实从run方法跑起来,跟它的类中声明的其他方法是两个概念,其他方法变为资源被线程使用。
JVM有自己管理内存的方案,因为它具有文件系统的功能,我们可以看成一个小型的数据库,内部有许许多多不同的表。表的字段可能是另外一张表的地址,也可以直接就是一个存储数据值的地址值。JVM所有对运行时候类的解析验证计算等管理工作,实际上都是在管理这些表的变动,如果我们从数据库的角度来看,JVM所做的就是根据你的代码来操作那么多个表最后返回给你结果的过程。里面的表结构包括class的表、field表、method表、 attribute表等。
JAVA虚拟机的生命周期JAVA虚拟机的生命周期:当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。如果同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例,每个Java程序都运行于它自己的Java虚拟机实例中。每个Java虚拟机都有一个类装载子系统,它根据给定的全限定名来装入类型(类或接口)。同样,每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载类的方法中的指令。
当JAVA虚拟机运行一个程序时,它需要内存来存储许多东西,例如:字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数,返回值、局部变量等等。Java虚拟机把这些东西都组织到几个“运行时数据区”中,以便于管理。
某些运行时数据区是由程序中所有线程共享的,还有一些则只能由一个线程拥有。每个Java虚拟机实例都有一个方法区以及一个堆,它们是由该虚拟机实例中所有的线程共享的。当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息。然后把这些类型信息放到方法区中。当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放到堆中。
当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)以及一个Java栈,如果线程正在执行的是一个Java方法(非本地方法),那么PC寄存器的值将总是指向下一条将被执行的指令,而它的Java栈则总是存储该线程中Java方法调用的状态——包括它的局部变量,被调用时传进来的参数、返回值,以及运算的中间结果等等。而本地方法调用的状态,则是以某种依赖于具体实现的方法存储在本地方法栈中,也可能是在寄存器或者其他某些与特定实现相关的内存区中。
Java栈是由许多栈帧(stack frame)组成的,一个栈帧包含一个Java方法调用的状态。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。
Java虚拟机没有寄存器,其指令集使用Java栈来存储中间数据。这样设计的原因是为了保持Java虚拟机的指令集尽量紧凑、同时也便于Java虚拟机在那些只有很少通用寄存器的平台上实现。另外,Java虚拟机这种基于栈的体系结构,也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。
下图描绘了Java虚拟机为每一个线程创建的内存区,这些内存区域是私有的,任何线程都不能访问另一个线程的PC寄存器或者Java栈。
上图展示了一个虚拟机实例的快照,它有三个线程正在执行。线程1和线程2都正在执行Java方法,而线程3则正在执行一个本地方法。
Java栈都是向下生长的,而栈顶都显示在图的底部。当前正在执行的方法的栈帧则以浅色表示,对于一个正在运行Java方法的线程而言,它的PC寄存器总是指向下一条将被执行的指令。
下一章将讲解 jvm核心之一 类加载
field attribute property的含义:
基于目前最新的UML2.0规范:
l 总体上来说,Attribute是Property的子集,Property会在适当的时机表现为Attribute;
l Property出现在类图的元模型中,代表了Class的所有结构化特征;Attribute没有出现在元模型中,它仅仅在Class的概念中存在,没有相应的语法了;
l field一般就是attribute