1991 年Sun公司的James Gosling(詹姆斯·高斯林)等人开始开发名称为 Oak 的语言,但愿用于控制嵌入在有线电视交换盒、PDA等的微处理器。1994年将Oak语言改名为Java。 java
J2EE:Java PlatformEnterprise Edition,开发企业环境下的应用程序,主要针对web程序开发;linux
J2SE:Java PlatformStandard Edition,完成桌面应用程序的开发,是其它二者的基础;web
J2ME:Java PlatformMicro Edition,开发电子消费产品和嵌入式设备,如手机中的程序; 数据库
1,JDK:JavaDevelopment Kit,java的开发和运行环境,java的开发工具和jre。express
2,JRE:Java RuntimeEnvironment,java程序的运行环境,java运行的所需的类库+JVM。编程
3,配置环境变量:让java jdk\bin目录下的工具,能够在任意目录下运行,缘由是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮咱们去找指定的目录。windows
4,javac命令和java命令作什么事情呢?数组
要知道java是分两部分的:一个是编译,一个是运行。安全
javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm能够识别的字节码文件。也就是class文件,也就是java的运行程序。服务器
java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.
Java为何能跨平台:
由于Java程序编译以后的代码不是能被硬件系统直接运行的代码,而是一种“中间码”,二进制字节码的class文件,jvm就解释执行class文件。不一样的硬件平台上安装有不一样的Java虚拟机(JVM),由JVM来把字节码再“翻译”成所对应的硬件平台可以执行的代码。因此它的代码就能不经修改,就能在不一样平台的jvm上运行(在UNIX用UNIX的jvm,在linux上用linux的jvm,在windows上用windows的jvm)。假如用windows移植到UNIX,只需把java文件是UNIX的jvm上编译成class文件,而后用jvm运行就能够了.所以对于Java编程者来讲,不须要考虑硬件平台是什么。因此Java能够跨平台。
&:位运算符,无论怎样,都会执行"&"符号左右两边的程序。
&&:逻辑运算符,只有当符号"&&"左边程序为真(true)后,才会执行符号"&&"右边的程序。
例子: if(str != null & !"".equals(str)),if(str != null && !"".equals(str))
使用场景:条件判断下使用&&比使用&效率更高,&通常用于位运算。
|和||同理可证。
1:这个关键字是一个修饰符,能够修饰类,方法,变量。
2:被final修饰的类是一个最终类,不能够被继承。
3:被final修饰的方法是一个最终方法,不能够被覆盖。
4:被final修饰的变量是一个常量,只能赋值一次。
常量名称定义时,有规范,全部字母都大写,若是由多个单词组成,中间用 _ 链接。
例子:private staticfiinal String OS_TYPE = “windows”;
值类型是存储在内存中的堆栈(之后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其自己则存储在堆中。
==操做:比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。
equals操做:表示的两个变量是不是对同一个对象的引用,即堆中的内容是否相同。
==比较的是2个对象的地址,而equals比较的是2个对象的内容。
显然,当equals为true时,==不必定为true;
例子:
1、String中的equals和==
一、
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 ="Monday";
}
}
上面这段程序中,到底有几个对象呢?
来检测一下吧,稍微改动一下程序
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 ="Monday";
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1!= s2");
}
}
编译并运行程序,输出:s1== s2说明:s1 与 s2 引用同一个 String 对象 -- "Monday"!
2.再稍微改动一下程序,会有更奇怪的发现:
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 = new String("Monday");
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))System.out.println("s1 equals s2");
else
System.out.println("s1 not equals s2");
}
}
咱们将s2用new操做符建立
程序输出:
s1 != s2
s1 equals s2
说明:s1 s2分别引用了两个"Monday"String对象
3. 字符串缓冲池
原来,程序在运行的时候会建立一个字符串缓冲池当使用 s2 = "Monday" 这样的表达是建立字符串的时候,程序首先会在这个String缓冲池中寻找相同值的对象,在第一个程序中,s1先被放到了池中,因此在s2被建立的时候,程序找到了具备相同值的 s1
将s2引用s1所引用的对象"Monday"
第二段程序中,使用了 new操做符,他明白的告诉程序:"我要一个新的!不要旧的!"因而一个新的"Monday"Sting对象被建立在内存中。他们的值相同,可是位置不一样,一个在池中游泳一个在岸边休息。哎呀,真是资源浪费,明明是同样的非要分开作什么呢?
4.
再次更改程序:
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 = new String("Monday");
s2 = s2.intern();
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))System.out.println("s1 equals s2");
else
System.out.println("s1 not equals s2");
}
}
此次加入:s2 =s2.intern();
程序输出:
s1 == s2
s1 equals s2
原来,(java.lang.String的intern()方法"abc".intern()方法的返回值仍是字符串"abc",表面上看起来好像这个方法没什么用处。但实际上,它作了个小动做:检查字符串池里是否存在"abc"这么一个字符串,若是存在,就返回池里的字符串;若是不存在,该方法会把"abc"添加到字符串池中,而后再返回它的引用。
)
更好的办法:
把全部的String都intern()到缓冲池去吧
最好在用到new的时候就进行这个操做
String s2 = newString("Monday").intern();
而后就能够用==比较两个字符串的值了
2、简单数据类型和封装类中的equals和==
Java为每个简单数据类型提供了一个封装类,每一个基本数据类型能够封装成对象类型。
除int(Integer)和char(Character),其他类型首字母大写即成封装类类型名。double (Double), float(Float),long(Long),short(Short),byte(Byte),boolean(Boolean).
以int和Integer为例说明
Java中int和Integer区别以下:
1.int是基本的数据类型,默认值能够为0;2.Integer是int的封装类,默认值为null;3.int和Integer均可以表示某一个数值;4.int和Integer不可以互用,由于他们两种不一样的数据类型;
int a1=1;
int a2=1;
Integer b1 =new Integer(1);
Integer b2 =new Integer(1);
------------------------------
a1==a2 这个是成立的,很简单,都知道a1==b1 这个是不成立的.表达式的值为 false,它们是不一样的数据类型b1==b2 这个也是不成立的.表达式的值为 false,虽然是相同的数据类型,可是它们是两个对象,==比较的是2个对象的地址,它们的地址是不相等的,内容相等都是1;
b1.equals(b2)==true 这个是成立的,表达式的值为 true. 相同数据类型,两个对象,地址不一样,内容相同, quals比较的是2个对象的内容,因此成立。
(a.equals(b),由于equals比较的是两个对象,因此a,b都不能为基本数据类型,不然会出编译错误。)同理,其它的封装类和基本类型也是这样的.
java中equals和==的区别
==比较的是2个对象的地址,而equals比较的是2个对象的内容。
3、其余类怎么使用equals和==
API里的类大部分都重写了equals方法,没有重写的通常是本身写的类,若是是你本身定义的一个类,比较自定义类用equals和==是同样的,都是比较句柄地址,由于自定义的类是继承于object,而object中的equals就是用==来实现的,你能够看源码。
4、java里equals和hashCode之间什么关系
只是为了维护hashCode 方法的常规协定,才要求用equals比较的两个对象的hashCode相同. equals()和hashCode()都来自java.lang.Object.你固然能够重写.
好比a.equals(b).仅当a的内存地址相等时,才返回true.固然如String等类已经对这个方法进行了重写,比较的就再也不是内存地址了. hashCode()的值也是与内存地址相关的.因此仅当内存地址相等时,hashCode才相等.
一样不少类也重写了这个方法,仍是以String为例:
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i <len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
就不在与内存地址相关了.这样作是为了保证用equals比较返回为true的两个对象,他们的hashCode是相同的.
因此通常重写equals的时候都会重写hashCode().固然,这个至关于一个约定,一个协议.你不这么作并不会错.
5、hashCode
在通常的应用中你不须要了解hashcode的用法,但当你用到hashmap,hashset等集合类时要注意下hashcode。
你想经过一个object的key来拿hashmap的value,hashmap的工做方法是,经过你传入的object的hashcode在内存中找地址,当找到这个地址后再经过equals方法来比较这个地址中的内容是否和你原来放进去的同样,同样就取出value。
因此这里要匹配2部分,hashcode和equals但假如说你new一个object做为key去拿value是永远得不到结果的,由于每次new一个object,这个object的hashcode是永远不一样的,因此咱们要重写hashcode,你能够令你的hashcode是object中的一个恒量,这样永远能够经过你的object的hashcode来找到key的地址,而后你要重写你的equals方法,使内存中的内容也相等。
if 语句适用范围比较广,只要是 boolean表达式均可以用 if 判断;而 switch 只能对基本类型进行数值比较。二者的可比性就仅限在两个基本类型比较的范围内。
说到基本类型的数值比较,那固然要有两个数。而后重点来了——
if 语句每一句都是独立的,看下面的语句:
if (a == 1) ...
else if (a == 2) ...
这样 a 要被读入寄存器两次,1 和 2 分别被读入寄存器一次。因而你是否发现其实 a 读两次是有点多余的,在你所有比较完以前只须要一次读入寄存器就好了,其他都是额外开销。可是 if 语句必须每次都把里面的两个数从内存拿出来读到寄存器,它不知道你其实比较的是同一个 a。
因而 switch case 就出来了,把上面的改为 switch case 版本:
switch (a) {
case 0:
break;
case 1:
}
由于特定的规则,他一开始就知道你要比 a,因而 a 一次性读取,相比 if 节约了不少开销。
1.三者在执行速度方面的比较:StringBuilder > StringBuffer > String
2.String <(StringBuffer,StringBuilder)的缘由
String:字符串常量
StringBuffer:字符串变量
StringBuilder:字符串变量
从上面的名字能够看到,String是“字符创常量”,也就是不可改变的对象。对于这句话的理解你可能会产生这样一个疑问 ,好比这段代码:
String s = "abcd";
s = s+1;
System.out.print(s);// result : abcd1
咱们明明就是改变了String型的变量s的,为何说是没有改变呢?其实这是一种欺骗,JVM是这样解析这段代码的:首先建立对象s,赋予一个abcd,而后再建立一个新的对象s用来执行第二行代码,也就是说咱们以前对象s并无变化,因此咱们说String类型是不可改变的对象了,因为这种机制,每当用String操做字符串时,其实是在不断的建立新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多低。而StringBuffer与StringBuilder就不同了,他们是字符串变量,是可改变的对象,每当咱们用它们对字符串作操做时,其实是在一个对象上操做的,这样就不会像String同样建立一些而外的对象进行操做了,固然速度就快了。
3.一个特殊的例子:
1 String str = “This is only a” + “ simple” + “ test”;
3 StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成str对象的速度简直太快了,而这个时候StringBuffer竟然速度上根本一点都不占优点。其实这是JVM的一个把戏,实际上:
String str = “This is only a” + “ simple” + “test”;
其实就是:
String str = “This is only a simple test”;
因此不须要太多的时间了。但你们这里要注意的是,若是你的字符串是来自另外的String对象的话,速度就没那么快了,譬如:
String str2 = “This is only a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
这时候JVM会规规矩矩的按照原来的方式去作。
4.StringBuilder与 StringBuffer
StringBuilder:线程非安全的
StringBuffer:线程安全的
当咱们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操做是安全的,虽然他的速度最快,可是能够保证StringBuffer是能够正确操做的。固然大多数状况下就是咱们是在单线程下进行的操做,因此大多数状况下是建议用StringBuilder而不用StringBuffer的,就是速度的缘由。
对于三者使用的总结: 1.若是要操做少许的数据用 = String
2.单线程操做字符串缓冲区下操做大量数据 = StringBuilder
3.多线程操做字符串缓冲区下操做大量数据 = StringBuffer
在不作编译优化的状况下,在循环中,循环条件会被反复计算,若是不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。
例子:
import java.util.vector;
class cel {
void method (vector vector) {
for (int i = 0; i < vector.size (); i++) // violation
; // ...
}
}
更正:
class cel_fixed {
void method (vector vector) {
int size = vector.size ()
for (int i = 0; i < size; i++)
; // ...
}
}
array 数组效率最高,但容量固定,没法动态改变,ArrayList容量能够动态增加,但牺牲了效率。
单线程应尽可能使用 HashMap, ArrayList,除非必要,不然不推荐使用HashTable,Vector,她们使用了同步机制,而下降了性能。
例如:
String str="abc";
if(i==1){ list.add(str);}
应修改成:
if(i==1){String str="abc";list.add(str);}
调用方法时传递的参数以及在调用中建立的临时变量都保存在栈(Stack)中,速度较快。其余变量,如静态变量,实例变量等,都在堆(Heap)中建立,速度较慢。
取反操做符(!)下降程序的可读性,因此不要老是使用。
例子:
public class dun {
boolean method (boolean a, boolean b) {
if (!a)
return !a;
else
return !b;
}
}
更正:
若是可能不要使用取反操做符(!)
stringbuffer的构造器会建立一个默认大小(一般是16)的字符数组。在使用中,若是超出这个大小,就会从新分配内存,建立一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数状况下,你能够在建立stringbuffer的时候指定大小,这样就避免了在容量不够的时候自动增加,以提升性能。
例子:
public class rsbc {
void method () {
stringbuffer buffer = new stringbuffer(); // violation
buffer.append ("hello");
}
}
更正:
为stringbuffer提供寝大小。
public class rsbc {
void method () {
stringbuffer buffer = new stringbuffer(max);
buffer.append ("hello");
}
private final int max = 100;
}
在循环体中实例化临时变量将会增长内存消耗
例子:
import java.util.vector;
public class loop {
void method (vector v) {
for (int i=0;i < v.size();i++) {
object o = new object();
o = v.elementat(i);
}
}
}
更正:
在循环体外定义变量,并反复使用
import java.util.vector;
public class loop {
void method (vector v) {
object o;
for (int i=0;i<v.size();i++) {
o = v.elementat(i);
}
}
}
例子:
public class ifas {
void method(boolean istrue) {
if (istrue) {
_value = 0;
} else {
_value = 1;
}
}
private int _value = 0;
}
更正:
public class ifas {
void method(boolean istrue) {
_value = (istrue ? 0 : 1); // compact expression.
}
private int _value = 0;
}
字符串的分析在不少应用中都是常见的。使用indexof()和substring()来分析字符串容易致使 stringindexoutofboundsexception。而使用stringtokenizer类来分析字符串则会容易一些,效率也会高一些。
StringTokenizer token=new StringTokenizer(orginStr,".");
把try/catch块放入循环体内,会极大的影响性能,若是编译jit被关闭或者你所使用的是一个不带jit的jvm,性能会将降低21%之多!
例子:
import java.io.fileinputstream;
public class try {
void method (fileinputstream fis) {
for (int i = 0; i < size; i++) {
try {
_sum += fis.read();
} catch (exception e) {}
}
}
private int _sum;
}
更正:
将try/catch块移出循环
voidmethod (fileinputstream fis) {
try {
for (int i = 0; i < size; i++) {
_sum += fis.read();
}
} catch (exception e) {}
方法的同步须要消耗至关大的资料,在一个循环中调用它绝对不是一个好主意。
例子:
import java.util.vector;
public class syn {
public synchronized void method (object o) {
}
private void test () {
for (int i = 0; i < vector.size(); i++) {
method (vector.elementat(i)); // violation
}
}
private vector vector = new vector (5, 5);
}
更正:
不要在循环体中调用同步方法,若是必须同步的话,推荐如下方式:
import java.util.vector;
public class syn {
public void method (object o) {
}
private void test () {
synchronized{//在一个同步块中执行非同步方法
for (int i = 0; i < vector.size(); i++) {
method(vector.elementat(i));
}
}
}
private vector vector = new vector (5, 5);
在不作编译优化的状况下,在循环中,循环条件会被反复计算,若是不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。
例子:
import java.util.vector;
class cel {
void method (vector vector) {
for (int i = 0; i < vector.size (); i++) // violation
; // ...
}
}
更正:
class cel_fixed {
void method (vector vector) {
int size = vector.size ()
for (int i = 0; i < size; i++)
; // ...
}
}
这毫无心义,若是代码中出现”The value of the local variable i is not used”、”The importjava.util is never used”,那么请删除这些无用的内容
反射是Java提供给用户一个很强大的功能,功能强大每每意味着效率不高。不建议在程序运行过程当中使用尤为是频繁使用反射机制,特别是Method的invoke方法,若是确实有必要,一种建议性的作法是将那些须要经过反射加载的类在项目启动的时候经过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。
这是一个比较常见的小技巧了,若是有如下代码:
String str = "123";
if (str.equals("123")) {
}
建议修改成:
String str = "123";
if ("123".equals(str)) {
...
}
这么作主要是能够避免空指针异常
这毫不会获得想要的结果:
public static void main(String[] args){
long l = 12345678901234L;
int i = (int)l;
System.out.println(i);
}
咱们可能指望获得其中的某几位,可是结果倒是:
1942892530
解释一下。Java中long是8个字节64位的,因此12345678901234在计算机中的表示应该是:
0000 0000 0000 0000 0000 1011 0011 10100111 0011 1100 1110 0010 1111 1111 0010
一个int型数据是4个字节32位的,从低位取出上面这串二进制数据的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010
这串二进制表示为十进制1942892530,因此就是咱们上面的控制台上输出的内容。从这个例子上还能顺便获得两个结论:
一、整型默认的数据类型是int,long l =12345678901234L,这个数字已经超出了int的范围了,因此最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,因此定义float的时候要写成”"float f = 3.5f”
二、接下来再写一句”int ii = l +i;”会报错,由于long + int是一个long,不能赋值给int
遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为何,很简单:
一、String.valueOf()方法底层调用了Integer.toString()方法,可是会在调用前作空判断
二、Integer.toString()方法就不说了,直接调用了
三、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串
三者对比下来,明显是2最快、1次之、3最慢
意思是,好比我有这么一段代码:
try{
XXX.close();
YYY.close();
}catch (Exception e){
}
建议修改成:
try{
XXX.close();
}catch (Exception e) {
...
}try{
YYY.close();
}catch (Exception e) {
...}
虽然有些麻烦,却能避免资源泄露。咱们想,若是没有修改过的代码,万一XXX.close()抛异常了,那么就进入了cath块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引发资源句柄泄露的。而改成下面的写法以后,就保证了不管如何XXX和YYY都会被close掉。
线程池的原理:
其实线程池的原理很简单,相似于操做系统中的缓冲区的概念,它的流程以下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。可能你也许会问:为何要搞得这么麻烦,若是每当客户端有新的请求时,我就建立一个新的线程不就完了?这也许是个不错的方法,由于它能使得你编写代码相对容易一些,但你却忽略了一个重要的问题?那就是性能!就拿我所在的单位来讲,个人单位是一个省级数据大集中的银行网络中心,高峰期每秒的客户端请求并发数超过100,若是为每一个客户端请求建立一个新线程的话,那耗费的CPU时间和内存将是惊人的,若是采用一个拥有200个线程的线程池,那将会节约大量的系统资源,使得更多的CPU时间和内存用来处理实际的商业应用,而不是频繁的线程建立与销毁。
数据库链接池的原理:
数据库链接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤其突出。
一个数据库链接对象均对应一个物理数据库链接,每次操做都打开一个物理链接,使用完都关闭链接,这样形成系统的性能低下。数据库链接池的解决方案是在应用程序启动时创建足够的数据库链接,并讲这些链接组成一个链接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的链接进行申请、使用和释放。对于多于链接池中链接数的并发请求,应该在请求队列中排队等待。而且应用程序能够根据池中链接的使用率,动态增长或减小池中的链接数。
链接池技术尽量多地重用了消耗内存地资源,大大节省了内存,提升了服务器地服务效率,可以支持更多的客户服务。经过使用链接池,将大大提升程序运行效率,同时,咱们能够经过其自身的管理机制来监视数据库链接的数量、使用状况等。
1)最小链接数是链接池一直保持的数据库链接,因此若是应用程序对数据库链接的使用量不大,将会有大量的数据库链接资源被浪费;
2)最大链接数是链接池能申请的最大链接数,若是数据库链接请求超过此数,后面的数据库链接请求将被加入到等待队列中,这会影响以后的数据库操做。
带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这能够极大地提高IO效率
要知道,当某个对象被定义为static的变量所引用,那么gc一般是不会回收这个对象所占有的堆内存的,如:
public class A{
private static B b = new B();
}
此时静态变量b的生命周期与A类相同,若是A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止
总之:无生命周期的用static,有生命周期的不用static。