毕向东—Java基础知识总结(超级经典) 感受很不错,能够当作本身学习Java知识的考评html
写代码:java
1,明确需求。我要作什么?linux
2,分析思路。我要怎么作?1,2,3。程序员
3,肯定步骤。每个思路部分用到哪些语句,方法,和对象。web
4,代码实现。用具体的java语言代码把思路体现出来。面试
学习新技术的四点:正则表达式
1,该技术是什么?算法
2,该技术有什么特色(使用注意):express
3,该技术怎么使用。demo编程
4,该技术何时用?test。
——————————————————————————————————————————————————————
一:java概述:
1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,但愿用于控制嵌入在有线电视交换盒、PDA等的微处理器;
1994年将Oak语言改名为Java;
Java的三种技术架构:
JAVAEE:Java Platform Enterprise Edition,开发企业环境下的应用程序,主要针对web程序开发;
JAVASE:Java Platform Standard Edition,完成桌面应用程序的开发,是其它二者的基础;
JAVAME:Java Platform Micro Edition,开发电子消费产品和嵌入式设备,如手机中的程序;
1,JDK:Java Development Kit:java的开发和运行环境,java的开发工具和jre。
2,JRE:Java Runtime Environment:java程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。
3,配置环境变量:让java jdk\bin目录下的工具,能够在任意目录下运行,缘由是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮咱们去找指定的目录。
环境变量的配置:
①:永久配置方式:JAVA_HOME=%安装路径%\Java\jdk
path=%JAVA_HOME%\bin
②:临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin
特色:系统默认先去当前路径下找要执行的程序,若是没有,再去path中设置的路径下找。
classpath的配置:
①:永久配置方式:classpath=.;c:\;e:\
②:临时配置方式:set classpath=.;c:\;e:\
注意:在定义classpath环境变量时,须要注意的状况
若是没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件;
若是指定了classpath,那么会在指定的目录下查找要运行的类文件。
还会在当前目录找吗?两种状况:
①:若是classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。
②:若是classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。
通常不指定分号,若是没有在指定目录下找到要运行的类文件,就报错,这样能够调试程序。
4,javac命令和java命令作什么事情呢?
要知道java是分两部分的:一个是编译,一个是运行。
javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm能够识别的字节码文件。也就是class文件,也就是java的运行程序。
java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.一个文件要被执行,必需要有一个执行的起始点,这个起始点就是main函数.
——————————————————————————————————————————————————————
二:java语法基础:
1,关键字:其实就是某种语言赋予了特殊含义的单词。
保留字:其实就是尚未赋予特殊含义,可是准备往后要使用过的单词。
2,标示符:其实就是在程序中自定义的名词。好比类名,变量名,函数名。包含 0—九、a—z、$、_ ;
注意:
①:数字不能够开头。
②:不可使用关键字。
3,常量:是在程序中的不会变化的数据。
4,变量:其实就是内存中的一个存储空间,用于存储常量数据。
做用:方便于运算。由于有些数据不肯定。因此肯定该数据的名词和存储空间。
特色:变量空间能够重复使用。
何时定义变量?只要是数据不肯定的时候,就定义变量。
变量空间的开辟须要什么要素呢?
①:这个空间要存储什么数据?数据类型。
②:这个空间叫什么名字啊?变量名称。
③:这个空间的第一次的数据是什么? 变量的初始化值。
变量的做用域和生存期:
变量的做用域:
做用域从变量定义的位置开始,到该变量所在的那对大括号结束;
生命周期:
变量从定义的位置开始就在内存中活了;
变量到达它所在的做用域的时候就在内存中消失了;
数据类型:
①:基本数据类型:byte、short、int、long、float、double、char、boolean
②:引用数据类型: 数组、类、接口。
级别从低到高为:byte,char,short(这三个平级)——>int——>float——>long——>double
自动类型转换:从低级别到高级别,系统自动转的;
强制类型转换:什么状况下使用?把一个高级别的数赋给一个别该数的级别低的变量;
运算符号:
①、算术运算符。
+ — * / %
%:任何整数模2不是0就是1,因此只要改变被模数就能够实现开关运算。
+:链接符。
++,——
②、赋值运算符。
= += —= *= /= %=
③、比较运算符。
特色:该运算符的特色是:运算完的结果,要么是true,要么是false。
④、逻辑运算符。
& | ^ ! && ||
逻辑运算符除了 ! 外都是用于链接两个boolean类型表达式。
&: 只有两边都为true结果是true。不然就是false。
|:只要两边都为false结果是false,不然就是true
^:异或:和或有点不同。
两边结果同样,就为false。
两边结果不同,就为true.
& 和 &&区别: & :不管左边结果是什么,右边都参与运算。
&&:短路与,若是左边为false,那么右边不参数与运算。
| 和|| 区别: |:两边都运算。
||:短路或,若是左边为true,那么右边不参与运算。
⑤、位运算符:用于操做二进制位的运算符。
& | ^
<< >> >>>(无符号右移)
练习:对两个变量的数据进行互换。不须要第三方变量。
int a = 3,b = 5;//——>b = 3,a = 5;
a = a + b; a = 8;
b = a — b; b = 3;
a = a — b; a = 5;
a = a ^ b;//
b = a ^ b;//b = a ^ b ^ b = a
a = a ^ b;//a = a ^ b ^ a = b;
练习:高效的算出 2*8 = 2<<3;
5,语句。
If switch do while while for
这些语句何时用?
1)、当判断固定个数的值的时候,可使用if,也可使用switch。
可是建议使用switch,效率相对较高。
switch(变量){
case 值:要执行的语句;break;
…
default:要执行的语句;
}
工做原理:用小括号中的变量的值依次和case后面的值进行对比,和哪一个case后面的值相同了
就执行哪一个case后面的语句,若是没有相同的则执行default后面的语句;
细节:①:break是能够省略的,若是省略了就一直执行到遇到break为止;
②、switch 后面的小括号中的变量应该是byte,char,short,int四种类型中的一种;
③、default能够写在switch结构中的任意位置;若是将default语句放在了第一行,
则无论expression与case中的value是否匹配,程序会从default开始执行直到第一个break出现。
2)、当判断数据范围,获取判断运算结果boolean类型时,须要使用if。
3)、当某些语句须要执行不少次时,就用循环结构。
while和for能够进行互换。
区别在于:若是须要定义变量控制循环次数。建议使用for。由于for循环完毕,变量在内存中释放。
break:做用于switch ,和循环语句,用于跳出,或者称为结束。
break语句单独存在时,下面不要定义其余语句,由于执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字便可,这个名字称之为标号。
continue:只做用于循环结构,继续循环用的。
做用:结束本次循环,继续下次循环。该语句单独存在时,下面不能够定义语句,执行不到。
6,函 数:为了提升代码的复用性,能够将其定义成一个单独的功能,该功能的体现就是java中的函数。函数就是体现之一。
java中的函数的定义格式:
修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数1,…){
执行语句;
return 返回值;
}
当函数没有具体的返回值时,返回的返回值类型用void关键字表示。
若是函数的返回值类型是void时,return语句能够省略不写的,系统会帮你自动加上。
return的做用:结束函数。结束功能。
如何定义一个函数?
函数其实就是一个功能,定义函数就是实现功能,经过两个明确来完成:
①、明确该功能的运算完的结果,实际上是在明确这个函数的返回值类型。
②、在实现该功能的过程当中是否有未知内容参与了运算,其实就是在明确这个函数的参数列表(参数类型&参数个数)。
函数的做用:
1)、用于定义功能。
2)、用于封装代码提升代码的复用性。
注意:函数中只能调用函数,不能定义函数。
主函数:
1)、保证该类的独立运行。
2)、由于它是程序的入口。
3)、由于它在被jvm调用。
函数定义名称是为何呢?
答:1)、为了对该功能进行标示,方便于调用。
2)、为了经过名称就能够明确函数的功能,为了增长代码的阅读性。
重载的定义是:在一个类中,若是出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不一样,便可称之为该函数重载了。
如何区分重载:当函数同名时,只看参数列表。和返回值类型不要紧。
7,数 组:用于存储同一类型数据的一个容器。
好处:能够对该容器中的数据进行编号,从0开始。数组用于封装数据,就是一个具体的实体。
如何在java中表现一个数组呢?两种表现形式。
1)、元素类型[] 变量名 = new 元素类型[元素的个数];
2)、元素类型[] 变量名 = {元素1,元素2...};
元素类型[] 变量名 = new 元素类型[]{元素1,元素2...};
——————————————————————————————————————————————————————
//二分查找法。必须有前提:数组中的元素要有序。
public static int halfSeach_2(int[] arr,int key){
int min,max,mid;
min = 0;
max = arr.length—1;
mid = (max+min)>>1; //(max+min)/2;
while(arr[mid]!=key){
if(key>arr[mid]){
min = mid + 1;
}
else if(key<arr[mid])
max = mid — 1;
if(max<min)
return —1;
mid = (max+min)>>1;
}
return mid;
}
——————————————————————————————————————————————————————
java分了5片内存。
1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。
栈:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 );
只要数据运算完成所在的区域结束,该数据就会被释放。
堆:用于存储数组和对象,也就是实体。啥是实体啊?就是用于封装多个数据的。
1:每个实体都有内存首地址值。
2:堆内存中的变量都有默认初始化值。由于数据类型不一样,值也不同。
3:垃圾回收机制。
——————————————————————————————————————————————————————
三:面向对象:★★★★★
特色:1:将复杂的事情简单化。
2:面向对象将之前的过程当中的执行者,变成了指挥者。
3:面向对象这种思想是符合如今人们思考习惯的一种思想。
过程和对象在咱们的程序中是如何体现的呢?
过程其实就是函数;
对象是将函数等一些内容进行了封装。
匿名对象使用场景:
1:当对方法只进行一次调用的时候,可使用匿名对象。
2:当对象对成员进行屡次调用时,不能使用匿名对象。必须给对象起名字。
在类中定义其实都称之为成员。成员有两种:
1:成员变量:其实对应的就是事物的属性。
2:成员函数:其实对应的就是事物的行为。
因此,其实定义类,就是在定义成员变量和成员函数。可是在定义前,必须先要对事物进行属性和行为的分析,才能够用代码来体现。
private int age;//私有的访问权限最低,只有在本类中的访问有效。
注意:私有仅仅是封装的一种体现形式而已。
私有的成员:其余类不能直接建立对象访问,因此只有经过本类对外提供具体的访问方式来完成对私有的访问,
能够经过对外提供函数的形式对其进行访问。
好处:能够在函数中加入逻辑判断等操做,对数据进行判断等操做。
总结:开发时,记住,属性是用于存储数据的,直接被访问,容易出现安全隐患,因此,类中的属性一般被私有化,并对外提供公共的访问方法。
这个方法通常有两个,规范写法:对于属性 xxx,可使用setXXX(),getXXX()对其进行操做。
类中怎么没有定义主函数呢?
注意:主函数的存在,仅为该类是否须要独立运行,若是不须要,主函数是不用定义的。
主函数的解释:保证所在类的独立运行,是程序的入口,被jvm调用。
成员变量和局部变量的区别:
1:成员变量直接定义在类中。
局部变量定义在方法中,参数上,语句中。
2:成员变量在这个类中有效。
局部变量只在本身所属的大括号内有效,大括号结束,局部变量失去做用域。
3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。
局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。
构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具备针对性,函数中的一种。
特色:
1:该函数的名称和所在类的名称相同。
2:不须要定义返回值类型。
3:该函数没有具体的返回值。
记住:全部对象建立时,都须要初始化才可使用。
注意事项:一个类在定义时,若是没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,
为了方便该类建立对象,完成初始化。若是在类中自定义了构造函数,那么默认的构造函数就没有了。
一个类中,能够有多个构造函数,由于它们的函数名称都相同,因此只能经过参数列表来区分。因此,
一个类中若是出现多个构造函数。它们的存在是以重载体现的。
构造函数和通常函数有什么区别呢?
1:两个函数定义格式不一样。
2:构造函数是在对象建立时,就被调用,用于初始化,并且初始化动做只执行一次。
通常函数,是对象建立后,须要调用才执行,能够被调用屡次。
何时使用构造函数呢?
分析事物时,发现具体事物一出现,就具有了一些特征,那就将这些特征定义到构造函数内。
构造代码块和构造函数有什么区别?
构造代码块:是给全部的对象进行初始化,也就是说,全部的对象都会调用一个代码块。只要对象一创建。就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化。它具备针对性。
Person p = new Person();
建立一个对象都在内存中作了什么事情?
1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈—进栈),而后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)
——————————————————————————————————————————————————————
封 装(面向对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离;便于使用;提升重用性;安全性。
封装原则:将不须要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。
this:表明对象。就是所在函数所属对象的引用。
this到底表明什么呢?哪一个对象调用了this所在的函数,this就表明哪一个对象,就是哪一个对象的引用。
开发时,何时使用this呢?
在定义功能时,若是该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。
this 还能够用于构造函数间的调用。
调用格式:this(实际参数);
this对象后面跟上 . 调用的是成员属性和成员方法(通常方法);
this对象后面跟上 () 调用的是本类中的对应参数的构造函数。
注意:用this调用构造函数,必须定义在构造函数的第一行。
由于构造函数是用于初始化的,因此初始化动做必定要执行。不然编译失败。
static:★★★ 关键字,是一个修饰符,用于修饰成员(成员变量和成员函数)。
特色:
1,想要实现对象中的共性数据的对象共享。能够将这个数据进行静态修饰。
2,被静态修饰的成员,能够直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。
3,静态随着类的加载而加载。并且优先于对象存在。
弊端:
1,有些数据是对象特有的数据,是不能够被静态修饰的。由于那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。因此,在定义静态时,必需要明确,这个数据是不是被对象所共享的。
2,静态方法只能访问静态成员,不能够访问非静态成员。
由于静态方法加载时,优先于对象存在,因此没有办法访问对象中的成员。
3,静态方法中不能使用this,super关键字。
由于this表明对象,而静态在时,有可能没有对象,因此this没法使用。
4,主函数是静态的。
何时定义静态成员呢?或者说:定义成员时,到底需不须要被静态修饰呢?
成员分两种:
1,成员变量。(数据共享时静态化)
该成员变量的数据是不是全部对象都同样:
若是是,那么该变量须要被静态修饰,由于是共享的数据。
若是不是,那么就说这是对象的特有数据,要存储到对象中。
2,成员函数。(方法中没有调用特有数据时就定义成静态)
若是判断成员函数是否须要被静态修饰呢?
只要参考,该函数内是否访问了对象中的特有数据:
若是有访问特有数据,那方法不能被静态修饰。
若是没有访问过特有数据,那么这个方法须要被静态修饰。
成员变量和静态变量的区别:
1,成员变量所属于对象。因此也称为实例变量。
静态变量所属于类。因此也称为类变量。
2,成员变量存在于堆内存中。
静态变量存在于方法区中。
3,成员变量随着对象建立而存在。随着对象被回收而消失。
静态变量随着类的加载而存在。随着类的消失而消失。
4,成员变量只能被对象所调用 。
静态变量能够被对象调用,也能够被类名调用。
因此,成员变量能够称为对象的特有数据,静态变量称为对象的共享数据。
静态的注意:静态的生命周期很长。
静态代码块:就是一个有静态关键字标示的一个代码块区域。定义在类中。
做用:能够完成类的初始化。静态代码块随着类的加载而执行,并且只执行一次(new 多个对象就只执行一次)。若是和主函数在同一类中,优先于主函数执行。
Public:访问权限最大。
static:不须要对象,直接类名便可。
void:主函数没有返回值。
Main:主函数特定的名称。
(String[] args):主函数的参数,是一个字符串数组类型的参数,jvm调用main方法时,传递的实际参数是 new String[0]。
jvm默认传递的是长度为0的字符串数组,咱们在运行该类时,也能够指定具体的参数进行传递。能够在控制台,运行该类时,在后面加入参数。参数之间经过空格隔开。jvm会自动将这些字符串参数做为args数组中的元素,进行存储。
静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块 ———> 构造代码块 ———> 构造函数;
生成Java帮助文档:命令格式:javadoc –d 文件夹名 –auther –version *.java
//格式
/**
*类描述
*@author 做者名
*@version 版本号
*/
/**
*方法描述
*@param 参数描述
*@return 返回值描述
*/
——————————————————————————————————————————————————————
设计模式:解决问题最行之有效的思想。是一套被反复使用、多数人知晓的、通过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
java中有23种设计模式:
单例设计模式:★★★★★
解决的问题:保证一个类在内存中的对象惟一性。
好比:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操做其中数据,又要保证多个程序读到的是同一个配置文件对象,就须要该配置文件对象在内存中是惟一的。
Runtime()方法就是单例设计模式进行设计的。
如何保证对象惟一性呢?
思想:
1,不让其余程序建立该类对象。
2,在本类中建立一个本类对象。
3,对外提供方法,让其余程序获取这个对象。
步骤:
1,由于建立对象都须要构造函数初始化,只要将本类中的构造函数私有化,其余程序就没法再建立该类对象;
2,就在类中建立一个本类的对象;
3,定义一个方法,返回该对象,让其余程序能够经过方法就获得本类对象。(做用:可控)
代码体现:
1,私有化构造函数;
2,建立私有并静态的本类对象;
3,定义公有并静态的方法,返回该对象。
——————————————————————————————————————————————————————
饿汉式
class Single{
private Single(){} //私有化构造函数。
private static Single s = new Single(); //建立私有并静态的本类对象。
public static Single getInstance(){ //定义公有并静态的方法,返回该对象。
return s;
}
}
——————————————————————————————————————————————————————
懒汉式:延迟加载方式。
class Single2{
private Single2(){}
private static Single2 s = null;
public static Single2 getInstance(){
if(s==null)
s = new Single2();
return s;
}
}
——————————————————————————————————————————————————————
继 承(面向对象特征之一)
好处:
1:提升了代码的复用性。
2:让类与类之间产生了关系,提供了另外一个特征多态的前提。
父类的由来:实际上是由多个类不断向上抽取共性内容而来的。
java中对于继承,java只支持单继承。java虽然不直接支持多继承,可是保留了这种多继承机制,进行改良。
单继承:一个类只能有一个父类。
多继承:一个类能够有多个父类。
为何不支持多继承呢?
由于当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪个呢?由于父类中的方法中存在方法体。
可是java支持多重继承。A继承B B继承C C继承D。
多重继承的出现,就有了继承体系。体系中的顶层父类是经过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。
因此,一个体系要想被使用,直接查阅该系统中的父类的功能便可知道该体系的基本用法。那么想要使用一个体系时,须要创建对象。建议创建最子类对象,由于最子类不只可使用父类中的功能。还可使用子类特有的一些功能。
简单说:对于一个继承体系的使用,查阅顶层父类中的内容,建立最底层子类的对象。
子父类出现后,类中的成员都有了哪些特色:
1:成员变量。
当子父类中出现同样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
若是想要调用父类中的属性值,须要使用一个关键字:super
This:表明是本类类型的对象引用。
Super:表明是子类所属的父类中的内存空间引用。
注意:子父类中一般是不会出现同名成员变量的,由于父类中只要定义了,子类就不用在定义了,直接继承过来用就能够了。
2:成员函数。
当子父类中出现了如出一辙的方法时,创建子类对象会运行子类中的方法。好像父类中的方法被覆盖掉同样。因此这种状况,是函数的另外一个特性:覆盖(复写,重写)
何时使用覆盖呢?当一个类的功能内容须要修改时,能够经过覆盖来实现。
3:构造函数。
发现子类构造函数运行时,先运行了父类的构造函数。为何呢?
缘由:子类的全部构造函数中的第一行,其实都有一条隐身的语句super();
super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。
为何子类对象初始化时,都须要调用父类中的函数?(为何要在子类构造函数的第一行加入这个super()?)
由于子类继承父类,会继承到父类中的数据,必需要看父类是如何对本身的数据进行初始化的。
因此子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。
注意:
子类中全部的构造函数都会默认访问父类中的空参数的构造函数,由于每个子类构造内第一行都有默认的语句super();
若是父类中没有空参数的构造函数,那么子类的构造函数内,必须经过super语句指定要访问的父类中的构造函数。
若是子类构造函数中用this来指定调用子类本身的构造函数,那么被调用的构造函数也同样会访问父类中的构造函数。
问题:super()和this()是否能够同时出现的构造函数中。
两个语句只能有一个定义在第一行,因此只能出现其中一个。
super()或者this():为何必定要定义在第一行?
由于super()或者this()都是调用构造函数,构造函数用于初始化,因此初始化的动做要先完成。
继承的细节:
何时使用继承呢?
当类与类之间存在着所属关系时,才具有了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。
英文书中,所属关系:" is a "
注意:不要仅仅为了获取其余类中的已有成员进行继承。
因此判断所属关系,能够简单看,
若是继承后,被继承的类中的功能,均可以被该子类所具有,那么继承成立。若是不是,不能够继承。
细节二:
在方法覆盖时,注意两点:
1:子类覆盖父类时,必需要保证,子类方法的权限必须大于等于父类方法权限能够实现继承。不然,编译失败。
2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是须要被继承,或者复写的。
这时如何解决问题呢?介绍一个关键字,final:最终。
final特色:
1:这个关键字是一个修饰符,能够修饰类,方法,变量。
2:被final修饰的类是一个最终类,不能够被继承。
3:被final修饰的方法是一个最终方法,不能够被覆盖。
4:被final修饰的变量是一个常量,只能赋值一次。
其实这样的缘由的就是给一些固定的数据起个阅读性较强的名称。
不加final修饰不是也可使用吗?那么这个值是一个变量,是能够更改的。加了final,程序更为严谨。常量名称定义时,有规范,全部字母都大写,若是由多个单词组成,中间用 _ 链接。
抽象类: abstract
抽象:不具体,看不明白。抽象类表象体现。
在不断抽取过程当中,将共性内容中的方法声明抽取,可是方法不同,没有抽取,这时抽取到的方法,并不具体,须要被指定关键字abstract所标示,声明为抽象方法。
抽象方法所在类必定要标示为抽象类,也就是说该类须要被abstract关键字所修饰。
抽象类的特色:
1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(能够描述类和方法,不能够描述变量)。
2:抽象方法只定义方法声明,并不定义方法实现。
3:抽象类不能够被建立对象(实例化)。
4:只有经过子类继承抽象类并覆盖了抽象类中的全部抽象方法后,该子类才能够实例化。不然,该子类仍是一个抽象类。
抽象类的细节:
1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。
2:抽象类中是否能够定义非抽象方法?
能够。其实,抽象类和通常类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。因此抽象类和通常类在定义上,都是须要定义属性和行为的。只不过,比通常类多了一个抽象函数。并且比通常类少了一个建立对象的部分。
3:抽象关键字abstract和哪些不能够共存?final , private , static
4:抽象类中可不能够不定义抽象方法?能够。抽象方法目的仅仅为了避免让该类建立对象。
——————————————————————————————————————————————————————
模板方法设计模式:
解决的问题:当功能内部一部分实现时肯定,一部分实现是不肯定的。这时能够把不肯定的部分暴露出去,让子类去实现。
abstract class GetTime{
public final void getTime(){ //此功能若是不须要复写,可加final限定
long start = System.currentTimeMillis();
code(); //不肯定的功能部分,提取出来,经过抽象方法实现
long end = System.currentTimeMillis();
System.out.println("毫秒是:"+(end—start));
}
public abstract void code(); //抽象不肯定的功能,让子类复写实现
}
class SubDemo extends GetTime{
public void code(){ //子类复写功能方法
for(int y=0; y<1000; y++){
System.out.println("y");
}
}
}
——————————————————————————————————————————————————————
接 口:★★★★★
1:是用关键字interface定义的。
2:接口中包含的成员,最多见的有全局常量、抽象方法。
注意:接口中的成员都有固定的修饰符。
成员变量:public static final
成员方法:public abstract
interface Inter{
public static final int x = 3;
public abstract void show();
}
3:接口中有抽象方法,说明接口不能够实例化。接口的子类必须实现了接口中全部的抽象方法后,该子类才能够实例化。不然,该子类仍是一个抽象类。
4:类与类之间存在着继承关系,类与接口中间存在的是实现关系。
继承用extends ;实现用implements ;
5:接口和类不同的地方,就是,接口能够被多实现,这就是多继承改良后的结果。java将多继承机制经过多现实来体现。
6:一个类在继承另外一个类的同时,还能够实现多个接口。因此接口的出现避免了单继承的局限性。还能够将类进行功能的扩展。
7:其实java中是有多继承的。接口与接口之间存在着继承关系,接口能够多继承接口。
接口都用于设计上,设计上的特色:(能够理解主板上提供的接口)
1:接口是对外提供的规则。
2:接口是功能的扩展。
3:接口的出现下降了耦合性。
抽象类与接口:
抽象类:通常用于描述一个体系单元,将一组共性内容进行抽取,特色:能够在类中定义抽象内容让子类实现,能够定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
接口:通常用于定义对象的扩展功能,是在继承以外还需这个对象具有的一些功能。
抽象类和接口的共性:都是不断向上抽取的结果。
抽象类和接口的区别:
1:抽象类只能被继承,并且只能单继承。
接口须要被实现,并且能够多实现。
2:抽象类中能够定义非抽象方法,子类能够直接继承使用。
接口中都有抽象方法,须要子类去实现。
3:抽象类使用的是 is a 关系。
接口使用的 like a 关系。
4:抽象类的成员修饰符能够自定义。
接口中的成员修饰符是固定的。全都是public的。
在开发以前,先定义规则,A和B分别开发,A负责实现这个规则,B负责使用这个规则。至于A是如何对规则具体实现的,B是不须要知道的。这样这个接口的出现就下降了A和B直接耦合性。
——————————————————————————————————————————————————————
多 态★★★★★(面向对象特征之一):函数自己就具有多态性,某一种事物有不一样的具体的体现。
体现:父类引用或者接口的引用指向了本身的子类对象。//Animal a = new Cat();
多态的好处:提升了程序的扩展性。
多态的弊端:当父类引用指向子类对象时,虽然提升了扩展性,可是只能访问父类中具有的方法,不能够访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性)
多态的前提:
1:必需要有关系,好比继承、或者实现。
2:一般会有覆盖操做。
多态的出现思想上也作着变化:之前是建立对象并指挥对象作事情。有了多态之后,咱们能够找到对象的共性类型,直接操做共性类型作事情便可,这样能够指挥一批对象作事情,即经过操做父类或接口实现。
——————————————————————————————————————————————————————
class 毕姥爷{
void 讲课(){
System.out.println("企业管理");
}
void 钓鱼(){
System.out.println("钓鱼");
}
}
class 毕老师 extends 毕姥爷{
void 讲课(){
System.out.println("JAVA");
}
void 看电影(){
System.out.println("看电影");
}
}
class {
public static void main(String[] args) {
毕姥爷 x = new 毕老师(); //毕老师对象被提高为了毕姥爷类型。
// x.讲课();
// x.看电影(); //错误.
毕老师 y = (毕老师)x; //将毕姥爷类型强制转换成毕老师类型。
y.看电影();//在多态中,自始自终都是子类对象在作着类型的变化。
}
}
——————————————————————————————————————————————————————
若是想用子类对象的特有方法,如何判断对象是哪一个具体的子类类型呢?
能够能够经过一个关键字 instanceof ;//判断对象是否实现了指定的接口或继承了指定的类
格式:<对象 instanceof 类型> ,判断一个对象是否所属于指定的类型。
Student instanceof Person = true;//student继承了person类
多态在子父类中的成员上的体现的特色:
1,成员变量:在多态中,子父类成员变量同名。
在编译时期:参考的是引用型变量所属的类中是否有调用的成员。(编译时不产生对象,只检查语法错误)
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
简单一句话:不管编译和运行,成员变量参考的都是引用变量所属的类中的成员变量。
再说的更容易记忆一些:成员变量 ——— 编译运行都看 = 左边。
2,成员函数。
编译时期:参考引用型变量所属的类中是否有调用的方法。
运行事情:参考的是对象所属的类中是否有调用的方法。
为何是这样的呢?由于在子父类中,对于如出一辙的成员函数,有一个特性:覆盖。
简单一句:成员函数,编译看引用型变量所属的类,运行看对象所属的类。
更简单:成员函数 ——— 编译看 = 左边,运行看 = 右边。
3,静态函数。
编译时期:参考的是引用型变量所属的类中是否有调用的成员。
运行时期:也是参考引用型变量所属的类中是否有调用的成员。
为何是这样的呢?由于静态方法,其实不所属于对象,而是所属于该方法所在的类。
调用静态的方法引用是哪一个类的引用调用的就是哪一个类中的静态方法。
简单说:静态函数 ——— 编译运行都看 = 左边。
——————————————————————————————————————————————————————
——————java.lang.Object
Object:全部类的直接或者间接父类,Java认为全部的对象都具有一些基本的共性内容,这些内容能够不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的就是全部对象都具有的功能。
具体方法:
1,boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。
而根据对象的属性不一样,判断对象是否相同的具体内容也不同。因此在定义类时,通常都会复写equals方法,创建本类特有的判断对象是否相同的依据。
public boolean equals(Object obj){
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.age == p.age;
}
2,String toString():将对象变成字符串;默认返回的格式:类名@哈希值 = getClass().getName() + '@' + Integer.toHexString(hashCode())
为了对象对应的字符串内容有意义,能够经过复写,创建该类对象本身特有的字符串表现形式。
public String toString(){
return "person : "+age;
}
3,Class getClass():获取任意对象运行时的所属字节码文件对象。
4,int hashCode():返回该对象的哈希码值。支持此方法是为了提升哈希表的性能。
一般equals,toString,hashCode,在应用中都会被复写,创建具体对象的特有的内容。
——————————————————————————————————————————————————————
内部类:若是A类须要直接访问B类中的成员,而B类又须要创建A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就能够了。A类就称为内部类。内部类能够直接访问外部类中的成员。而外部类想要访问内部类,必需要创建内部类的对象。
——————————————————————————————————————————————————————
class Outer{
int num = 4;
class Inner {
void show(){
System.out.println("inner show run "+num);
}
}
public void method(){
Inner in = new Inner();//建立内部类的对象。
in.show();//调用内部类的方法。
}
}
——————————————————————————————————————————————————————
当内部类定义在外部类中的成员位置上,可使用一些成员修饰符修饰 private、static。
1:默认修饰符。
直接访问内部类格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer.new Inner();//这种形式不多用。
可是这种应用很少见,由于内部类之因此定义在内部就是为了封装。想要获取内部类对象一般都经过外部类的方法来获取。这样能够对内部类对象进行控制。
2:私有修饰符。
一般内部类被封装,都会被私有化,由于封装性不让其余程序直接访问。
3:静态修饰符。
若是内部类被静态修饰,至关于外部类,会出现访问局限性,只能访问外部类中的静态成员。
注意;若是内部类中定义了静态成员,那么该内部类必须是静态的。
内部类编译后的文件名为:“外部类名$内部类名.java”;
为何内部类能够直接访问外部类中的成员呢?
那是由于内部中都持有一个外部类的引用。这个是引用是 外部类名.this
内部类能够定义在外部类中的成员位置上,也能够定义在外部类中的局部位置上。
当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。
匿名内部类:没有名字的内部类。就是内部类的简化形式。通常只用一次就能够用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:须要前提,内部类必须继承一个类或者实现接口。
匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。
匿名内部类的使用场景:
当函数的参数是接口类型引用时,若是接口中的方法不超过3个。能够经过匿名内部类来完成参数的传递。
其实就是在建立匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个之内。
——————————————————————————————————————————————————————
//面试
//1
new Object(){
void show(){
System.out.println("show run");
}
}.show();
//2
Object obj = new Object(){
void show(){
System.out.println("show run");
}
};
obj.show();
1和2的写法正确吗?有区别吗?说出缘由。
写法是正确,1和2都是在经过匿名内部类创建一个Object类的子类对象。
区别:
第一个但是编译经过,并运行。
第二个编译失败,由于匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提高为了
Object类型,而编译时检查Object类中是否有show方法,因此编译失败。
——————————————————————————————————————————————————————
class InnerClassDemo6 {
+(static)class Inner{
void show(){}
}
public void method(){
this.new Inner().show();//能够
}
public static void main(String[] args) {//static不容许this
This.new Inner().show();//错误,Inner类须要定义成static
}
}
——————————————————————————————————————————————————————
interface Inter{
void show();
}
class Outer{//经过匿名内部类补足Outer类中的代码。
public static Inter method(){
return new Inter(){
public void show(){}
};
}
}
class InnerClassDemo7 {
public static void main(String[] args) {
Outer.method().show();
/*
Outer.method():意思是:Outer中有一个名称为method的方法,并且这个方法是静态的。
Outer.method().show():当Outer类调用静态的method方法运算结束后的结果又调用了show方法,意味着:method()方法运算完一个是对象,并且这个对象是Inter类型的。
*/
function (new Inter(){
public void show(){}
}); //匿名内部类做为方法的参数进行传递。
}
public static void function(Inter in){
in.show();
}
}
——————————————————————————————————————————————————————
异 常:★★★★
异常:就是不正常。程序在运行时出现的不正常状况。其实就是程序中出现的问题。这个问题按照面向对象思想进行描述,并封装成了对象。由于问题的产生有产生的缘由、有问题的名称、有问题的描述等多个属性信息存在。当出现多属性信息最方便的方式就是将这些信息进行封装。异常就是java按照面向对象的思想将问题进行对象封装。这样就方便于操做问题以及处理问题。
出现的问题有不少种,好比角标越界,空指针等都是。就对这些问题进行分类。并且这些问题都有共性内容好比:每个问题都有名称,同时还有问题描述的信息,问题出现的位置,因此能够不断的向上抽取。造成了异常体系。
————————java.lang.Throwable:
Throwable:可抛出的。
|——Error:错误,通常状况下,不编写针对性的代码进行处理,一般是jvm发生的,须要对程序进行修正。
|——Exception:异常,能够有针对性的处理方式
不管是错误仍是异常,它们都有具体的子类体现每个问题,它们的子类都有一个共性,就是都以父类名才做为子类的后缀名。
这个体系中的全部类和对象都具有一个独有的特色;就是可抛性。
可抛性的体现:就是这个体系中的类和对象均可以被throws和throw两个关键字所操做。
——————————————————————————————————————————————————————
class ExceptionDemo{
public static void main(String[] args) {
// byte[] buf = new byte[1024*1024*700];//java.lang.OutOfMemoryError内存溢出错误
}
}
——————————————————————————————————————————————————————
在开发时,若是定义功能时,发现该功能会出现一些问题,应该将问题在定义功能时标示出来,这样调用者就能够在使用这个功能的时候,预先给出处理方式。
如何标示呢?经过throws关键字完成,格式:throws 异常类名,异常类名...
这样标示后,调用者,在使用该功能时,就必需要处理,不然编译失败。
处理方式有两种:一、捕捉;二、抛出。
对于捕捉:java有针对性的语句块进行处理。
try {
须要被检测的代码;
}
catch(异常类 变量名){
异常处理代码;
}
fianlly{
必定会执行的代码;
}
——————————————————————————————————————————————————————
catch (Exception e) { //e用于接收try检测到的异常对象。
System.out.println("message:"+e.getMessage());//获取的是异常的信息。
System.out.println("toString:"+e.toString());//获取的是异常的名字+异常的信息。
e.printStackTrace();//打印异常在堆栈中信息;异常名称+异常信息+异常的位置。
}
——————————————————————————————————————————————————————
异常处理原则:功能抛出几个异常,功能调用若是进行try处理,须要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。
特殊状况:try对应多个catch时,若是有父类的catch语句块,必定要放在下面。
throw 和throws关键字的区别:
throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。
throws用于抛出异常类,后面跟的异常类名,能够跟多个,用逗号隔开。throws用在函数上。
一般状况:函数内容若是有throw,抛出异常对象,并无进行处理,那么函数上必定要声明,不然编译失败。可是也有特殊状况。
异常分两种:
1:编译时被检查的异常,只要是Exception及其子类都是编译时被检测的异常。
2:运行时异常,其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常。
编译时被检查的异常和运行时异常的区别:
编译被检查的异常在函数内被抛出,函数必需要声明,否编译失败。
声明的缘由:是须要调用者对该异常进行处理。
运行时异常若是在函数内被抛出,在函数上不须要声明。
不声明的缘由:不须要调用者处理,运行时异常发生,已经没法再让程序继续运行,因此,不让调用处理的,直接让程序中止,由调用者对代码进行修正。
定义异常处理时,何时定义try,何时定义throws呢?
功能内部若是出现异常,若是内部能够处理,就用try;
若是功能内部处理不了,就必须声明出来,让调用者处理。
自定义异常:当开发时,项目中出现了java中没有定义过的问题时,这时就须要咱们按照java异常创建思想,将项目的中的特有问题也进行对象的封装。这个异常,称为自定义异常。
对于除法运算,0做为除数是不能够的。java中对这种问题用ArithmeticException类进行描述。对于这个功能,在咱们项目中,除数除了不能够为0外,还不能够为负数。但是负数的部分java并无针对描述。因此咱们就须要自定义这个异常。
自定义异常的步骤:
1:定义一个子类继承Exception或RuntimeException,让该类具有可抛性。
2:经过throw 或者throws进行操做。
异常的转换思想:当出现的异常是调用者处理不了的,就须要将此异常转换为一个调用者能够处理的异常抛出。
try catch finally的几种结合方式:
1,
try
catch
finally
2,
try
catch
3,
try
finally
这种状况,若是出现异常,并不处理,可是资源必定关闭,因此try finally集合只为关闭资源。
记住:finally颇有用,主要用户关闭资源。不管是否发生异常,资源都必须进行关闭。
System.exit(0); //退出jvm,只有这种状况finally不执行。
当异常出现后,在子父类进行覆盖时,有了一些新的特色:
1:当子类覆盖父类的方法时,若是父类的方法抛出了异常,那么子类的方法要么不抛出异常要么抛出父类异常或者该异常的子类,不能抛出其余异常。
2:若是父类抛出了多个异常,那么子类在覆盖时只能抛出父类的异常的子集。
注意:
若是父类或者接口中的方法没有抛出过异常,那么子类是不能够抛出异常的,若是子类的覆盖的方法中出现了异常,只能try不能throws。
若是这个异常子类没法处理,已经影响了子类方法的具体运算,这时能够在子类方法中,经过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不须要throws声明的。
常见异常:
一、脚标越界异常(IndexOutOfBoundsException)包括数组、字符串;
空指针异常(NullPointerException)
二、类型转换异常:ClassCastException
三、没有这个元素异常:NullPointerException
四、不支持操做异常;
异常要尽可能避免,若是避免不了,须要预先给出处理方式。好比家庭备药,好比灭火器。
——————————————————————————————————————————————————————
包:定义包用package关键字。
1:对类文件进行分类管理。
2:给类文件提供多层名称空间。
若是生成的包不在当前目录下,须要最好执行classpath,将包所在父目录定义到classpath变量中便可。
通常在定义包名时,由于包的出现是为了区分重名的类。因此包名要尽可能惟一。怎么保证惟一性呢?可使用url域名来进行包名称的定义。
package pack;//定义了一个包,名称为pack。 注意:包名的写法规范:全部字母都小写。
//package cn.itcast.pack.demo;
类的全名称是 包名.类名
编译命令:javac –d 位置(.当前路径) java源文件 (就能够自动生成包)
包是一种封装形式,用于封装类,想要被包之外的程序访问,该类必须public;
类中的成员,若是被包之外访问,也必须public;
包与包之间访问可使用的权限有两种:
1:public
2:protected:只能是不一样包中的子类可使用的权限。
总结java中的四种权限:
范围 public protected default private
同一个类中 ok ok ok ok
同一包中 ok ok ok
子类 ok
不一样包中 ok
——————————————————————————————————————————————————————
Import — 导入:类名称变长,写起来很麻烦。为了简化,使用了一个关键字:import,可使用这个关键字导入指定包中的类。记住:实际开发时,到的哪一个类就导入哪一个类,不建议使用*.
import packa.*;//这个仅仅是导入了packa当前目录下的全部的类。不包含子包。
import packa.abc.*;//导入了packa包中的子包abc下的当前的全部类。
若是导入的两个包中存在着相同名称的类。这时若是用到该类,必须在代码中指定包名。
常见的软件包:
java.lang : language java的核心包,Object System String Throwable jdk1.2版本后,该包中的类自动被导入。
java.awt : 定义的都是用于java图形界面开发的对象。
javax.swing: 提供全部的windows桌面应用程序包括的控件,好比:Frame , Dialog, Table, List 等等,就是java的图形界面库。
java.net : 用于java网络编程方面的对象都在该包中。
java.io : input output 用于操做设备上数据的对象都在该包中。好比:读取硬盘数据,往硬盘写入数据。
java.util : java的工具包,时间对象,集合框架。
java.applet: application+let 客户端java小程序。server+let ——> servlet 服务端java小程序。
jar :java的压缩包,主要用于存储类文件,或者配置文件等。
命令格式:jar –cf 包名.jar 包目录
解压缩:jar –xvf 包名.jar
将jar包目录列表重定向到一个文件中:jar –tf 包名.jar >c:\1.txt
——————————————————————————————————————————————————————
多线程:★★★★
进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。
线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。
一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每一个线程在栈区中都有本身的执行空间,本身的方法区、本身的变量。
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
当产生垃圾时,收垃圾的动做,是不须要主线程来完成,由于这样,会出现主线程中的代码执行会中止,会去运行垃圾回收器代码,效率较低,因此由单独一个线程来负责垃圾回收。
随机性的原理:由于cpu的快速切换形成,哪一个线程获取到了cpu的执行权,哪一个线程就执行。
返回当前线程的名称:Thread.currentThread().getName()
线程的名称是由:Thread—编号定义的。编号从0开始。
线程要运行的代码都统一存放在了run方法中。
线程要运行必需要经过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)
start方法:1)、启动了线程;2)、让jvm调用了run方法。
建立线程的第一种方式:继承Thread ,由子类复写run方法。
步骤:
1,定义类继承Thread类;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,经过建立Thread类的子类对象,建立线程对象;
4,调用线程的start方法,开启线程,并执行run方法。
线程状态:
被建立:start()
运行:具有执行资格,同时具有执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具有cpu的执行资格,没有cpu的执行权;
消亡:stop()
建立线程的第二种方式:实现一个接口Runnable。
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,经过Thread类建立线程对象;
4,将实现了Runnable接口的子类对象做为实际参数传递给Thread类中的构造函数。
为何要传递呢?由于要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
Ticket t = new Ticket();
/*直接建立Ticket对象,并非建立线程对象。
由于建立对象只能经过new Thread类,或者new Thread类的子类才能够。
因此最终想要建立线程。既然没有了Thread类的子类,就只能用Thread类。*/
Thread t1 = new Thread(t); //建立线程。
/*只要将t做为Thread类的构造函数的实际参数传入便可完成线程对象和t之间的关联
为何要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。*/
t1.start();
为何要有Runnable接口的出现?
1:经过继承Thread类的方式,能够完成多线程的创建。可是这种方式有一个局限性,若是一个类已经有了本身的父类,就不能够继承Thread类,由于java单继承的局限性。
但是该类中的还有部分代码须要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
因此,一般建立线程都用第二种方式。
由于实现Runnable接口能够避免单继承的局限性。
2:实际上是将不一样类中须要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其余类进行功能扩展提供了前提。
因此Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口能够避免单继承的局限性。并且,继承Thread,是能够对Thread类中的方法,进行子类复写的。可是不须要作这个复写动做的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。因此Runnable接口将线程要执行的任务封装成了对象。
——————————————————————————————————————————————————————
//面试
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnable run");
}
})
{
public void run(){
System.out.println("subthread run");
}
}.start(); //结果:subthread run
——————————————————————————————————————————————————————
Try {
Thread.sleep(10);
}catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu 切换状况。
多线程安全问题的缘由:
经过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程当中,其余线程参与进来,并操做了这个数据。致使到了错误数据的产生。
涉及到两个因素:
1,多个线程在操做共享数据。
2,有多条语句对共享数据进行运算。
缘由:这多条语句,在某一个时刻被一个线程执行时,尚未执行完,就被其余线程执行了。
解决安全问题的原理:
只要将操做共享数据的语句在某一时段让一个线程执行完,在执行过程当中,其余线程不能进来执行就能够解决这个问题。
如何进行多句操做共享数据代码的封装呢?
java中提供了一个解决方式:就是同步代码块。
格式:
synchronized(对象) { // 任意对象均可以。这个对象就是锁。
须要被同步的代码;
}
——————————————————————————————————————————————————————
同步:★★★★★
好处:解决了线程安全问题。
弊端:相对下降性能,由于判断锁须要消耗资源,产生了死锁。
定义同步是有前提的:
1,必需要有两个或者两个以上的线程,才须要同步。
2,多个线程必须保证使用的是同一个锁。
同步的第二种表现形式:
同步函数:其实就是将同步关键字定义在函数上,让函数具有了同步性。
同步函数是用的哪一个锁呢?
经过验证,函数都有本身所属的对象this,因此同步函数所使用的锁就是this锁。
当同步函数被static修饰时,这时的同步用的是哪一个锁呢?
静态函数在加载时所属于类,这时有可能尚未该类产生的对象,可是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
因此静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class
同步代码块和同步函数的区别?
同步代码块使用的锁能够是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步,可使用同步函数。若是有多同步,必须使用同步代码块,来肯定不一样的锁。因此同步代码块相对灵活一些。
——————————————————————————————————————————————————————
★考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?经过双重判断的形式解决。
//懒汉式:延迟加载方式。
当多线程访问懒汉式时,由于懒汉式的方法内对共性数据进行多条语句的操做。因此容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。可是却带来了效率下降。
为了效率问题,经过双重判断的形式解决。
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ //锁是谁?字节码文件对象;
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
——————————————————————————————————————————————————————
同步死锁:一般只要将同步进行嵌套,就能够看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。
线程间通讯:思路:多个线程在操做同一个资源,可是操做的动做却不同。
1:将资源封装成对象。
2:将线程执行的任务(任务其实就是run方法。)也封装成对象。
等待唤醒机制:涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的全部线程。
注意:
1:这些方法都须要定义在同步中。
2:由于这些方法必需要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就至关于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为何操做线程的方法定义在Object类中?
由于这三个方法都须要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又能够是任意对象,
那么能被任意对象调用的方法必定定义在Object类中。
wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:
wait:能够指定时间也能够不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,并且线程会释放锁。
Sleep:线程会释放执行权,但不是不释放锁。
线程的中止:经过stop方法就能够中止线程。可是这个方式过期了。
中止线程:原理就是:让线程运行的代码结束,也就是结束run方法。
怎么结束run方法?通常run方法里确定定义循环。因此只要结束循环便可。
第一种方式:定义循环的结束标记。
第二种方式:若是线程处于了冻结状态,是不可能读到标记的,这时就须要经过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具有执行资格的状态,让线程能够读到标记,并结束。
—————————< java.lang.Thread >————
interrupt():中断线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其余线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,
Java 虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。
A何时执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
——————————————————————————————————————————————————————
Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具有了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程当中,发现,获取锁,或者释放锁的动做应该是锁这个事物更清楚。因此将这些动做定义在了锁当中,并把锁定义成对象。
因此同步是隐示的锁操做,而Lock对象是显示的锁操做,它的出现就替代了同步。
在以前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是由于同步中的锁是任意对象,因此操做锁的等待唤醒的方法都定义在Object类中。
而如今锁是指定对象Lock。因此查找等待唤醒机制方式须要经过Lock接口来完成。而Lock接口中并无直接操做等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
——————————————————————————————————————————————————————
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}
finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
——count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}
——————————————————————————————————————————————————————
API:(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,而又无需访问源码,或理解内部工做机制的细节。
——< java.lang >—— String字符串:★★★☆
java中用String类进行描述。对字符串进行了对象的封装。这样的好处是能够对字符串这种常见数据进行方便的操做。对象封装后,能够定义N多属性和行为。
如何定义字符串对象呢?String s = "abc";只要是双引号引发的数据都是字符串对象。
特色:字符串一旦被初始化,就不能够被改变,存放在方法区中的常量池中。
——————————————————————————————————————————————————————
String s1 = "abc"; // s1指向的内存中只有一个对象abc。
String s2 = new String("abc"); // s2指向的内容中有两个对象abc、new 。
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//true ,字符串中equals比较的是字符串内容是否相同。
——————————————————————————————————————————————————————
字符串的方法:
1:构造方法:将字节数组或者字符数组转成字符串。
String s1 = new String();//建立了一个空内容的字符串。
String s2 = null;//s2没有任何对象指向,是一个null常量值。
String s3 = "";//s3指向一个具体的字符串对象,只不过这个字符串中没有内容。
//通常在定义字符串时,不用new。
String s4 = new String("abc");
String s5 = "abc"; 通常用此写法
new String(char[]);//将字符数组转成字符串。
new String(char[],offset,count);//将字符数组中的一部分转成字符串。
2:通常方法:
按照面向对象的思想:
2.1 获取:
2.1.1:获取字符串的长度。length();
2.1.2:指定位置的字符。char charAt(int index);
2.1.3:获取指定字符的位置。若是不存在返回—1,因此能够经过返回值—1来判断某一个字符不存在的状况。
int indexOf(int ch);//返回第一次找到的字符角标
int indexOf(int ch,int fromIndex); //返回从指定位置开始第一次找到的角标
int indexOf(String str); //返回第一次找到的字符串角标
int indexOf(String str,int fromIndex);
int lastIndexOf(int ch);
int lastIndexOf(int ch,int fromIndex);
int lastIndexOf(String str);
int lastIndexOf(String str,int fromIndex);
2.1.4:获取子串。
String substring(int start);//从start位开始,到length()—1为止.
String substring(int start,int end);//从start开始到end为止。//包含start位,不包含end位。
substring(0,str.length());//获取整串
2.2 判断:
2.2.1:字符串中包含指定的字符串吗?
boolean contains(String substring);
2.2.2:字符串是否以指定字符串开头啊?
boolean startsWith(string);
2.2.3:字符串是否以指定字符串结尾啊?
boolean endsWith(string);
2.2.4:判断字符串是否相同
boolean equals(string);//覆盖了Object中的方法,判断字符串内容是否相同。
2.2.5:判断字符串内容是否相同,忽略大小写。
boolean equalsIgnoreCase(string) ;
2.3 转换:
2.3.1:经过构造函数能够将字符数组或者字节数组转成字符串。
2.3.2:能够经过字符串中的静态方法,将字符数组转成字符串。
static String copyValueOf(char[] );
static String copyValueOf(char[],int offset,int count);
static String valueOf(char[]);
static String valueOf(char[],int offset,int count);
2.3.3:将基本数据类型或者对象转成字符串。
static String valueOf(char);
static String valueOf(boolean);
static String valueOf(double);
static String valueOf(float);
static String valueOf(int);
static String valueOf(long);
static String valueOf(Object);
2.3.4:将字符串转成大小写。
String toLowerCase();
String toUpperCase();
2.3.5:将字符串转成数组。
char[] toCharArray();//转成字符数组。
byte[] getBytes();//能够加入编码表。转成字节数组。
2.3.6:将字符串转成字符串数组。切割方法。
String[] split(分割的规则—字符串);
2.3.7:将字符串进行内容替换。注意:修改后变成新字符串,并非将原字符串直接修改。
String replace(oldChar,newChar);
String replace(oldstring,newstring);
2.3.8: String concat(string); //对字符串进行追加。
String trim();//去除字符串两端的空格
int compareTo();//若是参数字符串等于此字符串,则返回值 0;若是此字符串按字典顺序小于字符串参数,
则返回一个小于 0 的值;若是此字符串按字典顺序大于字符串参数,则返回一个大于 0 的值。
——————————————————————————————————————————————————————
——< java.lang >—— StringBuffer字符串缓冲区:★★★☆
构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符。
特色:
1:能够对字符串内容进行修改。
2:是一个容器。
3:是可变长度的。
4:缓冲区中能够存储任意类型的数据。
5:最终须要变成字符串。
容器一般具有一些固定的方法:
1,添加。
StringBuffer append(data):在缓冲区中追加数据。追加到尾部。
StringBuffer insert(index,data):在指定位置插入数据。
2,删除。
StringBuffer delete(start,end);删除从start至end—1范围的元素
StringBuffer deleteCharAt(index);删除指定位置的元素
//sb.delete(0,sb.length());//清空缓冲区。
3,修改。
StringBuffer replace(start,end,string);将start至end—1替换成string
void setCharAt(index,char);替换指定位置的字符
void setLength(len);将原字符串置为指定长度的字符串
4,查找。(查不到返回—1)
int indexOf(string); 返回指定子字符串在此字符串中第一次出现处的索引。
int indexOf(string,int fromIndex);从指定位置开始查找字符串
int lastIndexOf(string); 返回指定子字符串在此字符串中最右边出现处的索引。
int lastIndexOf(string,int fromIndex); 从指定的索引开始反向搜索
5,获取子串。
string substring(start); 返回start到结尾的子串
string substring(start,end); 返回start至end—1的子串
6,反转。
StringBuffer reverse();字符串反转
——————————————————————————————————————————————————————
——< java.lang >—— StringBuilder字符串缓冲区:★★★☆
JDK1.5出现StringBuiler;构造一个其中不带字符的字符串生成器,初始容量为 16 个字符。该类被设计用做 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种状况很广泛)。
方法和StringBuffer同样;
StringBuffer 和 StringBuilder 的区别:
StringBuffer线程安全。
StringBuilder线程不安全。
单线程操做,使用StringBuilder 效率高。
多线程操做,使用StringBuffer 安全。
——————————————————————————————————————————————————————
StringBuilder sb = new StringBuilder("abcdefg");
sb.append("ak"); //abcdefgak
sb.insert(1,"et");//aetbcdefg
sb.deleteCharAt(2);//abdefg
sb.delete(2,4);//abefg
sb.setLength(4);//abcd
sb.setCharAt(0,'k');//kbcdefg
sb.replace(0,2,"hhhh");//hhhhcdefg
//想要使用缓冲区,先要创建对象。
StringBuffer sb = new StringBuffer();
sb.append(12).append("haha");//方法调用链。
String s = "abc"+4+'q';
s = new StringBuffer().append("abc").append(4).append('q').toString();
——————————————————————————————————————————————————————
class Test{
public static void main(String[] args) {
String s1 = "java";
String s2 = "hello";
method_1(s1,s2);
System.out.println(s1+"...."+s2); //java....hello
StringBuilder s11 = new StringBuilder("java");
StringBuilder s22 = new StringBuilder("hello");
method_2(s11,s22);
System.out.println(s11+"—————"+s22); //javahello—————hello
}
public static void method_1(String s1,String s2){
s1.replace('a','k');
s1 = s2;
}
public static void method_2(StringBuilder s1,StringBuilder s2){
s1.append(s2);
s1 = s2;
}
}
——————————————————————————————————————————————————————
基本数据类型对象包装类:是按照面向对象思想将基本数据类型封装成了对象。
好处:
1:能够经过对象中的属性和行为操做基本数据。
2:能够实现基本数据类型和字符串之间的转换。
关键字 对应的类名
byte Byte
short Short paserShort(numstring);
int Integer 静态方法:parseInt(numstring)
long Long
float Float
double Double
char Character
Boolean Boolean
基本数据类型对象包装类:都有 XXX parseXXX 方法
只有一个类型没有parse方法:Character ;
——————————————————————————————————————————————————————
Integer对象: ★★★☆
数字格式的字符串转成基本数据类型的方法:
1:将该字符串封装成了Integer对象,并调用对象的方法intValue();
2:使用Integer.parseInt(numstring):不用创建对象,直接类名调用;
将基本类型转成字符串:
1:Integer中的静态方法 String toString(int);
2:int+"";
将一个十进制整数转成其余进制:
转成二进制:toBinaryString
转成八进制:toOctalString
转成十六进制:toHexString
toString(int num,int radix);
将其余进制转换十进制:
parseInt(string,radix); //将给定的数转成指定的基数进制;
在jdk1.5版本后,对基本数据类型对象包装类进行升级。在升级中,使用基本数据类型对象包装类能够像使用基本数据类型同样,进行运算。
Integer i = new Integer(4); //1.5版本以前的写法;
Integer i = 4; //自动装箱,1.5版本后的写法;
i = i + 5;
//i对象是不能直接和5相加的,其实底层先将i转成int类型,在和5相加。而转成int类型的操做是隐式的。自动拆箱:拆箱的原理就是i.intValue();i+5运算完是一个int整数。如何赋值给引用类型i呢?其实有对结果进行装箱。
Integer c = 127;
Integer d = 127;
System.out.println(c = = d); //true
//在装箱时,若是数值在byte范围以内,那么数值相同,不会产生新的对象,也就是说多个数值相同的引用指向的是同一个对象。
——————————————————————————————————————————————————————
集合框架:★★★★★,用于存储数据的容器。
特色:
1:对象封装数据,对象多了也须要存储。集合用于存储对象。
2:对象的个数肯定可使用数组,可是不肯定怎么办?能够用集合。由于集合是可变长度的。
集合和数组的区别:
1:数组是固定长度的;集合可变长度的。
2:数组能够存储基本数据类型,也能够存储引用数据类型;集合只能存储引用数据类型。
3:数组存储的元素必须是同一个数据类型;集合存储的对象能够是不一样数据类型。
数据结构:就是容器中存储数据的方式。
对于集合容器,有不少种。由于每个容器的自身特色不一样,其实原理在于每一个容器的内部数据结构不一样。
集合容器在不断向上抽取过程当中。出现了集合体系。
在使用一个体系时,原则:参阅顶层内容。创建底层对象。
——————————————————————————————————————————————————————
——< java.util >—— Collection接口:
Collection:
|——List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素能够重复。
|——Set:无序(存入和取出顺序有可能不一致),不能够存储重复元素。必须保证元素惟一性。
1,添加:
add(object):添加一个元素
addAll(Collection) :添加一个集合中的全部元素。
2,删除:
clear():将集合中的元素全删除,清空集合。
remove(obj) :删除集合中指定的对象。注意:删除成功,集合的长度会改变。
removeAll(collection) :删除部分元素。部分元素和传入Collection一致。
3,判断:
boolean contains(obj) :集合中是否包含指定元素 。
boolean containsAll(Collection) :集合中是否包含指定的多个元素。
boolean isEmpty():集合中是否有元素。
4,获取:
int size():集合中有几个元素。
5,取交集:
boolean retainAll(Collection) :对当前集合中保留和指定集合中的相同的元素。若是两个集合元素相同,返回flase;若是retainAll修改了当前集合,返回true。
6,获取集合中全部元素:
Iterator iterator():迭代器
7,将集合变成数组:
toArray();
——————————————————————————————————————————————————————
——< java.util >—— Iterator接口:
迭代器:是一个接口。做用:用于取集合中的元素。
boolean |
hasNext() 若是仍有元素能够迭代,则返回 true。 |
next() 返回迭代的下一个元素。 |
|
void |
remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操做)。 |
每个集合都有本身的数据结构,都有特定的取出本身内部元素的方式。为了便于操做全部的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口。
也就说,只要经过该接口就能够取出Collection集合中的元素,至于每个具体的容器依据本身的数据结构,如何实现的具体取出细节,这个不用关心,这样就下降了取出元素和具体集合的耦合性。
Iterator it = coll.iterator();//获取容器中的迭代器对象,至于这个对象是是什么不重要。这对象确定符合一个规则Iterator接口。
——————————————————————————————————————————————————————
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("abc0");
coll.add("abc1");
coll.add("abc2");
//————————方式1——————————
Iterator it = coll.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//—————————方式2用此种——————————
for(Iterator it = coll.iterator();it.hasNext(); ){
System.out.println(it.next());
}
}
——————————————————————————————————————————————————————
——< java.util >—— List接口:
List自己是Collection接口的子接口,具有了Collection的全部方法。如今学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特色。
List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素能够重复。
|——ArrayList:底层的数据结构是数组,线程不一样步,ArrayList替代了Vector,查询元素的速度很是快。
|——LinkedList:底层的数据结构是链表,线程不一样步,增删元素的速度很是快。
|——Vector:底层的数据结构就是数组,线程同步的,Vector不管查询和增删都巨慢。
1,添加:
add(index,element) :在指定的索引位插入元素。
addAll(index,collection) :在指定的索引位插入一堆元素。
2,删除:
remove(index) :删除指定索引位的元素。 返回被删的元素。
3,获取:
Object get(index) :经过索引获取指定元素。
int indexOf(obj) :获取指定元素第一次出现的索引位,若是该元素不存在返回—1;
因此,经过—1,能够判断一个元素是否存在。
int lastIndexOf(Object o) :反向索引指定元素的位置。
List subList(start,end) :获取子列表。
4,修改:
Object set(index,element) :对指定索引位进行元素的修改。
5,获取全部元素:
ListIterator listIterator():list集合特有的迭代器。
List集合支持对元素的增、删、改、查。
List集合由于角标有了本身的获取元素的方式: 遍历。
for(int x=0; x<list.size(); x++){
sop("get:"+list.get(x));
}
在进行list列表元素迭代的时候,若是想要在迭代过程当中,想要对元素进行操做的时候,好比知足条件添加新元素。会发生.ConcurrentModificationException并发修改异常。
致使的缘由是:
集合引用和迭代器引用在同时操做元素,经过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,因此会出现异常状况。
如何解决呢?
既然是在迭代中对元素进行操做,找迭代器的方法最为合适.但是Iterator中只有hasNext,next,remove方法.经过查阅的它的子接口,ListIterator,发现该列表迭代器接口具有了对元素的增、删、改、查的动做。
ListIterator是List集合特有的迭代器。
ListIterator it = list.listIterator;//取代Iterator it = list.iterator;
方法摘要 |
|
void |
|
boolean |
hasNext() 以正向遍历列表时,若是列表迭代器有多个元素,则返回 true(换句话说,若是 next 返回一个元素而不是抛出异常,则返回 true)。 |
boolean |
hasPrevious() 若是以逆向遍历列表,列表迭代器有多个元素,则返回 true。 |
next() 返回列表中的下一个元素。 |
|
int |
nextIndex() 返回对 next 的后续调用所返回元素的索引。 |
previous() 返回列表中的前一个元素。 |
|
int |
previousIndex() 返回对 previous 的后续调用所返回元素的索引。 |
void |
remove() 从列表中移除由 next 或 previous 返回的最后一个元素(可选操做)。 |
void |
可变长度数组的原理:
当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为 10 的空列表。
Vector:是按照原数组的100%延长。
注意:对于list集合,底层判断元素是否相同,其实用的是元素自身的equals方法完成的。因此建议元素都要复写equals方法,创建元素对象本身的比较相同的条件依据。
LinkedList:的特有方法。
addFirst();
addLast();
在jdk1.6之后。
offerFirst();
offerLast();
getFirst():获取链表中的第一个元素。若是链表为空,抛出NoSuchElementException;
getLast();
在jdk1.6之后。
peekFirst();获取链表中的第一个元素。若是链表为空,返回null。
peekLast();
removeFirst():获取链表中的第一个元素,可是会删除链表中的第一个元素。若是链表为空,抛出NoSuchElementException
removeLast();
在jdk1.6之后。
pollFirst();获取链表中的第一个元素,可是会删除链表中的第一个元素。若是链表为空,返回null。
pollLast();
——————————————————————————————————————————————————————
——< java.util >—— Set接口:
Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。
|——HashSet:底层数据结构是哈希表,线程是不一样步的。无序,高效;
HashSet集合保证元素惟一性:经过元素的hashCode方法,和equals方法完成的。
当元素的hashCode值相同时,才继续判断元素的equals是否为true。
若是为true,那么视为相同元素,不存。若是为false,那么存储。
若是hashCode值不一样,那么不判断equals,从而提升对象比较的速度。
|——LinkedHashSet:有序,hashset的子类。
|——TreeSet:对Set集合中的元素的进行指定顺序的排序。不一样步。TreeSet底层的数据结构就是二叉树。
哈希表的原理:
1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。
2,哈希值就是这个元素的位置。
3,若是哈希值出现冲突,再次判断这个关键字对应的对象是否相同。若是对象相同,就不存储,由于元素重复。若是对象不一样,就存储,在原来对象的哈希值基础 +1顺延。
4,存储哈希值的结构,咱们称为哈希表。
5,既然哈希表是根据哈希值存储的,为了提升效率,最好保证对象的关键字是惟一的。
这样能够尽可能少的判断关键字对应的对象是否相同,提升了哈希表的操做效率。
对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。
TreeSet:
用于对Set集合进行元素的指定顺序排序,排序须要依据元素自身具有的比较性。
若是元素不具有比较性,在运行时会发生ClassCastException异常。
因此须要元素实现Comparable接口,强制让元素具有比较性,复写compareTo方法。
依据compareTo方法的返回值,肯定元素在TreeSet数据结构中的位置。
TreeSet方法保证元素惟一性的方式:就是参考比较方法的结果是否为0,若是return 0,视为两个对象重复,不存。
注意:在进行比较时,若是判断元素不惟一,好比,同姓名,同年龄,才视为同一我的。
在判断时,须要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。
TreeSet集合排序有两种方式,Comparable和Comparator区别:
1:让元素自身具有比较性,须要元素对象实现Comparable接口,覆盖compareTo方法。
2:让集合自身具有比较性,须要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象做为实际参数传递给TreeSet集合的构造函数。
第二种方式较为灵活。
——————————————————————————————————————————————————————
Map集合:
|——Hashtable:底层是哈希表数据结构,是线程同步的。不能够存储null键,null值。
|——HashMap:底层是哈希表数据结构,是线程不一样步的。能够存储null键,null值。替代了Hashtable.
|——TreeMap:底层是二叉树结构,能够对map集合中的键进行指定顺序的排序。
Map集合存储和Collection有着很大不一样:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。
特色:要保证map集合中键的惟一性。
1,添加。
put(key,value):当存储的键相同时,新的值会替换老的值,并将老值返回。若是键没有重复,返回null。
void putAll(Map);
2,删除。
void clear():清空
value remove(key) :删除指定键。
3,判断。
boolean isEmpty():
boolean containsKey(key):是否包含key
boolean containsValue(value) :是否包含value
4,取出。
int size():返回长度
value get(key) :经过指定键获取对应的值。若是返回null,能够判断该键不存在。固然有特殊状况,就是在hashmap集合中,是能够存储null键null值的。
Collection values():获取map集合中的全部的值。
5,想要获取map中的全部元素:
原理:map中是没有迭代器的,collection具有迭代器,只要将map集合转成Set集合,可使用迭代器了。之因此转成set,是由于map集合具有着键的惟一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。
★ 把map集合转成set的方法:
Set keySet();
Set entrySet();//取的是键和值的映射关系。
Entry就是Map接口中的内部接口;
为何要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。
——————————————————————————————————————————————————————
取出map集合中全部元素的方式一:keySet()方法。
能够将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再经过get方法对获取到的键进行值的获取。
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()) {
Object key = it.next();
Object value = map.get(key);
System.out.println(key+":"+value);
}
——————————————————————————————————————————————————————
取出map集合中全部元素的方式二:entrySet()方法。
Set entrySet = map.entrySet();
Iterator it = entrySet.iterator();
while(it.hasNext()) {
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+"::::"+me.getValue());
}
——————————————————————————————————————————————————————
使用集合的技巧:
看到Array就是数组结构,有角标,查询速度很快。
看到link就是链表结构:增删速度快,并且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();
看到hash就是哈希表,就要想要哈希值,就要想到惟一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。
看到tree就是二叉树,就要想到排序,就想要用到比较。
比较的两种方式:
一个是Comparable:覆盖compareTo方法;
一个是Comparator:覆盖compare方法。
LinkedHashSet,LinkedHashMap:这两个集合能够保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。
集合何时用?
当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。
保证惟一,就用Set。不保证惟一,就用List。
——————————————————————————————————————————————————————
Collections:它的出现给集合操做提供了更多的功能。这个类不须要建立对象,内部提供的都是静态方法。
静态方法:
Collections.sort(list);//list集合进行元素的天然顺序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。
class ComparatorByLen implements Comparator<String>{
public int compare(String s1,String s2){
int temp = s1.length()—s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
Collections.max(list); //返回list中字典顺序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。
Collections.reverseOrder();//逆向反转排序。
Collections.shuffle(list);//随机对list中的元素进行位置的置换。
将非同步集合转成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定义一个类,将集合全部的方法加同一把锁后返回。
Collection 和 Collections的区别:
Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操做。
Collection是个java.util下的接口,它是各类集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操做,如插入、删除、判断一个元素是否其成员、遍历等。
——————————————————————————————————————————————————————
Arrays:
用于操做数组对象的工具类,里面都是静态方法。
asList方法:将数组转换成list集合。
String[] arr = {"abc","kk","qq"};
List<String> list = Arrays.asList(arr);//将arr数组转成list集合。
将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合;
能够经过list集合中的方法来操做数组中的元素:isEmpty()、contains、indexOf、set;
注意(局限性):数组是固定长度,不可使用集合对象增长或者删除等,会改变数组长度的功能方法。好比add、remove、clear。(会报不支持操做异常UnsupportedOperationException);
若是数组中存储的引用数据类型,直接做为集合的元素能够直接用集合方法操做。
若是数组中存储的是基本数据类型,asList会将数组实体做为集合元素存在。
集合变数组:用的是Collection接口中的方法:toArray();
若是给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再建立一个该类型的数据,长度为集合的size。
若是传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会建立新数组,直接使用该数组便可,并将集合中的元素存储到数组中,其余为存储元素的位置默认值null。
因此,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。
将集合变成数组后有什么好处?限定了对集合中的元素进行增删操做,只要获取这些元素便可。
——————————————————————————————————————————————————————
Jdk5.0新特性:
Collection在jdk1.5之后,有了一个父接口Iterable,这个接口的出现的将iterator方法进行抽取,提升了扩展性。
——————————————————————————————————————————————————————
加强for循环:foreach语句,foreach简化了迭代器。
格式:// 加强for循环括号里写两个参数,第一个是声明一个变量,第二个就是须要迭代的容器
for( 元素类型 变量名 : Collection集合 & 数组 ) {
…
}
高级for循环和传统for循环的区别:
高级for循环在使用时,必需要明确被遍历的目标。这个目标,能够是Collection集合或者数组,若是遍历Collection集合,在遍历过程当中还须要对元素进行操做,好比删除,须要使用迭代器。
若是遍历数组,还须要对数组元素进行操做,建议用传统for循环由于能够定义角标经过角标操做元素。若是只为遍历获取,能够简化成高级for循环,它的出现为了简化书写。
高级for循环能够遍历map集合吗?不能够。可是能够将map转成set后再使用foreach语句。
1)、做用:对存储对象的容器进行迭代: 数组 collection map
2)、加强for循环迭代数组:
String [] arr = {"a", "b", "c"};//数组的静态定义方式,只试用于数组首次定义的时候
for(String s : arr) {
System.out.println(s);
}
3)、单列集合 Collection:
List list = new ArrayList();
list.add("aaa");
// 加强for循环, 没有使用泛型的集合能不能使用加强for循环迭代?能
for(Object obj : list) {
String s = (String) obj;
System.out.println(s);
}
4)、双列集合 Map:
Map map = new HashMap();
map.put("a", "aaa");
// 传统方式:必须掌握这种方式
Set entrys = map.entrySet(); // 1.得到全部的键值对Entry对象
iter = entrys.iterator(); // 2.迭代出全部的entry
while(iter.hasNext()) {
Map.Entry entry = (Entry) iter.next();
String key = (String) entry.getKey(); // 分别得到key和value
String value = (String) entry.getValue();
System.out.println(key + "=" + value);
}
// 加强for循环迭代:原则上map集合是没法使用加强for循环来迭代的,由于加强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证必定有iterator方法,java有这样的限定是由于加强for循环内部仍是用迭代器实现的,而实际上,咱们能够经过某种方式来使用加强for循环。
for(Object obj : map.entrySet()) {
Map.Entry entry = (Entry) obj; // obj 依次表示Entry
System.out.println(entry.getKey() + "=" + entry.getValue());
}
5)、集合迭代注意问题:在迭代集合的过程当中,不能对集合进行增删操做(会报并发访问异常);能够用迭代器的方法进行操做(子类listIterator:有增删的方法)。
6)、加强for循环注意问题:在使用加强for循环时,不能对元素进行赋值;
int[] arr = {1,2,3};
for(int num : arr) {
num = 0; //不能改变数组的值
}
System.out.println(arr[1]); //2
——————————————————————————————————————————————————————
可变参数(...):用到函数的参数上,当要操做的同一个类型元素个数不肯定的时候,但是用这个方式,这个参数能够接受任意个数的同一类型的数据。
和之前接收数组不同的是:
之前定义数组类型,须要先建立一个数组对象,再将这个数组对象做为参数传递给函数。如今,直接将数组中的元素做为参数传递便可。底层实际上是将这些元素进行数组的封装,而这个封装动做,是在底层完成的,被隐藏了。因此简化了用户的书写,少了调用者定义数组的动做。
若是在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,不然编译会失败。)。
若是要获取多个int数的和呢?可使用将多个int数封装到数组中,直接对数组求和便可。
——————————————————————————————————————————————————————
静态导入:导入了类中的全部静态成员,简化静态成员的书写。
import static java.util.Collections.*; //导入了Collections类中的全部静态成员
——————————————————————————————————————————————————————
枚举:关键字 enum
问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个;
解决办法:
1)、在setGrade方法中作判断,不符合格式要求就抛出异常;
2)、直接限定用户的选择,经过自定义类模拟枚举的方式来限定用户的输入,写一个Grade类,私有构造函数,对外提供5个静态的常量表示类的实例;
3)、jdk5中新定义了枚举类型,专门用于解决此类问题;
4)、枚举就是一个特殊的java类,能够定义属性、方法、构造函数、实现接口、继承类;
——————————————————————————————————————————————————————
自动拆装箱:java中数据类型分为两种 : 基本数据类型 引用数据类型(对象)
在 java程序中全部的数据都须要当作对象来处理,针对8种基本数据类型提供了包装类,以下:
int ——> Integer
byte ——> Byte
short ——> Short
long ——> Long
char ——> Character
double ——> Double
float ——> Float
boolean ——> Boolean
jdk5之前基本数据类型和包装类之间须要互转:
基本———引用 Integer x = new Integer(x);
引用———基本 int num = x.intValue();
1)、Integer x = 1; x = x + 1; 经历了什么过程?装箱 ——> 拆箱——> 装箱;
2)、为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小 —128~127 一个字节的大小;
3)、String池:Java为了优化字符串操做 提供了一个缓冲池;
——————————————————————————————————————————————————————
泛型:jdk1.5版本之后出现的一个安全机制。表现格式:< >
好处:
1:将运行时期的问题ClassCastException问题转换成了编译失败,体如今编译时期,程序员就能够解决问题。
2:避免了强制转换的麻烦。
只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。
泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。
为何? 由于泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。
在运行时,如何知道获取的元素类型而不用强转呢?
泛型的补偿:由于存储的时候,类型已经肯定了是同一个类型的元素,因此在运行时,只要获取到该元素的类型,在内部进行一次转换便可,因此使用者不用再作转换动做了。
何时用泛型类呢?
当类中的操做的引用数据类型不肯定的时候,之前用的Object来进行扩展的,如今能够用泛型来表示。这样能够避免强转的麻烦,并且将运行问题转移到的编译时期。
——————————————————————————————————————————————————————
泛型在程序定义上的体现:
//泛型类:将泛型定义在类上。
class Tool<Q> {
private Q obj;
public void setObject(Q obj) {
this.obj = obj;
}
public Q getObject() {
return obj;
}
}
//当方法操做的引用数据类型不肯定的时候,能够将泛型定义在方法上。
public <W> void method(W w) {
System.out.println("method:"+w);
}
//静态方法上的泛型:静态方法没法访问类上定义的泛型。若是静态方法操做的引用数据类型不肯定的时候,必需要将泛型定义在方法上。
public static <Q> void function(Q t) {
System.out.println("function:"+t);
}
//泛型接口.
interface Inter<T> {
void show(T t);
}
class InterImpl<R> implements Inter<R> {
public void show(R r) {
System.out.println("show:"+r);
}
}
——————————————————————————————————————————————————————
泛型中的通配符:能够解决当具体类型不肯定的时候,这个通配符就是 ? ;当操做类型时,不须要使用类型的具体功能时,只使用Object类中的功能。那么能够用 ? 通配符来表未知类型。
泛型限定:
上限:?extends E:能够接收E类型或者E的子类型对象。
下限:?super E:能够接收E类型或者E的父类型对象。
上限何时用:往集合中添加元素时,既能够添加E类型对象,又能够添加E的子类型对象。为何?由于取的时候,E类型既能够接收E类对象,又能够接收E的子类型对象。
下限何时用:当从集合中获取元素进行操做的时候,能够用当前元素的类型接收,也能够用当前元素的父类型接收。
泛型的细节:
1)、泛型到底表明什么类型取决于调用者传入的类型,若是没传,默认是Object类型;
2)、使用带泛型的类建立对象时,等式两边指定的泛型必须一致;
缘由:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;
3)、等式两边能够在任意一边使用泛型,在另外一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错
//要保证左右两边的泛型具体类型一致就能够了,这样不容易出错。
ArrayList<? extends Object> al = new ArrayList<String>();
al.add("aa"); //错
//由于集合具体对象中既可存储String,也能够存储Object的其余子类,因此添加具体的类型对象不合适,类型检查会出现安全问题。 ?extends Object 表明Object的子类型不肯定,怎么能添加具体类型的对象呢?
public static void method(ArrayList<? extends Object> al) {
al.add("abc"); //错
//只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,由于子类型不肯定。
}
——————————————————————————————————————————————————————
API——— java.lang.System: 属性和行为都是静态的。
long currentTimeMillis(); // 返回当前时间毫秒值
exit(); // 退出虚拟机
Properties getProperties() ; // 获取当前系统的属性信息
Properties prop = System.getProperties(); //获取系统的属性信息,并将这些信息存储到Properties集合中。
System.setProperty("myname","毕老师"); //给系统属性信息集添加具体的属性信息
//临时设置方式:运行jvm时,能够经过jvm的参数进行系统属性的临时设置,能够在java命令的后面加入 –D<name>=<value> 用法:java –Dmyname=小明 类名。
String name = System.getProperty("os.name");//获取指定属性的信息
//想要知道该系统是不是该软件所支持的系统中的一个。
Set<String> hs = new HashSet<String>();
hs.add("Windows XP");
hs.add("Windows 7");
if(hs.contains(name))
System.out.println("能够支持");
else
System.out.println("不支持");
——————————————————————————————————————————————————————
API——— java.lang.Runtime: 类中没有构造方法,不能建立对象。
可是有非静态方法。说明该类中应该定义好了对象,并能够经过一个static方法获取这个对象。用这个对象来调用非静态方法。这个方法就是 static Runtime getRuntime();
这个Runtime其实使用单例设计模式进行设计。
class RuntimeDemo {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
Process p = r.exec("notepad.exe SystemDemo.java"); //运行指定的程序
Thread.sleep(4000);
p.destroy(); //杀掉进程
}
}
——————————————————————————————————————————————————————
API——— java.util.Math: 用于数学运算的工具类,属性和行为都是静态的。该类是final不容许继承。
static double ceil(double a) ; //返回大于指定数值的最小整数
static double floor(double a) ; //返回小于指定数值的最大整数
static long round(double a) ; //四舍五入成整数
static double pow(double a, double b) ; //a的b次幂
static double random(); //返回0~1的伪随机数
public static void main(String[] args) {
Random r = new Random();
for(int x=0; x<10; x++) {
//double d = Math.floor(Math.random()*10+1);
//int d = (int)(Math.random()*10+1);
int d = r.nextInt(10)+1;
System.out.println(d);
}
}
——————————————————————————————————————————————————————
API——— java.util.Date:日期类,月份从0—11;
/*
日期对象和毫秒值之间的转换。
1,日期对象转成毫秒值。Date类中的getTime方法。
2,如何将获取到的毫秒值转成具体的日期呢?
Date类中的setTime方法。也能够经过构造函数。
*/
//日期对象转成毫秒值
Date d = new Date();
long time1 = d.getTime();
long time2 = System.currentTimeMillis(); / /毫秒值。
//毫秒值转成具体的日期
long time = 1322709921312l;
Date d = new Date();
d.setTime(time);
/*
将日期字符串转换成日期对象:使用的就是DateFormat方法中的 Date parse(String source) ;
*/
public static void method() throws Exception {
String str_time = "2011/10/25";
DateFormat df = new SimpleDateFormat("yyyy/MM/dd"); //SimpleDateFormat做为能够指定用户自定义的格式来完成格式化。
Date d = df.parse(str_time);
}
/*
若是不须要使用特定的格式化风格,彻底可使用DateFormat类中的静态工厂方法获取具体的已经封装好风格的对象。getDateInstance();getDateTimeInstance();
*/
Date d = new Date();
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
df = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
String str_time = df.format(d);
//将日期对象转换成字符串的方式:DateFormat类中的format方法。
//建立日期格式对象。
DateFormat df = new SimpleDateFormat(); //该对象的创建内部会封装一个默认的日期格式。11—12—1 下午1:48
//若是想要自定义日期格式的话。可以使用SimpleDateFormat的构造函数。将具体的格式做为参数传入到构造函数中。如何表示日期中年的部分呢?能够必需要参与格式对象文档。
df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
//调用DateFormat中的format方法。对已有的日期对象进行格式化。
String str_time = df.format(d);
——————————————————————————————————————————————————————
API——— java.util. Calendar:日历类
public static void method(){
Calendar c = Calendar.getInstance();
System.out.println(c.get(Calendar.YEAR)+"年"+(c.get(Calendar.MONTH)+1)+"月"
+getNum(c.get(Calendar.DAY_OF_MONTH))+"日"
+"星期"+getWeek(c.get(Calendar.DAY_OF_WEEK)));
}
public static String getNum(int num){
return num>9 ? num+"" : "0"+num;
}
public static String getWeek(int index){
/*
查表法:创建数据的对应关系.
最好:数据个数是肯定的,并且有对应关系。若是对应关系的一方,是数字,并且能够做为角标,那么能够经过数组来做为表。
*/
String[] weeks = {"","日","一","二","三","四","五","六"};
return weeks[index];
}
——————————————————————————————————————————————————————
IO流:★★★★★,用于处理设备上数据。
流:能够理解数据的流动,就是一个数据流。IO流最终要以对象来体现,对象都存在IO包中。
流也进行分类:
1:输入流(读)和输出流(写)。
2:由于处理的数据不一样,分为字节流和字符流。
字节流:处理字节数据的流对象。设备上的数据不管是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,因此计算机中的最小数据单元就是字节。意味着,字节流能够处理设备上的全部数据,因此字节流同样能够处理字符数据。
那么为何要有字符流呢?由于字符每一个国家都不同,因此涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,因此须要获取中文字节数据的同时+ 指定的编码表才能够解析正确数据。为了方便于文字的解析,因此将字节流和编码表封装成对象,这个对象就是字符流。只要操做字符数据,优先考虑使用字符流体系。
注意:流的操做只有两种:读和写。
流的体系由于功能不一样,可是有共性内容,不断抽取,造成继承体系。该体系一共有四个基类,并且都是抽象类。
字节流:InputStream OutputStream
字符流:Reader Writer
在这四个系统中,它们的子类,都有一个共性特色:子类名后缀都是父类名,前缀名都是这个子类的功能名称。
——————————————————————————————————————————————————————
public static void main(String[] args) throws IOException { //读、写都会发生IO异常
/*
1:建立一个字符输出流对象,用于操做文件。该对象一创建,就必须明确数据存储位置,是一个文件。
2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置建立了一个存储数据的文件。
3:若是指定位置,出现了同名文件,文件会被覆盖。
*/
FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException
/*
调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(实际上是写入到内存缓冲区中)。怎么把数据弄到文件中?
*/
fw.write("abcde");
fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。
fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。
}
close()和flush()的区别:
flush():将缓冲区的数据刷到目的地中后,流可使用。
close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动做必定作。
——————————————————————————————————————————————————————
io异常的处理方式:io必定要写finally;
FileWriter写入数据的细节:
1:window中的换行符:\r\n两个符号组成。 linux:\n。
2:续写数据,只要在构造函数中传入新的参数true。
3:目录分割符:window \\ /
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt",true);
fw.write("abcde");
}
catch (IOException e ){
System.out.println(e.toString()+"....");
}
finally{
if(fw!=null)
try{
fw.close();
}
catch (IOException e){
System.out.println("close:"+e.toString());
}
}
}
——————————————————————————————————————————————————————
FileReader:使用Reader体系,读取一个文本文件中的数据。返回 —1 ,标志读到结尾。
import java.io.*;
class FileReaderDemo {
public static void main(String[] args) throws IOException {
/*
建立能够读取文本文件的流对象,FileReader让建立好的流对象和指定的文件相关联。
*/
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch = fr.read())!= —1) { //条件是没有读到结尾
System.out.println((char)ch); //调用读取流的read方法,读取一个字符。
}
fr.close();
}
}
——————————————————————————————————————————————————————
读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。
import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt"); //建立读取流对象和指定文件关联。
//由于要使用read(char[])方法,将读取到字符存入数组。因此要建立一个字符数组,通常数组的长度都是1024的整数倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != —1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
}
——————————————————————————————————————————————————————
IO中的使用到了一个设计模式:装饰设计模式。
装饰设计模式解决:对一组类进行功能的加强。
包装:写一个类(包装类)对被包装对象进行包装;
* 1、包装类和被包装对象要实现一样的接口;
* 2、包装类要持有一个被包装对象;
* 3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于须要修改的方法咱们本身实现;
——————————————————————————————————————————————————————
字符流:
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
|———BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 能够指定缓冲区的大小,或者可以使用默认的大小。大多数状况下,默认值就足够大了。
|———LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
|———InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集能够由名称指定或显式给定,或者能够接受平台默认的字符集。
|———FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要本身指定这些值,能够先在 FileInputStream 上构造一个 InputStreamReader。
|———CharArrayReader:
|———StringReader:
——————————————————————————————————————————————————————
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
|———BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|———OutputStreamWriter:是字符流通向字节流的桥梁:可以使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集能够由名称指定或显式给定,不然将接受平台默认的字符集。
|———FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要本身指定这些值,能够先在 FileOutputStream 上构造一个 OutputStreamWriter。
|———PrintWriter:
|———CharArrayWriter:
|———StringWriter:
——————————————————————————————————————————————————————
字节流:
InputStream:是表示字节输入流的全部类的超类。
|——— FileInputStream:从文件系统中的某个文件中得到输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|——— FilterInputStream:包含其余一些输入流,它将这些流用做其基本数据源,它能够直接传输数据或提供一些额外的功能。
|——— BufferedInputStream:该类实现缓冲的输入流。
|——— Stream:
|——— ObjectInputStream:
|——— PipedInputStream:
——————————————————————————————————————————————————————
OutputStream:此抽象类是表示输出字节流的全部类的超类。
|——— FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|——— FilterOutputStream:此类是过滤输出流的全部类的超类。
|——— BufferedOutputStream:该类实现缓冲的输出流。
|——— PrintStream:
|——— DataOutputStream:
|——— ObjectOutputStream:
|——— PipedOutputStream:
——————————————————————————————————————————————————————
缓冲区是提升效率用的,给谁提升呢?
BufferedWriter:是给字符输出流提升效率用的,那就意味着,缓冲区对象创建时,必需要先有流对象。明确要提升具体的流对象的效率。
FileWriter fw = new FileWriter("bufdemo.txt");
BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。
for(int x=0; x<4; x++){
bufw.write(x+"abc");
bufw.newLine(); //写入一个换行符,这个换行符能够依据平台的不一样写入不一样的换行符。
bufw.flush();//对缓冲区进行刷新,可让数据到目的地中。
}
bufw.close();//关闭缓冲区,其实就是在关闭具体的流。
——————————————————————————————————————————————————————
BufferedReader:
FileReader fr = new FileReader("bufdemo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null){ //readLine方法返回的时候是不带换行符的。
System.out.println(line);
}
bufr.close();
——————————————————————————————————————————————————————
//记住,只要一读取键盘录入,就用这句话。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//输出到控制台
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());//将输入的字符转成大写字符输出
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();
——————————————————————————————————————————————————————
流对象:其实很简单,就是读取和写入。可是由于功能的不一样,流的体系中提供N多的对象。那么开始时,到底该用哪一个对象更为合适呢?这就须要明确流的操做规律。
流的操做规律:
1,明确源和目的。
数据源:就是须要读取,可使用两个体系:InputStream、Reader;
数据汇:就是须要写入,可使用两个体系:OutputStream、Writer;
2,操做的数据是不是纯文本数据?
若是是:数据源:Reader
数据汇:Writer
若是不是:数据源:InputStream
数据汇:OutputStream
3,虽然肯定了一个体系,可是该体系中有太多的对象,到底用哪一个呢?
明确操做的数据设备。
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。
4,须要在基本操做上附加其余功能吗?好比缓冲。
若是须要就进行装饰。
转换流特有功能:转换流能够将字节转成字符,缘由在于,将获取到的字节经过查编码表获取到指定对应字符。
转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。
发现转换流有一个子类就是操做文件的字符流对象:
InputStreamReader
|——FileReader
OutputStreamWriter
|——FileWrier
想要操做文本文件,必需要进行编码转换,而编码转换动做转换流都完成了。因此操做文件的流对象只要继承自转换流就能够读取一个字符了。
可是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
以上两句代码功能一致,
若是仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //由于简化。
若是须要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。
凡是操做设备上的文本数据,涉及编码转换,必须使用转换流。
——————————————————————————————————————————————————————
File类:将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为能够对这些文件和文件夹进行操做。这些是流对象办不到的,由于流只操做数据。
File类常见方法:
1:建立。
boolean createNewFile():在指定目录下建立文件,若是该文件已存在,则不建立。而对操做文件的输出流而言,输出流对象已创建,就会建立文件,若是文件已存在,会覆盖。除非续写。
boolean mkdir():建立此抽象路径名指定的目录。
boolean mkdirs():建立多级目录。
2:删除。
boolean delete():删除此抽象路径名表示的文件或目录。
void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才能够将该文件夹用delete删除。
window的删除动做,是从里往外删。注意:java删除文件不走回收站。要慎用。
3:获取.
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():返回此抽象路径名父目录的抽象路径名,若是此路径名没有指定父目录,则返回 null。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符,windows默认为 “;”。
File.Separator:返回当前系统默认的目录分隔符,windows默认为 “\”。
4:判断:
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():测试此抽象路径名表示的文件是不是一个目录。
boolean isFile():测试此抽象路径名表示的文件是不是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是不是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
5:重命名。
boolean renameTo(File dest):能够实现移动的效果。剪切+重命名。
String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。
若是调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。若是封装的对象不存在也会返回null。只有封装的对象存在而且是文件夹时,这个方法才有效。
——————————————————————————————————————————————————————
递归:就是函数自身调用自身。
何时用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不肯定,都由上次的功能元素结果来肯定。
简单说:功能内部又用到该功能,可是传递的参数值不肯定。(每次功能参与运算的未知内容不肯定)。
递归的注意事项:
1:必定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
其实递归就是在栈内存中不断的加载同一个函数。
——————————————————————————————————————————————————————
Java.util.Properties:一个能够将键值进行持久化存储的对象。Map——Hashtable的子类。
Map
|——Hashtable
|——Properties:用于属性配置文件,键和值都是字符串类型。
特色:1:能够持久化存储数据。2:键值都是字符串。3:通常用于配置文件。
|—— load():将流中的数据加载进集合。
原理:其实就是将读取流和指定文件相关联,并读取一行数据,由于数据是规则的key=value,因此获取一行后,经过 = 对该行数据进行切割,左边就是键,右边就是值,将键、值存储到properties集合中。
|—— store():写入各个项后,刷新输出流。
|—— list():将集合的键值数据列出到指定的目的地。
——————————————————————————————————————————————————————
如下介绍IO包中扩展功能的流对象:基本都是装饰设计模式。
Java.io.outputstream.PrintStream:打印流
1:提供了更多的功能,好比打印方法。能够直接打印任意类型的数据。
2:它有一个自动刷新机制,建立该对象,指定参数,对于指定方法能够自动刷新。
3:它使用的本机默认的字符编码.
4:该流的print方法不抛出IOException。
该对象的构造函数。
PrintStream(File file) :建立具备指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn) :建立具备指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out) :建立新的打印流。
PrintStream(OutputStream out, boolean autoFlush) :建立新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding) :建立新的打印流。
PrintStream(String fileName) :建立具备指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn)
PrintStream能够操做目的:1:File对象。2:字符串路径。3:字节输出流。
前两个都JDK1.5版本才出现。并且在操做文本文件时,可指定字符编码了。
当目的是一个字节输出流时,若是使用的println方法,能够在printStream对象上加入一个true参数。这样对于println方法能够进行自动的刷新,而不是等待缓冲区满了再刷新。最终print方法都将具体的数据转成字符串,并且都对IO异常进行了内部处理。
既然操做的数据都转成了字符串,那么使用PrintWriter更好一些。由于PrintWrite是字符流的子类,能够直接操做字符数据,同时也能够指定具体的编码。
——————————————————————————————————————————————————————
PrintWriter:具有了PrintStream的特色同时,还有自身特色:
该对象的目的地有四个:1:File对象。2:字符串路径。3:字节输出流。4:字符输出流。
开发时尽可能使用PrintWriter。
方法中直接操做文件的第二参数是编码表。
直接操做输出流的,第二参数是自动刷新。
//读取键盘录入将数据转成大写显示在控制台.
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:键盘输入
//目的:把数据写到文件中,还想自动刷新。
PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//设置true后自动刷新
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());//转大写输出
}
//注意:System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。随时可使用。当jvm结束了,这两个流就结束了。可是,当使用了显示的close方法关闭时,这两个流在提早结束了。
out.close();
bufr.close();
——————————————————————————————————————————————————————
SequenceInputStream:序列流,做用就是将多个读取流合并成一个读取流。实现数据合并。
表示其余输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
这样作,能够更方便的操做多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
该对象的构造函数参数是枚举,想要获取枚举,须要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有本身去建立枚举对象。
可是方法怎么实现呢?由于枚举操做的是具体集合中的元素,因此没法具体实现,可是枚举和迭代器是功能同样的,因此,能够用迭代替代枚举。
合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。
import java.io.*;
import java.util.*;
class SplitFileDemo{
private static final String CFG = ".properties";
private static final String SP = ".part";
public static void main(String[] args) throws IOException{
File file = new File("c:\\0.bmp");
File dir = new File("c:\\partfiles");
meger(dir);
}
//数据的合并。
public static void meger(File dir)throws IOException{
if(!(dir.exists() && dir.isDirectory()))
throw new RuntimeException("指定的目录不存在,或者不是正确的目录");
File[] files = dir.listFiles(new SuffixFilter(CFG));
if(files.length==0)
throw new RuntimeException("扩展名.proerpties的文件不存在");
//获取到配置文件
File config = files[0];
//获取配置文件的信息。
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(config);
prop.load(fis);
String fileName = prop.getProperty("filename");
int partcount = Integer.parseInt(prop.getProperty("partcount"));
//——————————————
File[] partFiles = dir.listFiles(new SuffixFilter(SP));
if(partFiles.length!=partcount)
throw new RuntimeException("缺乏碎片文件");
//—————————
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for(int x=0; x<partcount; x++){
al.add(new FileInputStream(new File(dir,x+SP)));
}
Enumeration<FileInputStream> en = Collections.enumeration(al);
SequenceInputStream sis = new SequenceInputStream(en);
File file = new File(dir,fileName);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=—1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
//带有配置信息的数据切割。
public static void splitFile(File file)throws IOException{
//用一个读取流和文件关联。
FileInputStream fis = new FileInputStream(file);
//建立目的地。由于有多个。因此先建立引用。
FileOutputStream fos = null;
//指定碎片的位置。
File dir = new File("c:\\partfiles");
if(!dir.exists())
dir.mkdir();
//碎片文件大小引用。
File f = null;
byte[] buf = new byte[1024*1024];
//由于切割完的文件一般都有规律的。为了简单标记规律使用计数器。
int count = 0;
int len = 0;
while((len=fis.read(buf))!=—1){
f = new File(dir,(count++)+".part");
fos = new FileOutputStream(f);
fos.write(buf,0,len);
fos.close();
}
//碎片文件生成后,还须要定义配置文件记录生成的碎片文件个数。以及被切割文件的名称。
//定义简单的键值信息,但是用Properties。
String filename = file.getName();
Properties prop = new Properties();
prop.setProperty("filename",filename);
prop.setProperty("partcount",count+"");
File config = new File(dir,count+".properties");
fos = new FileOutputStream(config);
prop.store(fos,"");
fos.close();
fis.close();
}
}
class SuffixFilter implements FileFilter{
private String suffix;
SuffixFilter(String suffix){
this.suffix = suffix;
}
public boolean accept(File file){
return file.getName().endsWith(suffix);
}
}
——————————————————————————————————————————————————————
RandomAccessFile:
特色:
1:该对象便可读取,又可写入。
2:该对象中的定义了一个大型的byte数组,经过定义指针来操做这个数组。
3:能够经过该对象的getFilePointer()获取指针的位置,经过seek()方法设置指针的位置。
4:该对象操做的源和目的必须是文件。
5:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。
class RandomAccessFileDemo{
public static void main(String[] args) throws IOException{
write();
read();
randomWrite();
}
//随机写入数据,能够实现已有数据的修改。
public static void randomWrite()throws IOException{
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
raf.seek(8*4);
System.out.println("pos :"+raf.getFilePointer());
raf.write("王武".getBytes());
raf.writeInt(102);
raf.close();
}
public static void read()throws IOException{
RandomAccessFile raf = new RandomAccessFile("random.txt","r");//只读模式。
//指定指针的位置。
raf.seek(8*1);//实现随机读取文件中的数据。注意:数据最好有规律。
System.out.println("pos1 :"+raf.getFilePointer());
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+"::"+age);
System.out.println("pos2 :"+raf.getFilePointer());
raf.close();
}
public static void write()throws IOException{
//rw:当这个文件不存在,会建立该文件。当文件已存在,不会建立。因此不会像输出流同样覆盖。
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");//rw读写模式
//往文件中写入人的基本信息,姓名,年龄。
raf.write("张三".getBytes());
raf.writeInt(97);
raf.close();
}
}
——————————————————————————————————————————————————————
管道流:管道读取流和管道写入流能够像管道同样对接上,管道读取流就能够读取管道写入流写入的数据。
注意:须要加入多线程技术,由于单线程,先执行read,会发生死锁,由于read方法是阻塞式的,没有数据的read方法会让线程等待。
public static void main(String[] args) throws IOException{
PipedInputStream pipin = new PipedInputStream();
PipedOutputStream pipout = new PipedOutputStream();
pipin.connect(pipout);
new Thread(new Input(pipin)).start();
new Thread(new Output(pipout)).start();
}
——————————————————————————————————————————————————————
对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。
注意:静态数据不能被序列化,由于静态数据不在堆内存中,是存储在静态方法区中。
如何将非静态的数据不进行序列化?用transient 关键字修饰此变量便可。
Serializable:用于启动对象的序列化功能,能够强制让指定类具有序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。若是不须要自动获取一个uid,能够在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不一样,或者对信息的高度敏感性。最好每个序列化的类都进行手动显示的UID的指定。
import java.io.*;
class ObjectStreamDemo {
public static void main(String[] args) throws Exception{
writeObj();
readObj();
}
public static void readObj()throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Object obj = ois.readObject();//读取一个对象。
System.out.println(obj.toString());
}
public static void writeObj()throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",25)); //写入一个对象。
oos.close();
}
}
class Person implements Serializable{
private static final long serialVersionUID = 42L;
private transient String name;//用transient修饰后name将不会进行序列化
public int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return name+"::"+age;
}
}
——————————————————————————————————————————————————————
DataOutputStream、DataInputStream:专门用于操做基本数据类型数据的对象。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeInt(256);
dos.close();
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
System.out.println(num);
dis.close();
——————————————————————————————————————————————————————
ByteArrayInputStream:源:内存
ByteArrayOutputStream:目的:内存。
这两个流对象不涉及底层资源调用,操做的都是内存中数组,因此不须要关闭。
直接操做字节数组就能够了,为何还要把数组封装到流对象中呢?由于数组自己没有方法,只有一个length属性。为了便于数组的操做,将数组进行封装,对外提供方法操做数组中的元素。
对于数组元素操做无非两种操做:设置(写)和获取(读),而这两操做正好对应流的读写操做。这两个对象就是使用了流的读写思想来操做数组。
//建立源:
ByteArrayInputStream bis = new ByteArrayInputStream("abcdef".getBytes());
//建立目的:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch = 0;
while((ch=bis.read())!=—1){
bos.write(ch);
}
System.out.println(bos.toString());
——————————————————————————————————————————————————————
网络编程:
端口:
物理端口:
逻辑端口:用于标识进程的逻辑地址,不一样进程的标识;有效端口:0~65535,其中0~1024系统使用或保留端口。
java 中ip对象:InetAddress.
import java.net.*;
class IPDemo{
public static void main(String[] args) throws UnknownHostException{
//经过名称(ip字符串or主机名)来获取一个ip对象。
InetAddress ip = InetAddress.getByName("www.baidu.com");//java.net.UnknownHostException
System.out.println("addr:"+ip.getHostAddress());
System.out.println("name:"+ip.getHostName());
}
}
Socket:★★★★,套接字,通讯的端点。
就是为网络服务提供的一种机制,通讯的两端都有Socket,网络通讯其实就是Socket间的通讯,数据在两个Socket间经过IO传输。
UDP传输:
1,只要是网络传输,必须有socket 。
2,数据必定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。
直接操做udp不可能,对于java语言应该将udp封装成对象,易于咱们的使用,这个对象就是DatagramSocket. 封装了udp传输协议的socket对象。
由于数据包中包含的信息较多,为了操做这些信息方便,也同样会将其封装成对象。这个数据包对象就是:DatagramPacket.经过这个对象中的方法,就能够获取到数据包中的各类信息。
DatagramSocket具有发送和接受功能,在进行udp传输时,须要明确一个是发送端,一个是接收端。
udp的发送端:
1,创建udp的socket服务,建立对象时若是没有明确端口,系统会自动分配一个未被使用的端口。
2,明确要发送的具体数据。
3,将数据封装成了数据包。
4,用socket服务的send方法将数据包发送出去。
5,关闭资源。
——————————————————————————————————————————————————————
import java.net.*;
class UdpSend{
public static void main(String[] args)throws Exception {
// 1,创建udp的socket服务。
DatagramSocket ds = new DatagramSocket(8888);//指定发送端口,不指定系统会随机分配。
// 2,明确要发送的具体数据。
String text = "udp传输演示 哥们来了";
byte[] buf = text.getBytes();
// 3,将数据封装成了数据包。
DatagramPacket dp = new DatagramPacket(buf,
buf.length,InetAddress.getByName("10.1.31.127"),10000);
// 4,用socket服务的send方法将数据包发送出去。
ds.send(dp);
// 5,关闭资源。
ds.close();
}
}
——————————————————————————————————————————————————————
udp的接收端:
1,建立udp的socket服务,必需要明确一个端口,做用在于,只有发送到这个端口的数据才是这个接收端能够处理的数据。
2,定义数据包,用于存储接收到数据。
3,经过socket服务的接收方法将收到的数据存储到数据包中。
4,经过数据包的方法获取数据包中的具体数据内容,好比ip、端口、数据等等。
5,关闭资源。
——————————————————————————————————————————————————————
class UdpRece {
public static void main(String[] args) throws Exception{
// 1,建立udp的socket服务。
DatagramSocket ds = new DatagramSocket(10000);
// 2,定义数据包,用于存储接收到数据。先定义字节数组,数据包会把数据存储到字节数组中。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
// 3,经过socket服务的接收方法将收到的数据存储到数据包中。
ds.receive(dp);//该方法是阻塞式方法。
// 4,经过数据包的方法获取数据包中的具体数据内容,好比ip,端口,数据等等。
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());//将字节数组中的有效部分转成字符串。
System.out.println(ip+":"+port+"——"+text);
// 5,关闭资源。
ds.close();
}
}
——————————————————————————————————————————————————————
TCP传输:两个端点的创建链接后会有一个传输数据的通道,这通道称为流,并且是创建在网络基础上的流,称之为socket流。该流中既有读取,也有写入。
tcp的两个端点:一个是客户端,一个是服务端。
客户端:对应的对象,Socket
服务端:对应的对象,ServerSocket
TCP客户端:
1,创建tcp的socket服务,最好明确具体的地址和端口。这个对象在建立时,就已经能够对指定ip和端口进行链接(三次握手)。
2,若是链接成功,就意味着通道创建了,socket流就已经产生了。只要获取到socket流中的读取流和写入流便可,只要经过getInputStream和getOutputStream就能够获取两个流对象。
3,关闭资源。