java基础语法2.

第二章
2.1 class文件的生成
java文件为源代码文件
class为程序.
class文件实时修改.
eclipse自动生成.
project下面clean.


2.2 jar文件
如何将有用的类传给别人使用.
1.把*.java文件发给对方.
2.把*.class打包发给对方.

导出为jar文件.
右键export Java JAR file


2.3使用jar文件
new java project test2
右键 new folder libs
复制jar 粘贴到libs
右键jar build path add to
然后调用里面的方法,再声明就行了
库Library


2.4系统jar文件
String java.lang.String
ArrayList java.util.ArrayList

第三方jar
系统库System Library
三方库


第三章
3.1 抽象类Abstract Class
创建抽象类
public abstract class XXX //抽象类
{
    public abstract void yyy(); //抽象方法 不能有大括号(方法体)

}
抽象方法可以没有定义,称为抽象方法

抽象类不可实例化: FunnyThing f=new FunnyThing(); //错误 不可以实例化.

抽象类仅仅用于描述一类事情:应该有什么,应该能做什么.它不是具体类,不能创建对象.

所有抽象类方法的定义在子类里实现.


3.2抽象类的用法
暂时不需要写抽象类
java.io.InputStream
java.io.OutputStream

第四章
4.1接口
接口 interface(或翻做:界面)
定义一个接口
public interface AudioOutput
{
public void play(AudioData s); //不能写方法体{} 必须是public 默认接口都是抽象方法.
}

使用接口
和抽象类一样 必须派生一个子类;
public class XiaoMi implements AudioOutput
{

@Override
public void play(AudioData s)
{


}

}

接口和抽象类相似,区别为:
1.用implements 而不是extends(不表示继承关系)
2.一个类可以implements多个接口
public class X extends Y implements A,B,C
3接口不应该添加属性.(可以添加,但没有意义)

接口和继承是两个不同的设计概念

4.2接口的使用
当一个系统与另外一个系统对接时.
接口的使用

第五章 内部类
5.1内部类
当一个类写在另一个类内部时,称为内部类
内部类通常使用private,外部不可见.
如果你想在外部使用内部类,定义为public.


5.2内部类的使用
在内部类的里面可以访问外部类的所有方法和属性.
访问外部类时 + class名.this.

public class Example
{
private String name;

private void show()
{
System.out.println("名字:"+name);
}

public void test()
{
ABC abc=new ABC();
abc.work();
}

public class ABC
{
public void work()
{
Example.this.name="shao fa";
Example.this.show();
}
}

}


5.3静态内部类

在外面创建内部类对象
public class Example
{
public class ABC
{
}
}

Example e=new Example(); //实例化a
Example.ABC a=e.new ABC(); //实例化a

静态内部类
public class X
{
public String name;
public static class Y
{
}
}

Example.ABC a=new Example.ABC(); //实例化a

使用静态内部类 就无法使用Example.this.name="shao fa";

5.4 匿名内部类 //非常常用
内部类的简化写法
public interface XXX
XXX a=new XXX(){};
直接在大括号内重写XXX接口的方法
不用创建子类.

匿名内部类
还有一个写法
c.xxx(new XXX(){
})


第六章 静态对象
静态static
在Java中表示"全局的"
静态对象,即全局对象,一直存在的.
定义静态对象
public class MMM
{
public static XXX a=new XXX();
}

使用静态对象.
MMM.a.yyy(); //yyy 为XXX下面的方法

要点
1.在第一次使用时,静态对象被创建
例如:Example类被使用时,Example.a被创建
如果Example从未被使用,Exampee.a永不创建

2.静态对象不会被系统回收

3.静态对象只有一个实例
无论创建多少个Example对象,Example.a只创建一次.


6.2 单例模式
全局&&唯一实例 全局使用static实现,单例使用private实现.
在程序运行期间一直存在,唯一:只有一个实例
public class Earth
{
public static Earth i=new Earth(); //static成为了全局实例.

private Earth() //使用private 不能在外面添加新的实例,成为单例. 限制实例的创建. 让构造方法私有化
{

}
public void showCountries()
{
System.out.print("唯一地球");
}
}


第七章 出错处理
7.1出错处理
考虑2种情况,如果输入的字符串有问题.
1.输入非法字符.
2.用户输入过长的字符,超出int的极限.

public class Converter
{
public int status = 0;

// 把一个字符串转成整数
// 例如: "123" -> 123

public int str2int (String str)
{
status = 0;
if(str.length()>11)
{
status = -2; // 第2种情况
return 0;
}

int result = 0;
for(int i=0; i<str.length(); i++)
{
char ch = str.charAt(i);
if( ! isValid(ch) )
{
status = -1; // 第1种情况
return 0;
}
result = result * 10 + (ch - '0');
}

return result;
}

private boolean isValid(char ch)
{
if(ch >= '0' && ch <= '9')return true;
if(ch == '-') return false;
return false;
}
}

public class Test
{

public static void main(String[] args)
{
Converter conv = new Converter();

int result = conv.str2int("2201234");
if(conv.status == 0)
{
System.out.println("转换结果: " + result);
}
else
{
if(conv.status == -1)
System.out.println("非法字符");
else if(conv.status == -2)
System.out.println("超出范围");
}

}

}

可预期的错误情况.

7.2异常机制
1.在方法里,抛出异常
int str2int(String str)throws Exception
{
if(错误1发生)
throw new Exception("错误1");
if(错误2发生)
throw new Exception("错误2");
}

public int str2int (String str) throws Exception //添加throws Exception
{
status = 0;
if(str.length()>11)
throw new Exception("超出范围"); //抛出异常 也可以写作Exception ex=new Exception("超出范围")
int result = 0; //throw ex;
for(int i=0; i<str.length(); i++)
{
char ch = str.charAt(i);
if( ! isValid(ch) )
throw new Exception("非法字符"); //抛出异常
result = result * 10 + (ch - '0');
}

return result;
}

public static void main(String[] args) throws Exception //在main方法的入口添加throws Exception



2.在调用时,捕获异常
调用时,用try...catch...来捕获异常
try:监视若干行代码,如果里面抛出异常,则进入catch{}
catch:出错处理,参数为刚刚抛出的异常对象,所有出错信息都在异常对象里.

public static void main(String[] args) //或者使用try catch
{
Converter conv = new Converter();

int result;
try
{
result = conv.str2int("12134");
System.out.println("正常"+result); //正常情况下走try
}
catch (Exception e)
{
System.out.println(e.getMessage()); //异常时走catch
}


Java里普遍使用异常机制来进行出错处理
Exception对象本身就可以携带所有出错信息

7.3自定义异常
自定义异常 派生出Exception的子类
try {
int result = conv.str2int("2023321238");
System.out.println("正常:" + result);
}
catch( InvalidCharException e1)
{
System.out.println(e1.getMessage());
}
catch ( TooLargeException e2)
{
System.out.println(e2.getMessage());
}
catch( Exception e)
{
System.out.println(e.getMessage());
}

public class InvalidCharException extends Exception
{
public int pos; // 非法字符出现的位置
public char ch; // 非法字符

public InvalidCharException(int pos, char ch)
{
this.pos = pos;
this.ch = ch;
}

@Override
public String getMessage()
{
return "非法字符'" + ch + "',位置:" + pos;
}


}


public int str2int (String str) throws Exception
{
if(str.length()>11)
{
Exception ex = new TooLargeException(str.length()); //throw new TooLargeException(str.length());
throw ex;
}

int result = 0;
for(int i=0; i<str.length(); i++)
{
char ch = str.charAt(i);
if( ! isValid(ch) )
throw new InvalidCharException(i, ch);

result = result * 10 + (ch - '0');
}

return result;
}


7.4异常运行规则
1.抛出异常时,退出当前的方法

运行规则:捕获异常时
1.try{...}中出现异常时,退出try,进入catch
2.如果没有匹配到catch.
中断当前方法
继续向上抛出
上层调用者有义务抓住这个异常
main()-->a()-->b()-->c()
3.如果一个异常最终没被抓住,程序崩溃.

7.5退出清理
当异常出现时,doCleanJobs没有机会执行

退出清理finally
使用finally语句可以保证tyu{}退出时执行某些退出清理代码

tyu{}
catch..... //若干个
finally{}

规则:当退出try时,总是执行finally中的语句.

当异常发生时,跳出当前执行finally后结算.


e.printStackTrace(); //相对于e.getMessage有更多的信息
包含:-出错的代码行位置
异常的类型
每层方法的调用位置(函数栈)


7.6基本异常
语法类(基本异常)
1.空指针异常 NullPointerException
2.数组越界异常 ArrayIndexOutOfBoundsException
3.除零异常 ArithmeticException


即使没有throws声明,也能捕获异常.
Throwable父类 Error子类 Exception子类.

第八章 泛型
8.1泛型
通用的类型,
通用链表 GenericList 封装一个通用的链表设计
设计思路
1.不限制节点类型.
private static class GenericNode
{
Object value; //使用Object做为节点类型.
GenericNode next;
}

2.迭代器

8.2泛型的定义
泛型一般用于描述一种通用的算法,数据结构,对象类型其实不关心
在Java中泛型的规范写法.
public class Sample<T>
{
}
在类的定义里,T代表一个通用的类型,把T称为类型参数
T只是一个代号.


泛型的使用
public class Sample<T>
{
T value;

public void setValue(T value)
{
this.value=value;
}
public T getValue()
{
return this.value;
}

}


Simple<Student> sa=new Simple<Student>();

sa.setValue(new Student(1,"s","sd"));

Student s=sa.getValue();

System.out.println(s);

8.3常用的泛型
List,ArrayList
Map,HashMap

通常不需要我们自己写泛型,JDK自带的两个泛型足够使用

ArrayList:数组链表.
ArrayList<Student>
ArrayList<String>
ArrayList<Integer>
必须使用包装类.
使用
添加,插入
遍历:数组形式遍历,链表遍历
删除:数组形式删除,链表删除
1.排序
ArrayList<Student> list = new ArrayList<Student>();
list.add( new Student(1, "shao", "13810012345"));
list.add( new Student(2, "wang", "15290908889"));
list.add( new Student(3, "li", "13499230340"));
list.add( 0,new Student(4, "xxx", "1293023923923")); //从0的位置去插入

2.遍历
import java.util.Iterator;
Iterator<Student> iter=list.iterator();
while(iter.hasNext())
{
Student s=iter.next();
System.out.println(s);
}


链表的方式遍历


数组的方式遍历
for(int i=0;i<list.size();i++)
{
Student s=list.get(i);
System.out.println(s);
}


3.删除元素
数组方式删除
list.remove(index)

链表方式删除
Iterator<Student>iter=list.iterator();


4.排序
package my;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

public class Test
{

public static void main(String[] args)
{
ArrayList<Student> list=new ArrayList<Student>();
list.add(new Student(1,"陈","139")); //添加
list.add(new Student(2,"人","138"));
list.add(new Student(-1,"好","137"));
list.add(0,new Student(4,"我","136")); //插入

// Iterator<Student> iter=list.iterator();//删除
// while(iter.hasNext())
// {
// Student s=iter.next();
// if(s.id==2)
// {
// iter.remove();
// }
// }
//排序
Comparator<Student> comp=new Comparator<Student>() //定义一个比较器
{
@Override
public int compare(Student a,Student b)
{
if(a.id<b.id)
return -1;
if(a.id>b.id)
return 1;
return 0;
}
}; //有分号


//排序
Collections.sort(list, comp);

Iterator<Student> iter1=list.iterator(); //遍历
while(iter1.hasNext())
{
Student s=iter1.next();
System.out.println(s);
}

}

}

8.4
哈希映射表 HashMap
HashMap存储 key<=>value

HashMap的创建 需要制定key和value的类型
1.创建
HashMap<Integer,Student> map=.... //指定key的类型是Interger,value的类型是Student.

HashMap<Integer,Student>map=new HashMap();
2 添加对象

map.put(1, new Student(1,"2","3")); //map.put(key,new value());添加
map.put(2, new Student(4,"5","6")); //key为1,2,3,3, value为Student对象
map.put(3, new Student(7,"8","9"));
map.put(3, new Student(11,"12","13")); //同key值以后面一个为准

3.查找

Student s=map.get(3); //查找的是key值
if(s!=null)
{
System.out.println(s);
}

4.删除
删除一个对象
map.remove(key); //输入要删除的key值

删除所有
map.clear();

5遍历所有key和value
通常情况下map不遍历
//遍历所有的key
import java.util.Set; //声明
Set<Integer>keys=map.keySet(); //遍历所有的key
for(Integer k:keys)
{
System.out.println("key:"+k);
}


//遍历所有的value
for(Student v:map.values())
{
System.out.println("values:"+v);
}


重点能否以姓名做为key?
key的要求
1.可以比较 //String可以比较
2.唯一不重复 //如果姓名不重复,则可以.

Map的查找比list快?
HashMap查找更快

第九章JDK和JRE
9.1jdk jre
jdk Java开发工具包
jre Java运行环境
JDK是开发时的环境,JRE时程序运行环境.
JRE时JDK的子集;


9.2Java命令行
类的加载
类加载器ClassLoader
ClassLoader里寻找并加载需要的类
加载主类:my.Hello
根据import声明加载需要的类.

9.3可执行JAR包
cmd

cd /d d:\eclipse-workspace //移动到d盘目录下


java -jar example804.jar //执行jar包

jar包中有哪些东西
所有的class,所依赖的jar包里的class
清单文件META-INF/MANIFEST.MF
可发现,在清单文件里已经指定了Main Class

打包jar包 右键JAVA -Runnable JAR file

Java应用程序发布
-JRE环境
-class,jar
-执行脚本(双击运行).bat .cmd .sh


9.4命令行参数
命令行里的参数将被传递给main方法
public static void main(String[] args)
{
}
main方法的参数:来自命令行参数;

java -cp bin my.Hello 2 3
9.5JVM
JVM Java虚拟机
一个虚拟主机可以运行class文件

当Java程序运行时
打开jconsole,连接到java进程(JVM),观测CPU/内存/线程等信息
jdk1.8下面
做网站时会用到jconsole工具.

第十章
Java官方文档
https://docs.oracle.com/en/
常用api
Java API即java再带的类库

常用的
lang util io>net math>sql security nio>.......
java官方文档相当于字典.

左上package

左下class

10.2集成文档和源码
多看文档,少看源码


10.3
文档的使用方法
整数1234,转换成2进制字符串


第11章
11.1时间的表示
long 时间值
java.util.Date 时间对象
java.util.Calendar 时间操作工具
java.text.SimpleDateFormat 格式化工具类.

时间值long 单位:毫秒 从1970开始的时间值
运行一段代码花费的时间
public class Test
{
public void someBusyWork()
{
long start =System.currentTimeMillis(); //初始时间

for(int i=0;i<2002000;i++)
{
double a=Math.sin(i);
}

long duration=System.currentTimeMillis()-start; //求时间
System.out.println(duration);

}
public static void main(String[] args)
{
long now =System.currentTimeMillis();

Test t=new Test();
t.someBusyWork();

}

}

java.util.Date 基本都是Deprecated的类.不赞成使用.

java.util.Calendar 计算年月日时分秒
封装的时间工具.
Calendar cal=Calendar.getInstance();
int year=cal.get(Calendar.YEAR);
int month=cal.get(Calendar.MONTH); //月份的时间是从0开始计算,其他都是正常.
int day=cal.get(Calendar.DAY_OF_MONTH);
int house=cal.get(Calendar.HOUR);
int minute=cal.get(Calendar.MINUTE);
int second=cal.get(Calendar.SECOND);

Calendar与long的转换.
//Calendar ->long
long ms=cal.getTimeInMillis();

//long->Calendar
cal.setTimeInMillis(ms);


Calendar与Date转换.
//Calendar->Date
Dated=cal.getTime();

//Date->Calendar
cal.setTime(d);


11.2时间的处理
-时间与日期的差值
计算2018年2月10日-2018年3月10日之间多少天.
public static void main(String[] args)
{
Calendar c1=Calendar.getInstance(); //创建一个c1的实例 getInstance实例化.
Calendar c2=Calendar.getInstance();

c1.set(2018, 1, 10, 0, 0, 0); //日期转为long的毫米 月份的起始数为0,0表示1月,1表示2月.
c2.set(2018, 2, 10, 0, 0, 0);

long ms=c2.getTimeInMillis()-c1.getTimeInMillis(); //相差的毫米
long days=ms/(24*3600*1000); //因为结果是毫米所以*1000

System.out.println("相差天数为: "+days);
}

日期的推算.
public static void main(String[] args)
{
Calendar c1=Calendar.getInstance();

c1.set(2018, 2, 2, 0, 0, 0); //3月2日

c1.add(Calendar.DAY_OF_MONTH,+32); //第一个参数表示要修改哪个字段,第二个参数表示差值

System.out.printf("结果为:%d-%d-%d ",
c1.get(Calendar.YEAR),
c1.get(Calendar.MONTH)+1,
c1.get(Calendar.DAY_OF_MONTH)); //月份因为初始值为0所有要+1显示.
}

其中,add()的第一个参数表示要修改哪个字段,第二个参数表示差值

时间和日期的格式化
Date->String

public static void main(String[] args)
{
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//大写的HH 24小时制,小写的hh12小时制.

Date now =new Date();
String str=sdf.format(now);

System.out.println("日期:"+str);

}

String->Date

public static void main(String[] args)
{
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

try
{
String s1="2018-3-2 11:29:00";
Date t1=sdf.parse(s1);
}
catch(ParseException e)
{
e.printStackTrace();
}

第12章文件的操作
File文件,用于进行文件/目录相关操作
java.io.File

在Java文档里找到此类的说明.
1.File对象用于指向一个文件或者目录
"e:/examples/abc.txt"
"e\\examples\\abc.txt" (要转义)
并未创建实际对象,只是创建File对象.
public static void main(String[] args)
{
File f=new File("e:/examples/abc.txt");
}

2.判断对象是否存在.

public static void main(String[] args)
{
File f=new File("e:/examples/abc.txt");

if(f.exists()) //判断语句 f.exists
{
System.out.println("文件存在"+f);
}
else
{
System.out.println("文件不存在");
}
}

3.获取文件的属性
是文件还是目录isFile() isDirectory()
文件长度length()
最后修改时间lastModified()
public static void main(String[] args)
{
File a=new File("D:\\eclipse\\abc.txt"); //文件是否存在判断

if(a.isFile())
{
System.out.println("是文件");
}
long size=a.length(); //长度判断
System.out.println(size);

long s=a.lastModified(); //最后的修改时间获取和输出
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timestr =sdf.format(s);
System.out.println(timestr);
}
可读,可写,可执行?


4文件的操作
重命名 File.renameTo
public static void main(String[] args)
{
File a=new File("D:\\eclipse\\abc.txt");

if(a.isFile())
{
System.out.println("是文件");
}
a.renameTo(new File("D:\\eclipse\\abcd.txt"));
}

5.绝对路径与相对路径
相对路径
"../other/video.mp4"
"subdir/xy.doc"

绝对路径 即是完整路径
.getAbsolutePath();
"D:\\eclipse\\abc.txt"

12.2目录的操作
File类
if(f.exists)判断目录是否存在

if(f.isDirectory)
判断是否是目录


创建层级目录
public static void main(String[] args)
{
File d=new File("E:\\abc\\a\\b\\c");

d.mkdirs();
}
会创建没有的目录


遍历子项
使用listFile()可以遍历目录下的子目录/文件
仅扫描一级目录
public static void main(String[] args)
{
File d=new File("D:\\eclipse");

File[]subFiles=d.listFiles(); //设立一个数组subFiles存储

for(File f:subFiles)
{
if(f.isDirectory())
{
System.out.println("扫描到目录"+f);
}
else
{
System.out.println("扫描到文件"+f);
}

}

可以在listFilter的时候设置一个过滤器
File d=new File("D:\\eclipse");

FileFilter filter=new FileFilter() //过滤器fliter 创建一个匿名类
{

@Override
public boolean accept(File pathname)
{
String filePath=pathname.getAbsolutePath(); //创建一个字符创filePath获得他的绝对路径
if(filePath.endsWith(".rar")) //判断结尾是否是.rar .endsWith();
return true;
return false;
}
};



File[]subFiles=d.listFiles(filter); //设立一个数组subFiles存储,把filter过滤器参数传给他

for(File f:subFiles)
{
if(f.isDirectory())
{
System.out.println("扫描到目录"+f);
}
else
{
System.out.println("扫描到文件"+f);
}

}

3思考如何做递归查找,继续查找子目录


如何创建一个子目录
File homeDir =new File("e:/examples"); //先写出父目录
File f=new File(homeDir,"some.txt"); //再创建子目录

newFile时可以直接指定它的父目录.

File时个轻量级对象,不必转为String


12.3 相对路径
绝对路径从根目录开始指定.

相对路径,指定一个相对于当前目录的路径
File f=new File("src/my/Test.java");
当前目录
E:/JavaProjects/example1203

全路径
E:/JavaProjects/example1203/src/my/Test.java

相对路径示例
./data/config.txt
../example1101/src
../../WebProjects/
./src/../data/config.txt

.表示本目录
..表示父目录


工作目录
我们所说的当前路径,指的是程序的工作目录
默认的,工作目录时程序运行时的起始目录

注意: 工作目录不是class文件所在的目录
注意: 在Eclipse|Run Configuration 里可以指定 就是运行这个程序时的起始目录.

String workDir=System.getProperty("user.dir"); //查询当前的工作目录在哪

//修改工作目录
System.setProperty("user.dir","d:");


12.4 复制与移动
apache commons io
使用第三方的库来完成文件和目录操作

新建一个项目,添加commons-io-2.4.jar
(1)创建目录libs
(2)拷贝commons-io-2.4.jar到libs
(3)右键jar文件,选Add to Build Path


常用文件操作
FileUtils类支持的文件和目录
FileUtils.deleteQuietly()删除文件
FileUtils.copyFile()拷贝文件
FileUtils.copyFileToDirectory()复制文件到目录
FileUtils.moveFile()移动文件
FileUtils.moveFileDirectory()移动目录

//拷贝文件示例
public static void main(String[] args)
{
File src=new File("E:\\abc.txt");

File dst=new File("E:\\abc\\a.txt");

try
{
FileUtils.copyFile(src, dst);
}catch(IOException e)
{
e.printStackTrace();
}
System.out.println("完成");
}


第13章
13.1 文件的存储
文件:视频,音频,图片,文档,程序....

数据存储在文件中,关闭电脑后,数据不丢失.

无论是各种文件里面存储的都是byte[]字节数据

字符串的存储

String<=>byte[]

String=>byte[]

String text="abcdefg";
byte[] data=text.getBytes("UTF-8");

byte[]=>String
String text2=new String(data,"UTF-8");

写入文件 output写入
public static void main(String[] args)
{
File dir=new File("e:/examples");
dir.mkdirs(); //创建目录

File f=new File(dir,"123.txt");
String text="人人人1234aaaa";

try
{
FileOutputStream outputStream=new FileOutputStream(f);

byte[] data=text.getBytes("UTF-8"); //data把字符串编码成字节数据
outputStream.write(data); //将字节数据写入文件
outputStream.close(); //关闭文件
}catch(Exception e)
{
e.printStackTrace();
}

}

win10下面不能再分区根目录下面创建文件.


读取文件 input读取
public static void main(String[] args)
{
File dir=new File("e:/examples");
File f=new File(dir,"123.txt");

try {
FileInputStream inputStream=new FileInputStream(f);

int size=(int)f.length();
byte[]data=new byte[size]; //设立data[]的大小根据f.length
inputStream.read(data); //读取数据
inputStream.close();

//将读取来的数据转换成String
String text=new String(data,"UTF-8");
System.out.println(text);
}catch(Exception e)
{
e.printStackTrace();
}

}

13.2数据的格式化存储
整数,小数,布尔,字符串,日期....
统一的解决方法:把各项数据转换为String
写入
public static void main(String[] args)
{
Student stu=new Student(2018,"仁豪","1399009900");

String text="";
text+=("id:"+stu.id+",");
text+=("姓名:"+stu.name+",");
text+=("电话:"+stu.phone+",");

//存储到文件//
File f=new File("e:/examples/student.txt");
f.getParentFile().mkdirs();

try
{
FileOutputStream outputStream=new FileOutputStream(f);
byte[]data=text.getBytes("UTF-8");
outputStream.write(data);
outputStream.close();
}catch(Exception e)
{
e.printStackTrace();
}
System.out.println("exit");
}

读取 //没完全理解

public class ReadFile
{
public static String readTextFile(File f) //读取文档
{
try {
FileInputStream inputStream = new FileInputStream(f);

int size = (int) f.length();
byte[] data = new byte[size];
inputStream.read(data);
inputStream.close();

// 将读取来的数据转成String
String text = new String(data, "UTF-8");
return text;

}catch(Exception e)
{
e.printStackTrace();
}
return "";
}

public static HashMap parseText (String text) //HashMap
{
HashMap<String,String> values = new HashMap();
String[] abc = text.split(",");
for(String k : abc)
{
k = k.trim();
if(k.length() == 0) continue;

String[] nv = k.split(":");
String name = nv[0];
String value = nv[1];
values.put(name, value);
}
return values;
}


public static void main(String[] args)
{
File dir = new File("e:/examples");
File f = new File(dir, "student.txt");

// 从文件读出字符串
String text = readTextFile(f);

// 将字符串解析为 key-value
HashMap<String,String> values = parseText(text);

// 提取出Student信息
Student stu = new Student();
stu.id = Integer.valueOf( values.get("id"));
stu.name = values.get("name");
stu.phone = values.get("phone");

System.out.println("exit");

}

}

13.3
XML Extensible Markup Language
可扩展标记语言 (类似HTML), 一种用文本化表示的数据格式
特点:简单,使用广泛.

一个Student对象的存储

<?xml version="1.0" encoding="UTF-8"?> //示例1
<student>
<id> 20180001</id>
<name> 邵发 </name>
<sex> true </sex>
<cellphone> 13810012345</cellphone>
</student>

version 表示版本号
- 第一行是固定不变的,叫XML的声明
- 后面内容主体是一个树状层次的元素节点树。
-每个元素成对出现都是闭合的,以<XXX>开始,以</XXX>结束.

<?xml version="1.0" encoding="UTF-8"?> //示例2
<student>
<id> 20180001</id>
<name> 邵发 </name>
<sex> true </sex>
<cellphone> 13810012345</cellphone>

<score>
<chinese> 99 </chinese>
<math> 98 </math>
<english> 97 </english>
</score>
</student>


<?xml version="1.0" encoding="UTF-8"?> //示例3
<data>
<student> //同级元素可重名,可以保存数组类型.
<id> 20180001</id>
<name> shao </name>
<sex> true </sex>
<cellphone> 13810012345</cellphone>
</student>

<student>
<id> 20180002</id>
<name> wang </name>
<sex> true </sex>
<cellphone> 13822244452</cellphone>
</student>

</data>

13.3.2 创建XML文件
1.用notepad++创建
直接创建xml后缀的文件打开,把编码改为utf8格式编码.


2在eclipse中创建XML
右键project new file
创建.xml
右键properties other UTF-8

XML教程3:dom4j

在dom4j中,定义了以下几个术语:
Document : 指整个XML文档
Element : 指元素
Element Text : 指元素的值
Element Attribute : 元素的属性 ( 后面介绍)
如何创建和添加元素
public class MyTest
{
public static void haha() throws Exception
{
// 创建一个空的Document
Document x_doc = DocumentHelper.createDocument();

// 添加根元素: <root>
Element x_root = x_doc.addElement( "root" );

// 添加两个子元素: <server>, <port>
Element e1 = x_root.addElement( "server" ) .addText( "www.afanihao.cn" );
Element e2 = x_root.addElement( "port" ) .addText( String.valueOf(80));

Element e3 = x_root.addElement("xxxx");
e3.addText("你好");

// 输出到文件
File xmlFile = new File("output.xml");
OutputStream outputStream = new FileOutputStream(xmlFile);
try {
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8"); // 字符编码为UTF-8
XMLWriter writer = new XMLWriter(outputStream , format);
writer.write( x_doc ); // 把xml文档输出到文件里
writer.close();
}finally
{
// 确保文件句柄被关闭
try { outputStream.close();}catch(Exception e) {}
}
}

public static void main(String[] args)
{
try
{
haha();
} catch (Exception e)
{
e.printStackTrace();
}
System.out.println("hahha exit");
}
}

13.3.4生成XML

public static void writeXML( Student s, File xmlFile) throws Exception
{
// 创建一个空的Document
Document x_doc = DocumentHelper.createDocument();

// 添加根元素: <root>
Element x_root = x_doc.addElement( "root" );

// 添加两个子元素: <server>, <port>
x_root.addElement( "id" ) .addText(String.valueOf(s.id));
x_root.addElement( "name" ) .addText( s.name);
x_root.addElement( "sex" ) .addText( s.sex ? "男" : "女");
// 男 male , 女 female 三目运算符b?x:y
//先计算条件b,然后进行判断。如果b的值为true,计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值。
x_root.addElement( "cellphone" ) .addText(s.cellphone);

// 输出到文件
OutputStream outputStream = new FileOutputStream(xmlFile);
try {
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8"); // 字符编码为UTF-8
XMLWriter writer = new XMLWriter(outputStream , format);
writer.write( x_doc ); // 把xml文档输出到文件里
writer.close();
}finally
{
// 确保文件句柄被关闭
try
{
outputStream.close();
}
catch(Exception e)
{

}
}
}

public static void main(String[] args)
{
try
{
Student s = new Student(20180001, "邵", true, "13810012345");
writeXML ( s , new File("output1.xml"));

} catch (Exception e)
{
e.printStackTrace();
}
System.out.println("exit");
}

添加数组元素
public static void writeXML( List<Student> sss, File xmlFile) throws Exception
{
// 创建一个空的Document
Document x_doc = DocumentHelper.createDocument();

// 添加根元素: <root>
Element x_root = x_doc.addElement( "root" );

Element x_student_list = x_root.addElement( "student_list" );

for( Student s:sss )
{
Element x_student = x_student_list.addElement( "student" );
x_student.addElement( "id" ) .addText( String.valueOf(s.id));
x_student.addElement( "name" ) .addText( s.name);
x_student.addElement( "sex" ) .addText( s.sex ? "male" : "female"); // 男 male , 女 female
x_student.addElement( "cellphone" ) .addText(s.cellphone);
}


// 输出到文件
OutputStream outputStream = new FileOutputStream(xmlFile);
try {
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8"); // 字符编码为UTF-8
XMLWriter writer = new XMLWriter(outputStream , format);
writer.write( x_doc ); // 把xml文档输出到文件里
writer.close();
}finally
{
// 确保文件句柄被关闭
try { outputStream.close();}catch(Exception e) {}
}
}

public static void main(String[] args)
{
try
{
List<Student> sss = new ArrayList<>();

sss.add( new Student(20180001, "shao", true, "13810012345") );
sss.add( new Student(20180002, "wang", true, "17489938290") );
sss.add( new Student(20180003, "li", false, "19982998898") );

writeXML ( sss , new File("output3.xml"));

} catch (Exception e)
{
e.printStackTrace();
}
System.out.println("exit");
}

解析数据
public static Student readXML (File xmlFile) throws Exception
{
FileInputStream inputStream = new FileInputStream(xmlFile);
SAXReader xmlReader = new SAXReader(); // SAXReader 用于解析XML
Document x_doc = xmlReader.read(inputStream);// 得到一个Document对象
inputStream.close(); // 得到Document后即可关闭文件

Element x_root = x_doc.getRootElement();
Student s = new Student();
s.id = Integer.valueOf( x_root.element("id").getText());
s.name = x_root.element("name").getText().trim(); //.trim()的作用是清除两边的空格.
String sex = x_root.elementText("sex");
s.sex = sex.equals("male");
s.cellphone = x_root.elementText("cellphone");
return s;
}

public static void main(String[] args)
{
File xmlFile = new File("data1.xml");
try
{
Student s = readXML (xmlFile);
System.out.println("read complete");
} catch (Exception e)
{

e.printStackTrace();
}


System.out.println("exit");
}


(1)取值时要注意两边的空白
String name = x_root.elementText ("name").trim();

(2)由于XML里只存储文本,所以在读取Text之后,需要自己转成int, boolean 或其他类型的变量。
例如,
int id = Integer.valueOf( x_root.element("id").getText());

(3)取值时一般要注意 null 的判断
Element sex = x_root.element("sex");
if ( sex != null)
{
}

(4)注意关闭文件句柄 (高级话题)
在Java程序里,所有的文件FileInputStream/FileOutputStream在用完之后,都得确保close掉。否则会引起句柄泄露。

public static Document parseXmlDocument(File xmlFile) throws Exception
{
FileInputStream inputStream = new FileInputStream(xmlFile);
try {
SAXReader xmlReader = new SAXReader();
Document doc = xmlReader.read(inputStream);
return doc;
}finally {
// 确保关闭文件句柄
try{ inputStream.close(); }catch(Exception e) {}
}
}


假设从<parent>元素下查找子元素<id>的值,则有3种写法:

第一种写法:
Element e = parent.element("id");
String text = e.getText();

第二种写法:
String text = parent.element("id").getText();

第三种写法:
String text = parent.elementText("id");

XML教程6
元素的属性

使用第三方库dom4j来操作XML


先前我们已经学到的,如果用XML来表示一个学生的信息,可以表示为:
<student>
<id> 20180001</id>
<name> 邵发 </name>
<sex> true </sex>
<cellphone> 13810012345</cellphone>
</student>

也可以表示为:
<student id="20180001" >
<name> 邵发 </name>
<sex> true </sex>
<cellphone> 13810012345</cellphone>
</student>

其中,id作为元素<student>的属性出现。属性的名字为id,值为"20180001",必须使用双引号。

也可以表示为:
<student id="20180001" name="邵发" sex="true" cellphone="13810012345">
</student>
其中,<student>元素有4个属性。

由于<student>元素没有Text内容,所以可以简写为:
<student id="20180001" name="邵发" sex="true" cellphone="13810012345" />


添加元素


Element x_student = x_root.addElement("student");
x_student.addAttribute("id", String.valueOf(s.id));
x_student.addAttribute( "name" , s.name);
x_student.addAttribute( "sex" , s.sex ? "male" : "female");
x_student.addAttribute( "cellphone", s.cellphone);
也可以连在一起写,
Element x_student = x_root.addElement("student")
.addAttribute("id", String.valueOf(s.id))
.addAttribute( "name" , s.name)
.addAttribute( "sex" , s.sex ? "male" : "female")
.addAttribute( "cellphone", s.cellphone);

之所以可以连在一起写,是因为addAttribute() 的返回值仍然是当前的Element对象。


读取元素
public static Student readXML (File xmlFile) throws Exception
{
FileInputStream inputStream = new FileInputStream(xmlFile);
SAXReader xmlReader = new SAXReader(); // SAXReader 用于解析XML
Document x_doc = xmlReader.read(inputStream);// 得到一个Document对象
inputStream.close(); // 得到Document后即可关闭文件

Element x_root = x_doc.getRootElement();

Element x_student = x_root.element("student");


Student s = new Student();
s.id = Integer.valueOf( x_student.attributeValue("id").trim());
s.name = x_student.attributeValue("name").trim();
String sex = x_student.attributeValue("sex").trim();
s.sex = sex.equals("male");
s.cellphone = x_student.attributeValue("cellphone").trim();
return s;
}

public static void main(String[] args)
{
File xmlFile = new File("student.xml");
try
{
Student s = readXML (xmlFile);
System.out.println("read complete");
} catch (Exception e)
{

e.printStackTrace();
}


System.out.println("exit");
}


.addAttribute输入
.attributeValue读取


常用工具类.AfXml

13.4 JSON
XML多用于配置文件,JSON多用于数据传输
{
"id":20111111, //示例
"name":"仁豪",
"phone":"1300099";
}
使用JSON-ORG 或者jsonlib来操作JSON

其中,大括号表示一个JSON对象。在这个对象里,包含4个字段。
例如,
"id" 字段的值为 20180001,是数字类型 ( Number )
"name" 字段的值为 "邵", 是字符串类型 ( String )
"sex" 字段的值为 true,是布尔类型 (Boolean)

可以很直观的发现,
如果值是一个String,则应该用双引号,如"邵发"
如果值是一个Number,则不加双引号,如20180001
如果值是一个Boolean,则应该是 true 或者是 false, 不加双引号

加入JSON-java JSON-lib

JSONObject jobj = new JSONObject();
jobj.put("id", 20180001); // Integer
jobj.put("name", "邵发"); // String
jobj.put("sex", true); // Boolean
jobj.put("cellphone", "13810012345"); // String

String jsonstr = jobj.toString(2);
System.out.println(jsonstr);


JSONObject里的字段显示顺序是无关的,谁先上、谁在下不影响最终结果。


JSON的语法格式
用JSON可以表示Object信息,以一对大括号包围,里面可以多个字段。
例如:
{
"name": "邵发",
"id": 1208439,
"sex": true,
"phone": "13810012345"
}
语法要点:
- 以大括号包围
- 字段名称要加双引号
- 字段的值可以为String, Integer, Boolean, Array, null 等类型
- 每个字段以逗号分隔 (最后一个字段末尾不能加逗号)
- 字段的顺序无关
注:JSONObject在概念上对应于Java里的Map

JSON Array
例如
[
{
"name": "shao",
"id": 2018001,
"phone": "13810012345",
"sex": true
},
{
"name": "wang",
"id": 2018002,
"phone": "13810043245",
"sex": true
},
{
"name": "li",
"id": 2018003,
"phone": "1345015445",
"sex": true
}
]
语法要点:
- 以中括号包围
- 元素类型可以是任意类型。例如,元素类型可以是String,也可以是Object

注:JSONObject在概念上对应于Java里的数组


嵌套
Object的字段类型可以是Array
例如
{
"classId": 201801,
"className": "计科1801",
"members":
[
"shaofa",
"wang",
"li"
]
}

也就是说,Object 和 Array 是可以互相嵌套的。


JSON的生成与解析
JSON的生成
public class CreateJSON
{
// 生成 JSONObject
public static void test1()
{
JSONObject j1 = new JSONObject();
j1.put("name", "邵发");
j1.put("id", 1239349);
j1.put("sex", true);
j1.put("phone", "13810012345");
String jsonstr = j1.toString(2); // 缩进2
System.out.println(jsonstr);
}

// 生成JSONArray : 元素是字符串
public static void test2()
{
JSONArray j = new JSONArray();
j.put("shao");
j.put("wang");
j.put("li");
j.put("chen");
String jsonstr = j.toString(2); // 缩进2
System.out.println(jsonstr);
}

// POJO -> JSONObject
// 如果是POJO对象 ( 已经添加了 getter ),则可以直接转成JSONObject
public static void test3()
{
Student stu = new Student("邵发", 1238909, true, "13810012345");

JSONObject j = new JSONObject(stu);
String jsonstr = j.toString(2); // 缩进2
System.out.println(jsonstr);
}

// List -> JSONArray
public static void test4()
{
List<Student> sss = new ArrayList<Student>();
sss.add(new Student("shao", 1238901, true, "13810012345"));
sss.add(new Student("wang", 1238902, true, "13456678895"));
sss.add(new Student("qian", 1238903, false, "1381432435"));
sss.add(new Student("chen", 1238904, true, "13342353446"));

JSONArray jarray = new JSONArray();
for( Student s : sss)
{
JSONObject j1 = new JSONObject(s);
jarray.put( j1 );
}

String jsonstr = jarray.toString(2); // 缩进2
System.out.println(jsonstr);
}

// List -> JSONArray
public static void test5()
{
List<Student> sss = new ArrayList();
sss.add(new Student("shao", 1238901, true, "13810012345"));
sss.add(new Student("wang", 1238902, true, "13456678895"));
sss.add(new Student("qian", 1238903, false, "1381432435"));
sss.add(new Student("chen", 1238904, true, "13342353446"));

// 直接把ArrayList转成JSON,前提是ArrayList里的元素是可以转化的
JSONArray jarray = new JSONArray(sss);

String jsonstr = jarray.toString(2); // 缩进2
System.out.println(jsonstr);
}

JSON存储到文件13.4_05网盘

json.org库文件


13.5 Properties
另外一种配置文件
#开头是注释
port=80
server=127.0.0.1
*.properties文件的格式;
1.#开头是注释行
2.key=value

显然*.properties里的key不允许重复.

不适合保存树状形式的数据

public class Config
{
public String server;
public int port;


public void load(File f) throws Exception
{
Properties props = new Properties();

// 从 *.properties 文件加载数据
InputStream inputStream = new FileInputStream(f);
try {
props.load( new FileInputStream(f));

}finally
{
inputStream.close();
}

// 获取 Property 配置
server = props.getProperty("server");
port = Integer.valueOf( props.getProperty("port", "80"));



}

public void save (File f) throws Exception
{
Properties props = new Properties();
props.put("server", server);
props.put("port", String.valueOf(port));

// 存储到 *.properties 文件
OutputStream outputStream = new FileOutputStream(f);
try {
props.store(outputStream, "just test");
}finally {
outputStream.close();
}
}
}

java.util.Properties
Properties工具类,可以存储多个key-value
-getProperties/-setProperties
-load/store 读取文件/写入文件


第14章 反射机制
java.lang.Class
它是Java的基础类,用于描述一个class对象.
在文件系统中,class以文件的形式存在Student.class在运行时的JVM(Java虚拟机)中,
该*.class文件被加载到内存中成为一个对象,对象的类型是java.lang.Class

Class cls = Student.class;
System.out.println("Name: " + cls.getName());
其中,cls这个对象就是这个Class的描述。

Student obj = new Student();
Class cls = obj.getClass();
其中,obj是一个对象,obj.getClass()则是获取它的Class 描述。
Class有什么用
用于判断一个运行时对象的类型
public static void test1(Object obj)
{
Class cls = Student.class;
if(cls.isInstance(obj))
{
// 判断obj是不是Student类型
System.out.println("is an instance of Student");
}
else
{
System.out.println("不是 an instance of Student");
}
}
其中,cls.Instance(obj)意思是判断obj是否为my.Student 的一个实例。

另一种写法,也可以判断一个运行时对象的类型
public static void test2(Object obj)
{
String clsName = obj.getClass().getName();
if(clsName.equals("my.Student"))
{
System.out.println("is an instance of Student");
}
else
{
System.out.println("不是 an instance of Student");
}
}

比较S1和s2
Student s1 = new Student(123, "shaofa", "199900000");
Student s2 = new Student(124, "shaofa", "199900000");
if(s1.equals(s2))
{
System.out.println("equal");
}
else
{
System.out.println("not equal");
}

public boolean equals(Object obj)
{
// 与一个Student对象比较
if(this.getClass().isInstance(obj))
{
Student other = (Student) obj;
return other.id == this.id;
}

// 与一个String对象比较
if(String.class.isInstance(obj))
{
String other = (String)obj;
return other.equals(this.name);
}

// 与一个Integer对象比较
if(Integer.class.isInstance(obj))
{
Integer other = (Integer)obj;
return this.id == other;
}

return false;
}

14.2反射
Reflection
给定一个*.class文件中,我们可以得到以下信息:
类名 (含package路径)
函数 (名称,参数类型,返回值)
域 (名称,类型)
实现的接口 (interfaces) ……

使用java.lang.reflect.*下的类来实现。。。

注意:不需要源文件,只需要*.class文件

public static Class loadClass() throws Exception
{
// 加载my/Student.class
Class cls = Class.forName("my.Student");

// 获取函数列表
Method[] methods = cls.getMethods();

// 获取成员变量列表
Field[] fields = cls.getFields();

for(Method m : methods)
{
System.out.println("函数: " + m.toString());
}

return cls;
}

public static void main(String[] args)
{
try
{
// 得到Class
Class cls = loadClass();

//test2();

}catch(Exception e)
{
e.printStackTrace();
}
}


也就是说,虽然我们不知道Student类的代码,但是这个 class文件本身可以反映(reflect)出这些信息。。。
结论:通过Reflection机制,我们可以直接 从class文件反推出它有哪个成员变量、有哪 些函数


遍历Method
已经函数名,找到对象的
// 从Class中获取Method : 按名称查找 (假设没有重名的函数)
public static void findMethod(Class cls) throws Exception
{
String methodName = "setId";

// 获取所有Method列表,顺序比对
Method[] methods = cls.getMethods();
for(Method m : methods) //遍历
{
if(m.getName().equals(methodName)) //比较
{
break;
}
}

}

例2 查找Method
已经函数名,参数列表,寻找Method
public static void findMethod2(Class cls) throws Exception
{
String methodName = "setId";
Class[] parameterTypes = { int.class };
Method m = cls.getMethod(methodName, parameterTypes);

System.out.println("got the method");
}

例3 Reflection的运用

// 一个完整的reflection测试
public static void test1() throws Exception
{
// 加载my/Student.class
Class cls = Class.forName("my.Student");

// 创建一个实例, 要求有一个不带参数的构造函数
Object obj = cls.newInstance();

// 找到method
Class[] parameterTypes = { int.class };
Method m1 = cls.getMethod("setId", parameterTypes); //"setId"参数是函数名,parameterTypes是函数值类型.

// 调用method
Object[] paramters = { 123 };
m1.invoke(obj, paramters);

// 显示结果
System.out.println("result: " + obj.toString());
}
public static void main(String[] args)
{
try
{
// 得到Class
Class cls = loadClass();
test1();

}catch(Exception e)
{
e.printStackTrace();
}
}

Class.forName(…) 可以加载一个class文件。。
(1)由谁负责加载? 由Class Loader负责加载,具体地讲,JVM提供了一 个内置的 bootstrap class loader。

(2)从哪里加载? 从classpath下寻找class,以及该class里面import 的class

小结 1.Reflection: 从*.class文件中,可以reflect得到它 的描述信息:类名,函数名等
2. 通过Class,可以创建一个实例instance
3. 通过Class,可以找到它的某个Method,进而就 可以调用这个函数。
4. 需要知道Class Loader的存在及其作用

反射机制
Reflection 应用框架
一般地,当设计一个应用框架时才有可能用 到Reflection技术。

例如,著名的Struts Spring Hibernate框架

AfShell: Command Ui Framework
AfShell: 一个自定义的Framework,使用这个 Framework可以很方便的写出基于命令行界面的应用 程序。

添加AfShell框架支持
(1)加入afshell.jar到BuildPath
(2)添加afshell.properties
(3)在main中调用 AfShell.start() 完成!

AfShell: Command Ui Framework
添加命令处理
(1)自定义Action类 此类必须有一个函数public int execute()

(2)配置afshell.properties login=my.LoginAction

AfShell: Command Ui Framework
添加命令行参数的支持 例如,用户输入 login username=shaofa password=123456

(1)添加成员变量 username password
(2)添加setter setUsername() setPassword() 则AfShell框架会自动的把参数值传进来,然后再调用 execute()函数。。。


设计方法
那么,这样的一个Framework该如何实现? 例如,用户输入了 login username=shaofa password=123456

(1)得到命令名 "login"
(2)通过配置文件,找到my.LoginAction
(3)加载my.LoginAction,创建一个实例
(4)根据username=shaofa,找到setter函数setUsername, 调用此函数
(5)根据password=123456,找到setter函数并调用
(6)调用excute()函数,得到返回值 完成!

小结 介绍一个应用框架AfShell的使用方法
如果要使用这个框架,并不需要知道Reflection技术
如果自己设计一个框架给别人使用,则需要精确掌 握和运用Reflection技术。
请试着使用Reflection技术,自己来实现这个框架。

反射4

(1)得到命令名 "login"
(2)通过配置文件,找到my.LoginAction
(3)加载my.LoginAction,创建一个实例
(4)根据username=shaofa,找到setter函数setUsername, 调用此函数
(5)根据password=123456,找到setter函数并调用
(6)调用excute()函数,得到返回值 完成!

了解AfShell框架的架构方法.

15.1线程
:你手下管理着两名员工,一个是Confucian,一 个是Buddhist,如何让他们同时工作?

Confucian e1 = new Confucian(); e1.doJob();

Buddhist e2 = new Buddhist(); e2.doJob();

我们发现,只有e1先工作完成了,才能开始e2的工作, 两者无法“同时”进行。。。

线程 Thread
引入线程机制,可以并行地做多件事情。。。
继承于Thread类,重写run(),填上它要做的工作。。 extends Thread

public class Buddhist extends Thread
{
@Override
public void run()
{
for(int i=1; i<=500; i++)
{
System.out.println("ma mi ma mi hong ..." + i);

try
{
Thread.sleep(100);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args)
{
Confucian e1 = new Confucian();
e1.start();
}

线程 Thread
启动线程 Confucian e1 = new Confucian(); e1.start();

Buddhist e2 = new Buddhist(); e2.start();

启动一个线程 start()

线程主函数 run() : 描述了这个线程的工作任务

线程的调度
CPU只有一个,而系统中同时运行的进程有几十 个。每个进程又同时运行n个线程。忙得过来?


时间片划分:将1秒钟划分为N个小的时间片,大家 轮流运行。

比如,以5ms为一个时间片,每个线程运行5ms之后 就轮到下一个线程运行。

这样,所有的线程都有机会被运行,由于切换的速 度很快,给用户的感觉是:它们好像同时在运行.

Sleep暂歇

线程可以执行Sleep操作,用于告诉操作系统: 我要 休息一会,接下来的一段时间内不要管我。。。

例如, Thread.sleep(10000); 表示接下来将休息10秒钟,在这10秒钟内该线程主 动放弃CPU的使用权。。。

排队机制

假设线程①休息了10秒之后,那么醒来之后就能立 即获得CPU的使用权吗?不能。醒来之后要排队!

系统中有一个队列,所有需要CPU的线程在这里排队。

操作系统根据特定的算法,来决定下一个是谁。 (比始说,有的线程优先级较高,而有的较低)

使用sleep

当创建线程时,sleep是一个经常要使用的函数 原则:尽可能少的占用CPU,让别的线程也有机制运行。

试想,如果一个线程里是这样的:
while(true)
{
System.out.println("我就赖着CPU。。。");
}
那其他的线程会高兴吗?

虽然我们看到在任务管理器下很多线程,但在同一时刻,大多数线程都在sleep

引入线程的概念 (多条线并行发展) 介绍了线程调度的机制,由操作系统负责调度 介绍了sleep的作用,及其重要意义

15.2 线程的创建
第一种方法继承Thread
extends Thread
重写run方法,在主函数入口用start()启动.

第二章方法:实现Runnable接口
public class Buddhist implements Runnable
{

@Override
public void run()
{
for(int i=1;i<=500;i++)
{
System.out.println("emmmmmmmm.");
try
{
Thread.sleep(100);
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
}

}
public static void main(String[] args)
{
Buddhist e2 = new Buddhist();
Thread t=new Thread(e2);
t.start();
}

这是因为java不允许多重继承,如果你的类已经继承于别的类,又要用线程来运行,则使用这种方法.

有时候使用匿名类更方便一些
Thread t=new Thread(){
public void run()
{
}
};
t.start();


public static void main(String[] args)
{


Thread t2=new Thread() {

@Override
public void run()
{
for(int i=1;i<=500;i++)
{
System.out.println("emmmmmmmm.");
try
{
Thread.sleep(100);
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
};
t2.start();
}


15.3线程
线程的终止
当一个线程的start()调用后,线程为Alive状态。 当一个线程的主函数run()退出后,线程死亡(Dead)。
public void run()
{
for(int i=1; i<=10; i++)
{
System.out.println("..." + i);
}
}
该线程打印10句后终止。
(可以类比一下主线程,当main()中退出时,主程序终止。。。)

让一个线程终止,就是要想办法让它从run()中退出。 例如,设置一个标识变量,
public boolean quitflag = false;
public void run()
{
for(int i=1; i<=10; i++)
{
if(quitflag) break; // 退出循环
System.out.println("..." + i);
}
}


例子
public class Confucian extends Thread
{
public boolean quitflag = false;

@Override
public void run()
{
for(int i=1; i<=10; i++)
{
if(quitflag) break;

System.out.println("人之初,性本善 ..." + i);

try
{
Thread.sleep(1000);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}

System.out.println("Confucian exit.");
}
}

public class Test1
{

public static void main(String[] args)
{
// 创建并启动线程
Confucian t1 = new Confucian();
t1.start();

// 按回车后,中止线程,然后退出程序
InputStreamReader m = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(m);
try{
reader.readLine(); //由这句来读回车
reader.close();

// 控制线程的退出
t1.quitflag = true;
}
catch(Exception e)
{
}

}

}

sleep与interrupt
背景:Buddhist中每5秒输出一次,sleep的时间很长,如何让它立即退出??

方法: 调用 t2.interrupt()来中断目标线程。打断线程的sleep
则sleep()函数将抛出异常,结束sleep状态

public static void main(String[] args)
{
// 创建并启动线程

Buddhist t2 = new Buddhist();
t2.start();

// 按回车后,中止线程,然后退出程序
InputStreamReader m = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(m);
try{
reader.readLine();
reader.close();

// 控制线程的退出
t2.quitflag = true;
t2.interrupt(); //打断sleep
}
catch(Exception e)
{
}

等待线程的退出
Buddhist t3 = new Buddhist(); t3.start();

// 等待t4退出 t3.join();

join() 函数会阻塞住当前线程,等待目标线程退出后, 再继续执行。。。

注:在Java里, join()只起等待作用(线程死亡后不需 要手工回收,由JVM自动回收)

public static void main(String[] args)
{
// 创建并启动线程
Buddhist t3 = new Buddhist();
t3.start();

// 等待t3退出
try
{
t3.join(); //阻塞住当前线程,等待目标线程退出后,再继续执行.
} catch (InterruptedException e)
{
e.printStackTrace();
}

// 在t3线程完成之后,再执行下面的事情
for(int i=0; i<3; i++)
{
System.out.println("我自己的事情");
}

System.out.println("program exit");
}

注意事项
本质上,线程只有一种退出方式:从run()函数自然退出。 我们需要通过程序设计,控制它从run()中退出。

不能 stop() ,不能destroy()… deprecated

很多初学者会有一个想法:有没有简单暴力的手段, 直接杀死一个线程?
比如kill() ? 千万不要这么想! (正在使用ATM存钱的时候,断电了可以吗?)

小结
线程只有一种终止方式:从run()中退出。

介绍如何控制一个线程的退出。

15.4
引例
密钥Key :一个16字节的数组
密钥更新线程KeyUpdater:定期更新密钥
密钥获取线程KeyPrinter:打印显示密钥


密钥的完整性:密钥的16字节必须相同。

例如,
下面是有效密钥 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A
下面是无效密钥 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1B 1B 1B

密钥更新时应保证密钥的完整性。

什么样的情况算是出错?KeyPrinter获取密钥,发现密钥 不完整(16个字节不全相同),则认为此密钥无效!

分析代码,推断有没有可能出错。。。 运行代码,看看没有出错。。。

构造出错条件:假设 key.update()需要10ms 才能更新完成。。。

线程的同步机制
两个线程同时访问一个对象时,可能发生数据不同步的现象。

数据不同步:
比如,线程KeyUpdater正在更新密钥。。。
0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A xx xx xx xx 当更新过程完成了75%的时候。。。
另一个线程KeyPrinter要求获取当前密钥。。。
它拿走了密 钥,发现它是一个不完整的密钥。。。

出错

为了实现多线程对同一对象的同步访问,引入互斥锁的 概念。
synchronized (lock) // 申请锁
{ // 上锁 locked
… 关键代码 … } // 解锁 unlocked
(1)若别的线程正持有该锁(locked),则本线程阻塞等待
(2)若此锁空闲(unlocked),则本线程持有锁(locked), 进入大括号执行,完毕之后释放锁(unlocked)。

synchronized (key)
{
seed ++;
if(seed > 100) seed = 0;
key.update(seed);
}

形象理解: 好比一间房子,如果是空的,就能进入使用里面的资源, 并在里面把门反锁,此时别人无法进入。
(保证了资源的独 占使用)。此时有人想用房子,就得门口等着。。。

synchronized ( lock ) // 等待进入
{ // 进入,从里面反锁

… 使用资源res …

} // 出来

synchronized ( lock )
在Java里, synchronized (lock) 括号中的锁可以是任意 对象,甚至是数组对象。

比如,
Object obj = new Object();
synchronized (obj) { }
byte[] data = new byte[];
synchronized (data) { }


为了增强可读性,可以直接把要访问的那个对象当成锁, 类似下面这样:
synchronized (key)
{
seed ++;
if(seed > 100) seed = 0;
key.update(seed);
}
则可以一目了然地表明:大括号中要锁定的资源是key 对象。

介绍了线程同步的概念:多个线程访问同一对象时, 一个读,一个写,则容易发生不同步的现象。

线程同步:就是让两个线程步调一致,有序地访问 的访问同一对象。

synchronized代码块用于同步访问。


写在内部此时,外部就可以放心地调用update和get,不必担心不同步的问题。
public void update(byte seed)
{
synchronized (this)
{
for(int i=0; i<data.length; i++)
{
data[i] = seed;
}

}
}

// 获取密钥
public synchronized byte[] get()
{

synchronized (this)
{
// 复制一份传出去
byte[] copy = new byte[data.length];
System.arraycopy(data, 0, copy, 0, data.length);
return copy;
}
}


由于要锁定的资源对象就是自己,所以也可以使用 synchronized (this) { }


进一步简化的写法 public synchronized void update() { }

相当于 public void update()
{
synchronized (this)
{
}
}
如果函数内的所有语句都被synchronized括起来,则可以这么简写。


死锁 deadlock
死锁:因为不恰当地使用锁,导致线程锁死。

第一种情况: synchronized (lock)
{
synchronized (lock)
{
}
}
自己就在房子里,另一个自己在房子外面敲门

死锁 deadlock
第二种情况:两个线程互相等待。。。
线程①
synchronized (lock1)
{
synchronized (lock2)
{
}
}
线程②
synchronized (lock2)
{
synchronized (lock1)
{
}
}

大括内的代码尽可能快地完成,以提高多线程的运行效率。
(让别人少等一会)
synchronized (lock)
{
}
建议:把不影响共享资源访问的耗时操作都移出去


引例
鸡蛋Egg 篮子ArrayList<Egg>
母鸡Hen : 每隔一段时间产出一个鸡蛋,放入篮子
男孩Boy : 负责将篮子里的鸡蛋取出来吃掉

要求:确保篮子里的鸡蛋被及时取走。 (如果取走不及时,鸡蛋就会堆积在篮子里。。。)

生产者 – 消费者 模型

由于Boy不确定母鸡什么时候会产出鸡蛋,只好频繁的 去检查篮子里有没有鸡蛋。有则取出,没有则空跑一趟。

轮询机制
假设母鸡每5-10秒会随机产出一个鸡蛋
则小明为了确保能及时取走鸡蛋,可以每隔1秒去检查 一次。。。这样,
虽然会空跑很多次,但能够完成目标。
这种实现机制称为:轮询机制。(反复查询) 特点:效率低,但实现起来简单。


通知机制
为了提升小明的效率,不让小明空跑,则可以使用通知机制。
小明等待通知 basket.wait … 母鸡在产出鸡蛋后,立即通知小明来取 basket.notify …
这样,小明就能够在第一时间取走鸡蛋。
这种实现机制称为:通知机制 特点:效率高,但实现起来复杂


wait / notify
wait/notify 使用注意事项: (1)必须结合synchronized使用 (2)synchronized对象和wait/notify的对象必须是同一 个

这是Java特定的机制,必须这么使用(官方说法是,在 wait/notify之前,必须先成为该对象的监视者Monitor)

当然,也可以使用别的方法来实现通知机制。但这套机 制是Java自带的。

notify / notifyAll
notify : 通知一个线程 如果有很多线程都在wait,则由系统选择一个 notifyAll : 通知所有线程

wait / notify
wait / notify 是Object类的方法

发出通知
synchronized (basket)
{
basket.add(egg);
basket.notify();
}
等待通知
synchronized (basket)
{
try
{
basket.wait();
}
catch (InterruptedException e)
{
}
if(basket.size() > 0)
egg = basket.remove(0);
}

实例代码
public class Test2
{
// 鸡蛋
class Egg
{
int id; // 每个鸡蛋有个编号
public Egg(int id)
{
this.id = id;
}
public String toString()
{
return "Egg("+id+")";
}
}

// 母鸡 (生产者)
class Hen extends Thread
{
ArrayList<Egg> basket ;

public Hen(ArrayList<Egg> basket)
{
this.basket = basket;
}

public void run()
{
Random r = new Random();
int count = 0;

while(true)
{
// 生产一个蛋,放在篮子里
Egg egg = new Egg(++count);
System.out.println("产出: " + egg);

synchronized (basket)
{
basket.add(egg);
basket.notify(); //通知,notify和wait的对象必须是统一的对象.
}

// 休息一段时间: 1~5秒 (随机)
int interval = 1 + r.nextInt(4);
try
{
Thread.sleep(interval * 1000);
} catch (InterruptedException e1)
{
}
}
}
}

// 小孩 (消费者)
class Boy extends Thread
{
ArrayList<Egg> basket ;

public Boy(ArrayList<Egg> basket)
{
this.basket = basket;
}

public void run()
{
while(true)
{
// 查看篮子里有没有鸡蛋
Egg egg = null;
synchronized (basket)
{
try
{
basket.wait(); //等待通知,阻塞线程.notify和wait的对象必须是统一的对象.
} catch (InterruptedException e)
{
}

if(basket.size() > 0)
{
// 从篮子里取出鸡蛋
egg = basket.remove(0);
}
}

if(egg != null)
{
// 吃掉这个鸡蛋
System.out.println(" 吃掉 :" + egg);
}

}
}
}


public void test()
{
ArrayList<Egg> basket = new ArrayList<Egg>();
Hen xiaoji = new Hen(basket);
Boy xiaoming = new Boy(basket);

xiaoji.start();
xiaoming.start();
}

public static void main(String[] args)
{
Test2 t = new Test2();
t.test();
}

}


注解Annotation
@开头的都是注解
@Deprecated 意思是“废弃的,过时的”

@Override 意思是“重写、覆盖”

@SuppressWarnings 意思是“压缩警告”

注解和注释的区别
//Comment是给程序员看的,不会被编译到class文件
注解@是给编译器和IDE看的

猜你喜欢

转载自www.cnblogs.com/cqbstyx/p/10248641.html