导图社区 Java SE
Java SE知识思维导图,包括:Java基础语法、Java OOP编程、Java高级特性、JDK8、Eclispe等内容。
编辑于2022-06-28 21:41:28Java SE
Java 基础语法
初始Java
Java 的历史
Java是Sun Microsystems于1995年推出的高级编程语言,2010年初被Oracle公司以74亿美元收购。
Java能够做什么
C/S(Client/Server)结构程序:C/S结构简单地说就是需要有一个客户端和服务器。例如:QQ、银行的自动取款机等等 B/S(Browser/Server)结构程序:B/S简单滴说就是通过浏览器访问网站或系统。例如:淘宝、新浪、某公司的OA系统等等。
Java的技术组成
Java SE:Java Standard Edition:基础部分 Java ME: Java Micro Edition:主要是便携式设备 Java EE:Java Enterprise Edition:主要是企业级开发方向
Java的优势
Java是跨平台(操作系统)的语言: 真正执行的不是二进制代码,而是字节码(bytecode)。 JVM(Java Virtual Machine,Java虚拟机) Java是跨平台的,而JVM不是跨平台的(JVM是由C语言编写的) Java之所以能够做到跨平台,是因为操作系统里都含有JVM,而本质原因在于JVM不是跨平台的。 Java的安全性: Java的健壮性(鲁棒):
写Java程序的准备工作
JDK的下载
下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html
JDK安装和配置
       NOTE:JDK的安装一定要把jdk和jre安装在同一目录下
命令提示符的使用
dir:查询当前路径 cd 可以进入和返回目录 cd aaa-->进入aaa目录 cd ..--->返回上一级目录 cd \ --->返回根目录 cd aa\bb\cc --->进入aa目录中的bb目录中的cc E:进入E盘
写第一个Java程序
Java程序的开发步骤
开发Java程序分三步: 所有的Java代码,其后缀都是以java结尾。 编译(javac ClassName.java) 执行(java ClassName) .class文件是字节码文件,程序最终执行的就是这个字节码(bytecode)文件。 
Java程序代码

Java的注释
Java中共有3种类型的注释: 单行注释:以//开头,//后面的所有内容均被当作注释处理。 多行注释:以/*开头,以*/结束,中间的所有内容均被当作注释处理。多行注释来源于C/C++。关于多行注释,需要注意的是,多行注释不能嵌套。 另一种多行注释:用于产生Java Doc帮助文档 注意:注释只是起提示说明作用,不参与编译。
数据类型和运算符
数据类型
数据类型的分类
原生类型
数值型: 整型:byte、short、char、int、long 浮点型:float、double boolean型:true false Java中的原生数据类型共有8种: 整型:使用int表示。(4个字节) 字节型:使用byte表示。(表示-128~127之间的256个整数, 1个字节)。 短整型:使用short表示。( 2个字节) 长整型:使用long表示。( 8个字节) 字符型:使用char表示(char是character的缩写)。所谓字符,就是单个的字符表示,比如字母a,或者中文张,外面用单引号包围上。比如char a = ‘B’; char b = ‘张’( 2个字节) 单精度浮点型:使用float表示。所谓浮点型,指的就是小数,也叫做实数,比如1.2f。(4个字节) 双精度浮点型:使用double表示。双精度浮点型表示的数据范围要比单精度浮点型大。( 8个字节) 布尔类型,使用boolean表示。布尔类型只有两种可能值,分别是true与false。 (1个字节)
引用类型
引用类型(对象类型):类、接口、枚举、数组
数据类型转换
1.自动类型转换(向上转型):由小类型转成大类型(由占用内存空间字节数少的类型转成占用内存空间字节数多的类型) 2.强制类型转换(下转型):由大类型转成小类型(由占用内存空间字节数多的类型转成占用内存空间字节数少的类型) 示例á 如下代码无法通过编译: int a = 1; short b = (short)a; 解析:a是int类型,b是short类型,int类型表示的数据范围要比short类型大,不能将表示范围大的值赋给表示范围小的变量。如下代码可以通过编译: 如下代码可以通过编译: short a = 1; int b = a; 解析: a是short类型,b是int类型,int类型表示的数据范围要比short类型大,可以将表示范围小的值赋给表示范围大的变量。 总结:可以将表示范围小的值赋给表示范围大的变量;但不能直接将表示范围大的值赋给表 示范围小的变量,只能通过强制类型转换实现
计算机的基本单位
关于计算机系统中的数据表示 位:bit(只有0,1两种状态),是计算机系统中的最小数据表示单位。 字节:byte, 1B(Byte 字节)=8bit, 1KB (Kilobyte 千字节)=1024B, 1MB (Megabyte 兆字节 简称“兆”)=1024KB, 1GB (Gigabyte 吉字节 又称“千兆”)=1024MB, 1TB (Trillionbyte 万亿字节 太字节)=1024GB,其中1024=2^10 ( 2 的10次方), 1PB(Petabyte 千万亿字节 拍字节)=1024TB, 1EB(Exabyte 百亿亿字节 艾字节)=1024PB, 1ZB (Zettabyte 十万亿亿字节 泽字节)= 1024 EB, 1YB (Yottabyte 一亿亿亿字节 尧字节)= 1024 ZB, 1BB (Brontobyte 一千亿亿亿字节)= 1024 YB. 注:“兆”为百万级数量单位。 附:进制单位全称及译音 yotta, [尧]它, Y. 10^21, zetta, [泽]它, Z. 10^18, exa, [艾]可萨, E. 10^15, peta, [拍]它, P. 10^12, tera, [太]拉, T. 10^9, giga, [级]咖, G. 10^6, mega, [兆],M. 10^3
变量与常量
变量和常量的定义规则:必须是由字母、数字、下划线和$符号组成,且不能以数字开头。 变量的声明通常用名词,以小写字母开头,个别情况会以下划线(_)开头,$符号基本不用。例如: int num = 3; 常量的声明通常用名词,以全大写表示,如果该常量由多个单词组成,中间以下划线相连。例如:final int NUM_AGE = 23;
变量
概念
能够变化的量就是变量(类似于数学里的未知数)
语法
对于任意一个变量只要定义都需要三部分的内容 变量的类型 变量名称 = 变量的值 (变量的值必须和类型一致)
赋值
变量的赋值: 字节类型的赋值: byte b = 3; 短整型类型的赋值: short s = 3; 字符类型的赋值: char c = '张'; 整型类型的赋值: int i = 34; 长整型类型的赋值: long l = 23; 单精度浮点型的赋值: float f = 2.3f; 双精度浮点型的赋值:double d = 2.4; 布尔类型的赋值:boolean b = true;
常量
概念
不能变化的量
语法
定义常量的语法 final 常量名 = 常量值;
赋值
常量的赋值: final int NUM_AGE = 34;
运算符
算术运算符
+、-、*、/、%、乘方、开方、++、--等 NOTE: 除法运算符的结果只取整,不取小数。例如: 5 / 2 == 2; 取模结果的符号与被除数的符号相同。例如: 5 % 2 == 1; 5 % -2 == 1; -5 % 2 == -1; -5 % -2 == -1; 关于变量的自增与自减运算: 1. 关于int b = a++,作用是将a的值先赋给b,然后再让a自增1. 2. 关于int b = ++a,作用是将a的值先自增1,然后将自增后的结果赋给b 3. 关于int b = a--,作用是将a的值先赋给b,然后再让a自减1. 4. 关于int b = --a,作用是将a的值先自减1,然后将自增后的结果赋给b
关系运算符
>、=、 关系运算符的结果都是产生的布尔值
逻辑运算符
逻辑与&& :用&&表示,逻辑与是个双目运算符(即有两个表达式的运算符),只有当两个表达式都为真的时候,结果才为真;只要有一个表达式的结果为false,其结果就为false。逻辑与表示的并且的意思。 逻辑或||:用||表示,逻辑或也是个双目运算符,只有当两个表达式都为假的时候,结果才为假;只要有一个表达式的结果为真(true),其结果就为真(true)。逻辑或表示或者的意思。 逻辑非—(不用) 逻辑运算符的短路特性: 逻辑与:如果第一个操作数为false,那么结果肯定就是false,所以在这种情况下,将不执行逻辑与后面的运算了,即发生了短路。 逻辑或:如果第一个表达式为true,那么结果肯定就是true,在这种情况下,将不再执行逻辑或后面的运算了,即发生了短路。 示例: 测试逻辑与&&是否短路 
按位运算符
按位与 & 按位或 | 按位非 ^ 测试按位或是否短路: 
位移运算符
左移运算符 右移运算符>>:右移一位相当于除2 请您以最有效率的方式写出2* 8的结果 System.out.println(2
复合运算符
+=:例如:int sum += num;相当于sum = sum + num; -+:例如:int sum -= num;相当于sum = sum - num; /=:例如:int sum /= num;相当于sum = sum / num; %=:例如:int sum %= num;相当于sum = sum % num;
三目运算符
也叫三元表达式: ? : 例如: 5 > 4 ? 1 : 0; 三目运算符的本质是一个if-else语句 NOTE: 建议问号后面的值和冒号后面的值的类型要保持一致
语法结构
顺序结构
按照程序的顺序执行
选择结构
if-else结构
语法
选择结构 if(条件) { 条件满足,执行代码 } else if(条件) { 如果这个条件满足,就执行这段代码 } else { 以上条件都不满足,执行这段代码 }
示例
选择结构示例: 
分支结构
switch结构
语法
分支结构(可以通过选择结构来实现) switch(整型) { case 值:xxx; case 值:xxx; default:xxx } switch后的括号中能够接收的数据类型有:byte、short、char、int、enum、String(在JDK7之后才可以使用的类型)
示例
分支结构示例: 
循环结构
while循环
语法
语法: while(布尔表达式) { //待执行的代码 } 先判断条件,如果条件成立则进入循环体,否则直接跳出循环。也就是说如果条件不满足,循环体中的内容一次都不执行。
示例
while循环示例 
do-while循环
语法
语法: do { //待执行的代码 } while(布尔表达式); 先执行循环体一次,再判断条件是否成立,如果条件成立则再次进入循环体,否则跳出循环。也就是说即使条件不满足,循环体中的内容一次也要最少执行一次。
示例
do-while示例: 
for循环
语法
语法: for循环(使用最多的一种循环),形式为: for(变量初始化; 条件判断; 步进) { //待执行的代码 } for循环的执行过程: 执行变量初始化。 执行条件判断。如果条件判断结果为假,那么退出for循环,开始执行循环后面的代码;如果条件判断为真,执行for循环里面的代码。 执行步进。 重复步骤2。
示例
for循环示例: 
补充:break和continue的用法
break语句:经常用在循环语句中,用于跳出整个循环,执行循环后面的代码。 continue语句:经常用在循环语句中,用于跳出当前的这个循环(或者是跳出本次循环),开始下一次循环的执行。
数组
概念
数组的定义:相同类型数据的集合就叫做数组。
语法及知识点
数组的定义:type[] 变量名 = new type[数组中元素的个数]; 可以按照下列方式定义长度为10的数组: int[] a = new int[10]; 或者 int a[] = new int[10]; Java中的每个数组都有一个名为length的属性,表示数组的长度。length属性是public,final,int的。数组长度一旦确定,就不能改变大小。 数组中的元素索引是从0开始的。对于数组来说,最大的索引==数组的长度 – 1。 int[] a = new int[10],其中a是一个引用,它指向了生成的数组对象的首地址,数组中每个元素都是int类型,其中仅存放数据值本身。 6. Arrays工具类
数组的赋值方式
数组的赋值方式: 直接赋值 type[] 变量名 = new type[]{逗号分隔的初始化值列表}; type[] 变量名 = {逗号分隔的初始化值列表};
直接赋值
int[] arr = new int[5];// 定义一个有五个元素的整型数组 //arr[0] = 0;//中括号里的数组叫做数组的下标,也叫做索引;它的值是从0开始 // // arr[1] = 1; // // arr[2] = 2; // // arr[3] = 3; // // arr[4] = 4;//数组下标(索引)的最大值是:数组的长度 - 1 for(inti = 0; i arr.length; i++) { System.out.println("arr[" + i + "] = " + i); }
以new的方式
int[] arr = newint[] { 9, 4, 7, 1, 3 };
不以new的方式
int[] arr = { 9, 4, 7, 1, 3 };
数组的工具类
数组的工具类:java.util.Arrays。该类提供了给无序数组排序,进行二叉树查找的一些方法 示例:如果给数组int[] arr = { 9, 4, 7, 1, 3 };排序,则输出顺序为1,3,4,7,9
Java OOP编程
Java的面向对象编程是学习Java语言最基础、最重要的知识。
面向对象与面向过程的区别
面向对象编程更关注于整体,而面向过程编程关注细节。
类和对象
对象的概念
对象:万物皆对象,是一个具体的事物
类的概念
类是抽象出来的,是具有相同属性和行为的一些事物的抽象。是Java的基本编程单位
类的组成
属性:也叫字段、成员变量。从java程序的角度上说,定义在方法外部的变量就是属性 行为:也叫方法、成员方法。是事物具有的动作。
类与对象的关系
一个类可以产生多个对象(has a),一个对象只能从属于一个类(is a)。
类的定义
用class来定义一个类 
对象的创建
创建一个对象用new来实现 
成员方法的重载
方法重载的分类: 在同一个类中的重载 在继承前提下的重载 同一类中的重载条件:在同一个类中,不关注方法的返回值类型,方法名称相同 方法中的参数个数不同就是重载  方法中只有一个参数时,如果方法中的参数类型不一致就是重载  方法中有两个以上参数时,如果方法中的参数类型不同,且参数的顺序不同亦是重载  3. 在继承前提下的重载只有大前提是在继承的前提下,其他的均和在同一个类中的重载相同
构造方法
构造方法只能被调用,不能被继承
作用
初始化对象的属性信息
分类
默认构造方法
不带参数的构造方法就是默认构造方法 如果一个类中没有任何带参的构造方法,则系统会自动生成一个默认构造方法 
带参构造方法
一个类中可以有多个构造方法,如果该类中只有带参的构造方法,则默认构造方法将不在自动生成,如果需要必须要显示写出。 
特征
构造方法没有任何的返回值类型 构造方法的名称必须和类名相同 构造方法中不能有return语句
构造方法的重载
同成员方法的重载条件一样,但必须在同一个类中
super和this的用法
super的用法: 必须放在构造方法中的第一行 调用父类的默认构造方法 如果要调用父类中带参数的构造方法,则需要在super(ParamValue); this的用法: 必须放在构造方法里的第一行 作用是:调用同一类中的相应构造方法  NOTE:构造方法中的super和this不能共存。
成员方法
访问修饰符 返回值类型 方法名称(parameters list) { 方法体 } 
对象的赋值方式
直接赋值
例如:Person类中有两个属性id和name,可以直接赋值(开发中不用): 
构造方法赋值
Person类中的构造方法赋值:  //构造方法的赋值 
setter和getter方法赋值
setter和getter方法(开发中用): 1.setter方法是给属性赋值(一般都是私有属性) 2.getter方法是获取属性值(一般都是私有属性) 
对象的输出:toString方法
对于输出Java对象的信息,Java语言里没有直接提供一个输出对象信息的方法,而是把该对象信息以字符串的方式输出,即为toString()方法。所以如果要输出一个对象的信息时,如果该类中没有toString()方法,则一定要重写一个toString()方法。  
面向对象的三个基本特征
封装
概念
隐藏对象的实现细节。
作用
安全
包
定义
用package定义包,放在程序的第一行 
好处
起限制作用
命名规范
互联网网址反过来写,一般都是小写字母
访问修饰符
public
public级别是作用范围最大:只要是在当前项目范围内都可以访问到
protected
protected级别是在继承的前提下有效,不管是否在同一个包中都可以访问得到,作用范围次之。
包级别(无关键字)
包级别范围只能在当前的包中才能访问
private
private范围只有在当前类中有效,如果要在类的外部访问private修饰的属性,只能通过getter和setter方法
static关键字的用法
static修饰成员变量时称为静态变量,也叫类变量。修饰字段时,属于该类的对象所共有,在内存中只有一份 static修饰方法时称为静态方法,也叫类方法。修饰方法时,属于该类的对象所共有,在内存中只有一份 使用规则: 有两种使用方式 实例化后通过引用名.属性名(方法名)来调用 直接通过类名.属性名(方法名)来调用 NOTE:建议用第中方式。 实例化后通过引用名.属性名(方法名)来调用  直接通过类名.属性名(方法名)来调用  总结:静态方法在使用时只能调用静态方法或静态变量,非静态方法中既可调用非静态的方法或非静态变量,也可以调用静态的方法或静态变量
继承
好处
减少代码的重复使用,方便程序的维护与扩展
实现
通过extends关键字来实现  
父类与子类的关系
父类也叫基类,子类也叫派生类。 子类和父类是继承关系。 子类的功能是父类功能的一种扩展。即父类中的非私有的属性或方法都可以被子类作为其自身的方法来使用。
继承前提下方法重载
1.继承中的方法的Íý和在同一类中的Íý条件相同,只是将前提条件改为在继承的前提下。 2.在继承前提下的重ý条件:在有继承关系的类中,不关注方法的返回值类型,方法名称相同 (1)方法中的参数个数不同就是重ý  (2)方法中只有一个参数时,如果方法中的参数类型不一致就是重载  (3)方法中有两个以上参数时,如果方法中的参数类型不同,且参数的顺序不同亦是重载  3. 在继承前提下的重载只有大前提是在继承的前提下,其他的均和在同一个类中的重载相同
方法重写
只有在继承的前提下才有重写。 重写的条件:在继承前提下,关注方法的返回值类型,方法的名称必须相同,方法中的参数个数、参数类型、以及其参数类型的顺序必须相同 简单地说,子类方法的签名必须和父类中的方法的签名相同。   以上两个类中省略了getter和setter方法,其中Person类和Employee类中的run方法就是重写方法。
super和this的用法
1.super的用法:在成员方法中,super关键字可以放在程序的任一行,语法为super.方法名(); 2.this的用法:在成员方法中,this关键字可以放在程序的任一行,语法为this.方法名();
final的用法
final的用法 修饰类的时候该类不能被继承。例如:String 修饰方法的时候该方法不能被重写。 修饰字段的时候该字段时常量。 修饰字段时的赋值方式有直接赋值和构造方法赋值两种形式
多态
抽象类和接口
抽象类与接口的区别 抽象类类似于是一种模板,而接口则相当于标准。接口可以更方便的解耦。
抽象类
定义
通过abstract关键字来定义的类就是抽象类 
特征
抽象类不能被实例化,即不能通过new来创建对象
抽象方法
抽象方法的特征:只有方法的声明,没有方法的实现。即没有方法体。 抽象类与抽象方法的关系:抽象类中不一定有抽象方法,但有抽象方法中的类一定是抽象类。
与具体类的关系
抽象类(Abstract Class)和具体类(Concrete Class)是继承关系,如果是具体类继承抽象类,并且抽象类中如果有抽象方法,则具体类必须要重写抽象类中的抽象方法。否则该具体类也将变成一个抽象类。 
接口
定义
用interface定义的是接口 
特征
在JDK7之前的版本中: 接口中所有的方法都是public abstract的方法。 接口中的属性都是public static final类型的,必须赋初始值。
与类之间的关系
类与接口之间都是实现(implements)关系,并且可以实现多个接口 
与接口之间的关系
接口与接口之间是继承关系,并且可以多继承  其中A和B都是接口
概念
简单地说是一种事物可以有不同的表示方式 比如说一个学生类,可以说某个学生是学生,学生是人等,也就是说可以用人和学生来表示某个学生。
好处
减少代码的重复使用,方便程序的扩展与维护。
本质
多态的本质是动态绑定,也叫运行时绑定。也就是说在程序编译时不知道它的具体类型是什么,只有在运行时才知道这个类型是一个具体的什么类型。
用法
是整个面向对象的核心
直接使用
直接使用多态:   instanceof关键字的用法:用于对象类型的强制类型转换,避免因错误的强制类型转换抛出的异常。instanceof指判断左边对象的引用是不是右边类型的一个实例
方法中的参数使用
方法中的参数使用多态是在实际开发过程中使用最多的一种方式。  
方法的返回值使用
方法的返回值使用多态:其本质是工厂方法模式  
单实例设计模式
单实例设计模式:   单实例设计模式的四要素: 1. 单实例类必须是final类型(目的是阻止其被继承) 2. 单实例里必须要有一个私有的构造方法(目的是阻止在类的外部对其用new来实例化) 3. 单实例类里一定要有一个私有的、静态的、fanal类型的一个实例(目的是要有一个不能改变的实例,以供调用) 4. 单实例类里一定要有一个公有的、静态的、返回要素3中实例的一个方法(目的是要获取这个单实例)
异常和错误
异常和错误的根类是Throwable Throwable 类是 Java 语言中所有错误或异常的超类。 两个子类的实例,Error 和 Exception,通常用于指示发生了异常情况。通常,这些实例是在异常情况的上下文中新近创建的,因此包含了相关的信息(比如堆栈跟踪数据)。
错误
Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。Error:是系统中的错误,程序员是不能改变,处理的,在程序编译的时候出现的错误.只有通过修改程序才能修正Error.
异常
Exception表示程序需要捕捉和处理的的异常; Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理。用try来指定一块预防所有“异常”的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的“异常”的类型。throw语句用来明确地抛出一个“异常”。throws用来标明一个成员函数可能抛出的各种“异常”。finally为确保一段代码不管发生什么“异常”都被执行
异常的引入
当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。所有的异常都是java.lang.Thowable的子类。
分类
非运行时异常
非运行时异常:又称为编译时异常,即当程序在编译之前就出现错误。
运行时异常
运行时异常在代码完成时没有错误,在运行时会出现异常。
try-catch-finally结构
Exception:是在程序运行的时候捕捉的错误,是可以处理的异常. 语法是:try {} catch(Exception 名字) {} finally {进行资源的清理工作: 比如关闭打开的文件: 删除临时的文件; ....}
自定义异常
自定义异常是开发中必用的知识: 自定义异常必须要继承Exception类 
Java 高级特性
集合和泛型
泛型的作用
泛型主要是保证集合使用过程中的安全问题。
集合的结构
 
List接口
List接口是有序的、可以添加重复元素、也可以添加null,并且可以添加多个。
Vector
Vector是同步的、同步意味着安全,但效率低。现在开发中基本不用 Vector迭代时是用Enumeration迭代器迭代,不能以Iterator迭代,因为如果数据量特别大时,迭代会快速失败。示例如下: 
ArrayList
ArrayList是异步的、异步以为着效率高,但不安全。 ArrayList是一种顺序存储结构,从数据结构的角度上说,它在查询时平均复杂度低,效率高;在修改或删除时,平均复杂度高,效率低。 ArrayList迭代时是用Iterator迭代器迭代,不能以Enumeration迭代,因为如果数据量特别大时,迭代会快速失败。示例如下: 
LinkedList
LinkedList是一种双链式结构,从数据结构的角度上说,它在修改或删除时平均复杂度低,效率高;在添加时,平均复杂度高,效率低。 LinkedList迭代时是用Iterator迭代器迭代,不能以Enumeration迭代,因为如果数据量特别大时,迭代会快速失败。示例如下: 
Set接口
Set接口是无序的、不可以可以添加重复元素、可以添加null,但只能添加一个。
HashSet
HashSet实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素 HashSet示例: 
SortedSet
SortedSet是Set的子接口,在JDK5之前,TreeSet都是直接实现的SortedSet接口,JDK6新增加了其子接口NavigableSet.
NavigableSet
NavigableSet扩展的 SortedSet,具有了为给定搜索目标报告最接近匹配项的导航方法。方法 lower、floor、ceiling 和 higher 分别返回小于、小于等于、大于等于、大于给定元素的元素,如果不存在这样的元素,则返回 null。可以按升序或降序访问和遍历 NavigableSet。descendingSet 方法返回 set 的一个视图,该视图表示的所有关系方法和方向方法都是逆向的。升序操作和视图的性能很可能比降序操作和视图的性能要好。此外,此接口还定义了 pollFirst 和 pollLast 方法,它们返回并移除最小和最大的元素(如果存在),否则返回 null。subSet、headSet 和 tailSet 方法与名称相似的 SortedSet 方法的不同之处在于:可以接受用于描述是否包括(或不包括)下边界和上边界的附加参数。任何 NavigableSet 的 Submap 必须实现 NavigableSet 接口。
TreeSet
TreeSet类是有序的,不可以添加重复元素。基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。 
Map接口
Map将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值
Hashtable
Hashtable是java se中常用类中唯一一个没有按照命名规范定义的一个类名,其中Hashtable中的"t"是小写,而不是大写。 Hashtable是同步的,同步意味着效率低,但是安全。 示例: 
Properties
Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
HashMap
HashMap是基于哈希表的 Map 接口的实现。是以键值对的方式存在,可以参考高中代数中的映射来理解。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap是异步的,异步意味着效率高,但是不安全。 示例: 
SortedMap
SortedMap进一步提供关于键的总体排序 的 Map。和SortedSet类似,该映射是根据其键的自然顺序进行排序的,或者根据通常在创建有序映射时提供的 Comparator 进行排序。对有序映射的 collection 视图(由 entrySet、keySet 和 values 方法返回)进行迭代时,此顺序就会反映出来。要采用此排序方式,还需要提供一些其他操作(此接口是 SortedSet 的对应映射)。
NavigableMap
NavigableMap扩展的 SortedMap,和NavigableSet类似,具有了针对给定搜索目标返回最接近匹配项的导航方法。方法 lowerEntry、floorEntry、ceilingEntry 和 higherEntry 分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象,如果不存在这样的键,则返回 null。类似地,方法 lowerKey、floorKey、ceilingKey 和 higherKey 只返回关联的键。所有这些方法是为查找条目而不是遍历条目而设计的。
TreeMap
TreeMap基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。 注意,如果要正确实现 Map 接口,则有序映射所保持的顺序(无论是否明确提供了比较器)都必须与 equals 一致 
常用的迭代器
Enumeration
Enumeration迭代器一般专用于对Vector的迭代,不能以Iterator迭代,因为如果数据量特别大时,迭代会快速失败。
Iterator
Iterator迭代器一般用于ArrayList、LinkedList以及Set接口的迭代,不能以Enumeration迭代,因为如果数据量特别大时,迭代会快速失败
ListIterator
ListIterator迭代器是Iterator迭代器的子接口,它只用于对ArrayList及LinkedList的迭代,除了其具备Iterator的功能外,其还具备反序迭代的功能 反序迭代ArrayList: 
Collections工具类
Collections是集合的一个工具类,此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含了操作Collection的最长用的一些方法。例如:排序、复制集合、集合中添加集合、随机排序、 让异步的ArrayList等变成同步的ArrayList的方法等。 排序方法:  随机输出集合中元素:  由异步变同步的方法: 
实用类
Object
Object类是类层次结构的根类。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。
toString方法
返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。
equal和hashCode方法
比较两个对象是不是同一个对象,即占用的内存相同,用“==”和equals方法。 “==”和equals方法的区别: “==”始终是比较两个对象是不是同一个对象。 equals方法比较两个对象是否相等,即是不是同一个对象,但当Object的子类中重写了equals和hashCode方法之后,则equals比较的是两个对象的内容是否相等。例如String类就重写了Object类中的equals和hashCode方法。 示例(以下示例重写了Object类中的hashCode和equals方法后才是比较的两个对象的值是否相等):    
String
String的常用方法:   
StringBuffer和StringBuilder
使用StringBuilder来连接字符串:  StringBuffer和StringBuilder的方法相同,只不过StringBuffer是同步的,而StringBuilder是异步的。
Date
类 Date 表示特定的瞬间,精确到毫秒。 在 JDK 1.1 之前,类 Date 有两个其他的函数。它允许把日期解释为年、月、日、小时、分钟和秒值。它也允许格式化和解析日期字符串。不过,这些函数的 API 不易于实现国际化。从 JDK 1.1 开始,应该使用 Calendar 类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和解析日期字符串。Date 中的相应方法已废弃。 尽管 Date 类打算反映协调世界时 (UTC),但无法做到如此准确,这取决于 Java 虚拟机的主机环境。当前几乎所有操作系统都假定 1 天 = 24 × 60 × 60 = 86400 秒。但对于 UTC,大约每一两年出现一次额外的一秒,称为“闰秒”。闰秒始终作为当天的最后一秒增加,并且始终在 12 月 31 日或 6 月 30 日增加。例如,1995 年的最后一分钟是 61 秒,因为增加了闰秒。大多数计算机时钟不是特别的准确,因此不能反映闰秒的差别。 
Calendar
Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。 
Math
Math 类包含用于执行基本数学运算的方法 
Random
此类的实例用于生成伪随机数流。 
包装类
输入输出(I/O)流
输入输出是相对于内存而言。把数据加载到内存中是输入;而把数据从内存中清除出去是输出
File
文件和目录路径名的抽象表示形式。 用户界面和操作系统使用与系统相关的路径名字符串来命名文件和目录。此类呈现分层路径名的一个抽象的、与系统无关的视图。抽象路径名 有两个组件: 一个可选的与系统有关的前缀 字符串,比如盘符,"/" 表示 UNIX 中的根目录,"\\\\" 表示 Microsoft Windows UNC 路径名。 零个或更多字符串名称 的序列。 
字节流
字节流使用了装饰设计模式 字节流是基于二进制的万能流,即对于任何类型的文件通过字节流都可以对其进行读写
InputStream
字节输入流的结构图: 
FileInputStream
FileInputStream 用于读取诸如图像数据之类的原始字节流。读取的对象时文件   JDK7的写法: 
FilterInputSteam
输入输出流使用了装饰设计模式(Decorator Pattern)。 通过FileInputStream的子类装饰其兄弟类,其子类之间可以相互装饰,但其兄弟类不是装饰实际模式,不能相互装饰。
BufferedInputStream
装饰例子:  
DataInputStream
OutputStream
字节输出流结构图: 
FileOutputStream
  JDK7的例子: 
FilterOUtputStream
BufferedOutputStream
同BufferedInputStream类似,只需要把例子中的Input换成Output即可。
DataOutputStream
字符流
字符流只能够读取文本文件,不能够读取流媒体文件,但理论上读取的效率高于字节流的效率
Reader
字符输入流结构图: 
FileReader
用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。 FileReader 用于读取字符流。要读取原始字节流,请考虑使用 FileInputStream。
BufferedReader
 
InputStreamReader
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。 每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。 为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如: BufferedReader in = new BufferedReader(neInputStreamReader(System.in)); 字节流到字符流转换示例:  
Writer
字符输出流结构图: 
FileWriter
用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。 文件是否可用或是否可以被创建取决于底层平台。特别是某些平台一次只允许一个 FileWriter(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。 FileWriter 用于写入字符流。要写入原始字节流,请考虑使用 FileOutputStream。
BufferedWriter
 
OutputStreamWriter
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。 每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给 write() 方法的字符没有缓冲。 为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。例如: Writer out = new BufferedWriter(new OutputStreamWriter(System.out)); 字符流到字节流的一个转换与字节流到字符流的转换类似:即把BufferedReader换成BufferedWriter, Input换成Output即可。
反射与代理
Reflection
1.在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。 2.Java 反射机制主要提供了以下功能: 1>.在运行时判断任意一个对象所属的类。 2>.在运行时构造任意一个类的对象。 3>.在运行时判断任意一个类所具有的成员变量和方法。 4>.在运行时调用任意一个对象的方法。 注:java的反射机制都是在运行时发生。缺点:会破坏类的封装性 3.Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现的interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods 4.尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。 这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造( 但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。这种“看透class” 的能力(the ability of the program to examine itself)被称为introspection(内省、 内观、反省)。Reflection和introspection是常被并提的两个术语 Java Reflection API 简介 1.在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中 1> Class类:代表一个类。 2> Field 类:代表类的成员变量(成员变量也称为类的属性)。 3> Method类:代表类的方法。 4> Constructor 类:代表类的构造方法。 5> Array类:提供了动态创建数组,以及访问数组的元素的静态方法
Class

Field
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段 
Method
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 
Constructor
Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。 
Array
Array 类提供了动态创建和访问 Java 数组的方法。 
Proxy
代理模式涉及到三个角色: 抽象(主题)角色:声明真实角色的接口; 真实(主题)角色:抽象角色的实现; 代理(主题)角色:代理角色内可以含有真实角色的引用,同时可以实现一些附加操作。
Static Proxy
静态代理示例:    
Dynamic Proxy
动态代理: 动态代理运用java中的反射机制.要写动态代理的首要任务是要实现java.lang.reflect 包里InvocationHandler接口, 创建一个invocation handler对象.该对象拦截对被代理类中方法的调用后执行需要的逻辑,然后才将调用转发给被代理类中的方法. Proxy类中的方法: getInvocationHandler():返回指定代理实例的调用处理程序 getProxyClass():返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。该代理类将由指定的类加载器定义,并将实现提供的所有接口。如果类加载器已经定义了具有相同排列接口的代理类,那么现有的代理类将被返回;否则,类加载器将动态生成并定义这些接口的代理类。 newProxyInstance(): 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于: Proxy.getProxyClass(loader, interfaces). getConstructor(new Class[] { InvocationHandler.class }). newInstance(new Object[] { handler }); isProxyClass(): 当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。 动态代理示例:     
线程
进程和线程及其区别
1.进程和线程 1.1 概述: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行. 相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。 在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。 1.2 区别: 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。 1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 2) 线程的划分尺度小于进程,使得多线程程序的并发性高。 3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。 4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。 1.3 优缺点: 线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。 2.多进程,多线程 2.1 概述: 进程就是一个程序运行的时候被CPU抽象出来的,一个程序运行后被抽象为一个进程,但是线程是从一个进程里面分割出来的,由于CPU处理进程的时候是采用时间片轮转的方式,所以要把一个大个进程给分割成多个线程,例如:网际快车中文件分成100部分 10个线程 文件就被分成了10份来同时下载 1-10 占一个线程 11-20占一个线程,依次类推,线程越多,文件就被分的越多,同时下载 当然速度也就越快 进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序只是一组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。而进程则不同,它是程序在某个数据集上的执行,是一个动态实体。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上运行的全部动态过程。进程是操作系统分配资源的单位。在Windows下,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。 在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多任务。现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。 多任务带来的好处是明显的,比如你可以边听mp3边上网,与此同时甚至可以将下载的文档打印出来,而这些任务之间丝毫不会相互干扰。那么这里就涉及到并行的问题,俗话说,一心不能二用,这对计算机也一样,原则上一个CPU只能分配给一个进程,以便运行这个进程。我们通常使用的计算机中只有一个CPU,也就是说只有一颗心,要让它一心多用,同时运行多个进程,就必须使用并发技术。实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。 如果一台计算机有多个CPU,情况就不同了,如果进程数小于CPU数,则不同的进程可以分配给不同的CPU来运行,这样,多个进程就是真正同时运行的,这便是并行。但如果进程数大于CPU数,则仍然需要使用并发技术。 在Windows中,进行CPU分配是以线程为单位的,一个进程可能由多个线程组成,这时情况更加复杂,但简单地说,有如下关系: 总线程数 总线程数> CPU数量:并发运行 并行运行的效率显然高于并发运行,所以在多CPU的计算机中,多任务的效率比较高。但是,如果在多CPU计算机中只运行一个进程(线程),就不能发挥多CPU的优势。 多任务操作系统(如Windows)的基本原理是:操作系统将CPU的时间片分配给多个线程,每个线程在操作系统指定的时间片内完成(注意,这里的多个线程是分属于不同进程的).操作系统不断的从一个线程的执行切换到另一个线程的执行,如此往复,宏观上看来,就好像是多个线程在一起执行.由于这多个线程分属于不同的进程,因此在我们看来,就好像是多个进程在同时执行,这样就实现了多任务. 2.2 分类 根据进程与线程的设置,操作系统大致分为如下类型: (1) 单进程、单线程,MS-DOS大致是这种操作系统; (2) 多进程、单线程,多数UNIX(及类UNIX的LINUX)是这种操作系统; (3) 多进程、多线程,Win32(Windows NT/2000/XP等)、Solaris 2.x和OS/2都是这种操作系统; (4) 单进程、多线程,VxWorks是这种操作系统。 2.3 引入线程带来的主要好处: (1) 在进程内创建、终止线程比创建、终止进程要快; (2) 同一进程内的线程间切换比进程间的切换要快,尤其是用户级线程间的切换。
线程的概念
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
线程的生命周期
线程的生命周期:一个线程从创建到消亡的过程。 线程的生命周期可以分为四个状态: (1)创建状态:当用new操作符创建一个新的线程对象时,该线程处于创建状态。处于创建状态的线程只是一个空的线程对象,系统不会为其分配资源 (2)可运行状态:执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体,即run()方法,这样就使得该线程处于可运行( Runnable )状态。这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行,它需要系统给其分配资源后才可以真正的运行。 (3)不可运行状态:当发生下列事件时,处于运行状态的线程会转入到不可运行状态。 •调用了sleep()方法; •线程调用wait方法等待特定条件的满足 •线程输入/输出阻塞 如果以上不可运行状态的形成要转成可运行状态,需要: •处于睡眠状态的线程在指定的时间过去后 •如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变 •如果线程是因为输入/输出阻塞,等待输入/输出完成 (4)消亡状态 :当线程的run方法执行结束后,该线程自然消亡。 3. 线程的状态转换图: 
线程的实现
在Java中通过run方法为线程指明要完成的任务,有以下两种技术为线程提供run方法的运行: 1.继承Thead类并重写run方法。 2.通过定义实现Runnable接口进而实现run方法。
继承Thread类
示例1:   示例2:    对于单核CPU来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。 对于双核或双核以上的CPU来说,可以真正做到微观并行。 NOTE:以上示例无法确定线程的执行顺序。
实现Runnable接口
示例1:   示例2:   
终止线程执行的常用方法
推荐方式: 1. 设置一个boolen类型在某种条件下改为false终止其执行 2. 在某种情况下可以用break终止其执行 3. 用return NOTE:不能用stop()方法
线程的优先级及其调度策略
1.线程的优先级及其设置 设置优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。 一个线程的优先级设置遵从以下原则: 线程创建时,子继承父的优先级 线程创建后,可通过调用setPriority()方法改变优先级。 线程的优先级是1-10之间的正整数。 在支持时间片的系统中,该线程的时间片用完。 1 - MIN_PRIORITY, 10 – MAX_PRIORITY 5- NORM_PRIORITY NOTE:不能依靠线程的优先级来决定线程的执行顺序 2.线程的调度策略 线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行。 •线程体中调用了yield()方法,让出了对CPU的占用权 •线程体中调用了sleep()方法, 使线程进入睡眠状态 •线程由于I/O操作而受阻塞 •另一个更高优先级的线程出现。
线程的成员变量和局部变量
   关于成员变量与局部变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
多线程的同步
为什么要引入线程的同步
为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。 解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。
线程同步到实现
同步线程的状态图:  synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。 Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。 4.如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是 synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序 的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始 执行。      synchronized块,写法: synchronized(object) { } 表示线程在执行的时候会对object对象上锁。      synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的。 死锁(deadlock): 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性的访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。 wait与notify方法都是定义在Object类中,而且是final的,因此会被所有的Java类所继承并且无法重写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或块当中。当线程执行了wait方法时,它会释放掉对象的锁。 另一个会导致线程暂停的方法就是Thread类的sleep方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。  示例: 线程所操作的对象:   增加线程:  减少线程:  测试类: 
线程组
1. 在Java中每个线程都属于某个线程组(ThreadGroup)。例如,如果在main()中产生一个线程,则这个线程属于main线程组管理的一员,您可以使用下面的指令来获得目前线程所属的线程组名称: Thread.currentThread().getThreadGroup().getName(); 一个线程产生时,都会被归入某个线程组,视线程是在哪个线程组中产生而定。如果没有指定,则归入产生该子线程的线程组中。您也可以自行指定线程组,线程一旦归入某个组,就无法更换组。 2. java.lang.ThreadGroup类正如其名,可以统一管理整个线程组中的线程,您可以使用以下方式来产生线程组,而且一并指定其线程组: ThreadGroup threadGroup1 = new ThreadGroup("group1"); ThreadGroup threadGroup2 = new ThreadGroup("group2"); Thread thread1 = new Thread(threadGroup1, "group1's member"); Thread thread2 = new Thread(threadGroup2, "group2's member"); 3. ThreadGroup中的某些方法,可以对所有的线程产生作用,例如interrupt()方法可以interrupt线程组中所有的线 程,setMaxPriority()方法可以设置线程组中线程所能拥有的最高优先权(本来就拥有更高优先权的线程不受影响)。 如果想要一次获得线程组中所有的线程来进行某种操作,可以使用enumerate()方法,例如: Thread[] threads = new Thread[threadGroup1.activeCount()]; threadGroup1.enumerate(threads);
线程的并发与并行
就当前计算机的技术来讲,目前大部分的语言能够满足并发执行,但是现在的多核cpu或者多cpu下开始产生并行的概念。 总体概念: 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。 并发编程: "并发"在微观上不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行,从宏观外来看,好像是这些进程都在执行。 使用多个线程可以帮助我们在单个处理系统中实现更高的吞吐量,如果一个程序是单线程的,这个处理器在等待一个同步I/O操作完成的时候,他仍然是空闲的。在多线程系统中,当一个线程等待I/O的同时,其他的线程也可以执行。 这个有点像一个厨师做麻辣鸡丝的时候同时做香辣土豆条,总比先做麻辣鸡丝再做香辣土豆条效率要高,因为这样可以交替着做。 上面这总情况是在单处理器(厨师)的系统处理任务(做菜)的情况,厨师只有一个,他在一个微观的时间点上,他只能做一件事情,这种情况就是虽然是多个线程,但是都是在同一个处理器上运行。 但是多线程并不能一定能提高程序的执行效率,比如,你的项目经理给你分配了10个bug让你修改,你应该会一个一个去改,一般的人不会每个bug去改5分钟,知道改完为止,如果这样的话,上次改到什么地方都记不得了。在这总情况下并发并没有提高程序的执行效率,反而因为过多的上下文切换引入了一些额外的开销。 因此在单cpu下只能实现程序的并发,但是无法实现程序的并行。 现在cpu到了多核的时代,那么就出现了新的概念:并行。 并行是真正的细粒度上的同时进行;既同一时间点上同时发生着多个并发;更加确切并且简单的来讲:就是每个cpu上运行一个程序,以达到同一时间点上每个cpu上运行一个程序。 总结,计算机的计算原理是顺序执行的;既一台计算机同一时间点上只能完成一个运算;既然突破不了这个理论基础,那么就在其基础上去发展去:为一台计算机内置多个cpu,这样就可以达到真正意义上的并行。 就目前来讲,硬件上已经到来了多cpu的时代,软件技术上已经开始跟进并行,比如java已经开始在新的虚拟机上加入并行。但是就当前来讲,并发编程还是主流的。那么什么情况下去使用并发编程呢? 比如,你有一个计算,包括cpu的计算,网络的传输,数据库的访问,io的操作,如果不采用并发,而是单线程执行,那么cpu计算结束,程序还需要等待接下来步骤的彻底完成,才算一次执行完毕。如果在执行的过程中,又有新的访问到来,那么就需要等待上一次的执行彻底完成,才能开始本次的访问。这样从很大程度上浪费了cpu的资源;如果采用并发编程,既线性编程,那么上面的过程就得这样描述了:一次请求完成cpu计算之后,开始等待剩余步骤的执行;而下一次的请求也可以先完成cpu的计算,进入剩余步骤状态;这样从很大程度上提高了程序的执行效率,也提高了cpu的利用率,充分的利用了cpu的计算资源。 因此,如果你的程序是只进行cpu的计算就over,那么采用线性编程就得不偿失,因为cpu一次只能完成一次计算,如果将一次正在进行的计算挂起,开始一个新的计算,新的计算进行一半,又被挂起,回头执行上次的计算;大家从现实当中就可以考虑一下:一个学生在同一时间,是只能做一门功课的,当前正在做语文作业,做了一些之后放下,开始做数学,那么这时还得打开数据作业本,整理思路,找到老师布置的科目等,然后开始做;然后做了一半,又放下开始做剩余的语文作业。。。。。。不用再描述下去了吧?所以,只进行cpu计算的程序,是不需要多线程的。而如果程序包括多个执行步骤,而其中的某一个步骤比较耗时,程序想将一次计算完毕,需要等待耗时的操作返回,这个时候就得考虑线性编程了。
网络编程
在现有的网络中,网络通讯的方式主要有两种: 1.TCP(传输控制协议)方式:面向连接的可靠的传输协议 TCP是Transfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作 2.UDP(用户数据报协议)方式:无连接的不可靠的传输协议 UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的 3.为了方便理解这两种方式,还是先来看一个例子。大家使用手机时,向别人传递信息时有两种方式:拨打电话和发送短信。使用拨打电话的方式可以保证将信息传递给 别人,因为别人接听电话时本身就确认接收到了该信息。而发送短信的方式价格低廉,使用方便,但是接收人有可能接收不到。 在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。 4.两种协议做简单比较 (1)使用UDP时,每个数据报中都给出了完整的地址信息,因此无需要建 立发送方和接收方的连接。 对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数 据传输之前必然要建立连接,所以在TCP中多了一个连接建立的时间 (2)使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定 在64KB之内。 TCP没有这方面的限制,一旦连接建立起来,双方的socket就可以按 统一的格式传输大量的数据。 (3)UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的 次序到达接收方。 TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送 的全部数据 总结: (1)TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。 (2)相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序 (3)这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。 (4)由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。
URL
URL: 类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。通俗地说读取互联网上的数据。  开发中可以使用commons中的io插件。网址:commons.apache.org  插件的用法: 
套接字编程
Socket的引入
为了能够方便的开发网络应用软件,由美国伯克利大学在Unix上推出了一种应用程序访问通信协议的操作系统调用socket(套接字)。socket的出现,使程序员可以很方便地访问TCP/IP,从而开发各种网络应用的程序。 随着Unix的应用推广,套接字在编写网络软件中得到了极大的普及。后来,套接字又被引进了Windows等操作系统中。Java语言也引入了套接字编程模型。
Socket的概念
Socket是连接运行在网络上的两个程序间的双向通讯的端点 通俗地说: 1.Socket(套接字)网络编程是基于C/S(Client/Server)结构:需要有一个服务器端和若干个客户端。 2.服务器套接字ServerSocket:此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。 3.客户端套接字Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
Socket网络通信的过程
使用Socket进行网络通信的过程 1.服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户的连接请求。 2.客户程序根据服务器程序所在的主机名和端口号发出连接请求。  3.如果一切正常,服务器接受连接请求。并获得一个新的绑定到不同端口地址的套接字。 4.客户和服务器通过读、写套接字进行通讯。 
Socket编程的实现
使用ServerSocket和Socket实现服务器端和客户端的 Socket通信  C/S结构示例: 模拟服务器端:  服务器端输入线程:   服务器端输出线程:   模拟一个客户端:  客户端输入线程:   客户端输出线程:   总结: (1)建立Socket连接 (2)获得输入/输出流 (3)读/写数据 (4)关闭输入/输出流 (5)关闭Socket
UDP
1. UDP(User Datagram Protocol),中文意思是用户数据报协议,方式类似于发短信息,是一种物美价廉的通讯方式,使用该种方式无需建立专用的虚拟连接,由于无需建立专用的连接,所以对于服务器的压力要比TCP小很多,所以也是一种常见的网络编程方式。但是使用该种方式最大的不足是传输不可靠,当然也不是说经常丢失,就像大家发短信息一样,理论上存在收不到的可能,这种可能性可能是1%,反正比较小,但是由于这种可能的存在,所以平时我们都觉得重要的事情还是打个电话吧(类似TCP方式),一般的事情才发短信息(类似UDP方式)。网络编程中也是这样,必须要求可靠传输的信息一般使用TCP方式实现,一般的数据才使用UDP方式实现。 2. UDP方式的网络编程也在Java语言中获得了良好的支持,由于其在传输数据的过程中不需要建立专用的连接等特点,所以在Java API中设计的实现结构和TCP方式不太一样。当然,需要使用的类还是包含在java.net包中。 3. 在Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是: (1)DatagramSocket DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。 (2)DatagramPacket DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。其实DatagramPacket类的作用类似于现实中的信件,在信件中包含信件发送到的地址以及接收人,还有发送的内容等,邮局只需要按照地址传递即可。在接收数据时,接收到的数据也必须被处理成DatagramPacket类型的对象,在该对象中包含发送方的地址、端口号等信息,也包含数据的内容。和TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。 4. 示例: 发送方:   接收方:  
JDK8
默认方法
什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。 为什么要有这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。 java 8抽象类与接口对比 这一个功能特性出来后,很多同学都反应了,java 8的接口都有实现方法了,跟抽象类还有什么区别?其实还是有的,请看下表对比。。   多重继承的冲突说明 由于同一个方法可以从不同接口引入,自然而然的会有冲突的现象,默认方法判断冲突的规则如下: 1.一个声明在类里面的方法优先于任何默认方法(classes always win) 2.否则,则会优先选取最具体的实现,比如下面的例子 C重写了A的hello方法。 示例:     
功能性接口(函数式接口)
函数式接口(functional interface 也叫功能性接口,其实是同一个东西)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但 最好在接口上使用注@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。 
Lambda表达式和方法的引用
Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现,下面讲到语法会讲到 Lambda语法 包含三个部分 1. 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数 2. 一个箭头符号:-> 3. 方法体,可以是表达式和代码块,方法体是函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{} 总体看起来像这样:(parameters) -> expression 或者 (parameters) -> { statements; }   示例2   方法引用 其实是lambda表达式的一个简化写法,所引用的方法其实是lambda表达式的方法体实现,语法也很简单,左边是容器(可以是类名,实例名),中间是"::",右边是相应的方法名。如下所示: ObjectReference::methodName 一般方法的引用格式是 1. 如果是静态方法,则是ClassName::methodName。如 Object ::equals 2. 如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals; 3. 构造函数.则是ClassName::new 方法引用的示例:    //测试方法引用类 
Stream API
流(Stream)仅仅代表着数据流,并没有数据结构,所以他遍历完一次之后便再也无法遍历(这点在编程时候需要注意,不像Collection,遍历多少次里面都还有数据),它的来源可以是Collection、array、io等等。 1中间与终点方法 流作用是提供了一种操作大数据接口,让数据操作更容易和更快。它具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法,“流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,如果是Stream则是中间方法,否则是终点方法。具体请参照Stream的api。 简单介绍下几个中间方法(filter、map)以及终点方法(collect、sum) (1)Filter 在数据流中实现过滤功能是首先我们可以想到的最自然的操作了。Stream接口暴露了一个filter方法,它可以接受表示操作的Predicate实现来使用定义了过滤条件的lambda表达式。 List persons = … //过滤18岁以上的人 Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18); (2)Map 假使我们现在过滤了一些数据,比如转换对象的时候。Map操作允许我们执行一个Function的实现(Function的泛型T,R分别表示执行输入和执行结果),它接受入参并返回。 首先,让我们来看看怎样以匿名内部类的方式来描述它: Stream adult= persons .stream() .filter(p -> p.getAge() > 18) .map(new Function() { @Override public Adult apply(Person person) { //将大于18岁的人转为成年人 return new Adult(person); } }); 现在,把上述例子转换成使用lambda表达式的写法: Stream map = persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)); (3)Count count方法是一个流的终点方法,可使流的结果最终统计,返回int,比如我们计算一下满足18岁的总人数 int countOfAdult=persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .count(); (4)Collect collect方法也是一个流的终点方法,可收集最终的结果 List adultList= persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .collect(Collectors.toList()); 或者,如果我们想使用特定的实现类来收集结果: List adultList = persons .stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .collect(Collectors.toCollection(ArrayList::new)); 篇幅有限,其他的中间方法和终点方法就不一一介绍了,看了上面几个例子,大家明白这两种方法的区别即可,后面可根据需求来决定使用。 2.顺序流与并行流 每个Stream都有两种模式:顺序执行和并行执行。 顺序流: List people = list.getStream.collect(Collectors.toList()); 并行流: List people = list.getStream.parallel().collect(Collectors.toList()); 顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时, 数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。 (1)并行流原理: List originalList = someData; split1 = originalList(0, mid);//将数据分小部分 split2 = originalList(mid,end); new Runnable(split1.process());//小部分执行操作 new Runnable(split2.process()); List revisedList = split1 + split2;//将结果合并 大家对hadoop有稍微了解就知道,里面的 MapReduce 本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。 (2)如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码 long t0 = System.nanoTime(); //初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法 int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray(); long t1 = System.nanoTime(); //和上面功能一样,这里是用并行流来计算 int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray(); long t2 = System.nanoTime(); //我本机的结果是serial: 0.06s, parallel 0.02s,证明并行流确实比顺序流快 System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9); 3.关于Folk/Join框架 应用硬件的并行性在java 7就有了,那就是 java.util.concurrent 包的新增功能之一是一个 fork-join 风格的并行分解框架,同样也很强大高效,有兴趣的可以去研究一下,这里不详谈了,相比Stream.parallel()这种方式,我更倾向于后者。 4.总结 如果没有lambda,Stream用起来相当别扭,他会产生大量的匿名内部类,比如上面的1.(2)map例子,如果没有default method,集合框架更改势必会引起大量的改动,所以lambda+default method使得jdk库更加强大,以及灵活,Stream以及集合框架的改进便是最好的证明。
Eclispe
建议不要使用中文版本
Eclipse基础
workspace
所有的开发项目都需要在workspace中,记住workspace放置在一个不会被删除的目录中  改变workspace 
project
eclipse和大多数IDE一样,都是基于project(项目)来管理和开发代码的 一定要想创建项目才能开发代码 ·创建项目: File-->new Java Project ·删除项目: 右键选择项目-->delete  ·导入项目   
创建和运行文件
所有的java文件都要在src目录中创建    ·编译的过程 自动编译  ·运行  
工作空间布局
1、恢复工作空间的布局 
Eclipse的常用操作
自动生成getter和setter方法

自动生成构造方法
 
常用的快捷键
1、多行注释 选中要注释的行,使用快捷键:ctrl+shift+/,取消注释:ctrl+shift+\ 2、单行注释 选择要注释的行,使用快捷键:ctrl+/,取消也是使用这个快捷键 3、删除一行 使用ctrl+d 4、自动代码提示(默认是alt+/)-->可以考虑改为alt+z   5、代码模板的使用  常用模板:main,sysout