Java编程基础
1.基本语法
大小写敏感:Java时大小写敏感的,区分大小写
类名:类名的首字母应该大写,且每个单词的首字母都应该大写,如 MyJava
方法名:所有的方法名应该以小写字母开头,其后的每个单词首字母大写
源文件名:源文件名必须和类名相同。不相同会导致编译错误。
主方法入口:所有的Java程序由public static void main(String[] args)方法开始执行
2.关键字
类别 关键字 说明
访问控制 private 私有的
protected 受保护的
public 公共的
default 默认
abstract 抽象
class 类
类、方法 extends 继承
和变量修 final 最终值
饰符 implements 实现接口
interface 接口
native 本地,原生方法
new 创建对象
static 静态
strictfp 严格
synchronized 线程同步
transient 短暂
volatile 易失
break 跳出循环
case 和switch一起用
continue 跳出当次循环
default 默认
流程控 do 运行
制语句 else 否则
for 循环
if 判断
instanceof 判断是否实例
return 返回
switch 根据值选择执行
while 循环
异常处理 assert 判断表达式是否为真
throw 抛出一个异常对象
throws 声明一个异常可能被抛出
try 捕获异常
catch 捕获异常
finally 有没有异常都执行
包相关 import 导入包
package 包
基本类型 byte 字节型
short 短整型
int 整型
long 长整型
float 单精度浮点型
double 双精度浮点型
char 字符型
boolean 布尔型
变量引用 void 无返回值
this 本类
super 父类、超类
保留关键字 goto 暂时无用
const 暂时无用
null 空
3.注释规则
//这是单行注释 /*
这是多行
注释
*/
4.数据类型
4.1基本数据类型
整形:byte 8位1字节 short 16位2字节
int 32位4字节 long 64位8字节
浮点型:float 单精度 32位4字节 浮点型是科学计数法,表
double 双精度 64位8字节 示数的范围比整形大很多
字符型:char 16位2字节
布尔型:boolean 1位 默认值是false
4.2引用数据类型
除了八种基本数据类型之外的都是引用数据类型
最常用的就是String
5.数组
5.1数组声明
int[] a={1,2,3}; int[] a=new int[]{1,2,3}; int[] a=new int[3];
5.2数组工具类
Arrays.toString(a) //将数组里的数据连接成一个字符串 [值1,值2,值3] Arrays.sort(a) //数组排序,默认从小到大,基本类型是优化后的快速排序,引用类型是优化后的合并排序算法 Arrays.copyOf(a,length) //数组复制,长度大于原数组,后面的是默认值,数组的扩容;小于原数组,只复制前面length个,数组的缩容
5.3稀疏数组
稀疏数组(一种压缩方式):
使用条件:当一个数组中大部分元素为0,或者为同一值时。
处理方式:
①记录数组一共有几行几列,有多少个不同值
②把具有不同值得元素和行列及值 记录在一个小规模的数组中,从而缩小程序的规模
二、JavaOOP
1.面向对象思想
1.1面向过程与面向对象
面向过程:
步骤清晰,线性思维,适合处理一些较为简单的问题
面向对象:
物以类聚,分类的思维模式,适合处理复杂的问题,适合处理需要多人协作的问题
两者不可分割,宏观上面向对象,但到了微观操作,仍然需要面向过程的思路去处理
面向对象Object-Oriented Programming
1.2面向对象的本质
以类的方式组织代码,以对象的形式封装数据
1.3面向对象的三大特性
·封装
·继承
·多态
2.对象和类
2.1定义
对象:对象是类的一个实例,有状态和行为
类:类是一个模板,它描述一类对象的行为和状态
类是对对象的抽象,对象是类的实例
比如:人类为一个类,而个体为类的对象
2.2变量的分类
局部变量:定义在方法中的变量
成员变量:定义在类中。在创建对象时实例化,可以被类中方法、构造方法和特定类的语句块访问
类变量:定义在类中。但必须声明为static类型
变量的初始值:
数字:0 0.0
char: u0000
boolean:false
引用: null
2.3对象的创建
创建对象:对象根据类来创建,使用new来创建一个新的对象
1.声明:声明一个对象,包括对象名称和对象类型
2.实例化:使用关键字new来创建一个对象
3.初始化:使用new创建对象时,会调用构造方法初始化对象
public class Puppy{ public Puppy(String name){ System.out.println("小狗的名字是:"+name); } public static void main(String[] args){ //下面的语句将创建一个Puppy对象 Puppy myPuppy = new Puppy("tommy"); // 类名 对象名 类名 参数 } } 运行结果:
小狗的名字是:tommy
2.4源文件声明规则
·一个源文件中只能有一个public类
·一个源文件可以有多个非public类
·源文件的名称应该与public类的类名保持一致
·如果一个类定义在某个包中,那么package语句应该在源文件的首行
·如果源文件包含import语句,那么应该放在package语句和类定义语句之间。如果没有package语句,那么import语句应该在源文件中最前面。
·import语句和package语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
3.构造方法
3.1定义
构造方法:每个类都有构造方法,如果没有显式地为类定义构造方法,编译器会自动生成一个默认的构造方法。在创建一个对象的时候,至少要调用一个构造方法。与类名相同,没有返回值,也没有void
public class MyJava{ public MyJava(){ } public MyJava(String name){ //构造方法 } }
3.2构造方法的作用
创建对象时自动调用,用于初始化对象的值
3.3注意
无参构造不用定义,但是如果要定义有参构造,则必须显式地定义无参构造,否则会报错
4.封装
4.1定义
高内聚,低耦合。将类中的属性和方法的实现隐藏起来,只提供给外部少量方法访问方式
4.2实现
1.属性私有:
外部不能访问private修饰的属性,但是类内部可以访问
private String name;
2.所以通过get/set方法对private值进行操作,外部只要调用get/set方法就可以实现对private修饰的属性进行操作
public String getName(){ return this.name; } public void setName(String name){ this.name=name; }
4.3封装的意义
1.提高了程序的安全性,保护数据
2.隐藏代码的实现细节
3.统一接口
4.提高了系统的可维护性
5.继承
·继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
·extends字面意思是扩展,子类是对父类的扩展
·子类也叫派生类
·私有的属性和方法无法被继承
·区别于组合:在一个类中实例化另一个类
·继承是类和类之间的一种关系,类和类之间的关系还有依赖、组合、聚合等
·object类是所有类的父类
5.1this和super
1.介绍
this指向当前类的属性和方法
super指向父类的属性和方法
2.super注意点
①super调用父类的构造方法,必须在构造方法的第一行
②super只能出现在子类的方法或者构造方法中
③super和this不能同时调用构造方法
3.this和super的区别
①代表的对象不同:
this:本身调用者这个对象
super:代表父类对象的应用
②前提:
this:没有继承也可以使用
super:只能在继承条件才可以使用
③构造方法:
this():本类的构造
super():父类的构造
5.2方法重写
重写跟静态方法没有关系,静态的方法在子类中再次声明,则调用时对象的类型是子类,则调用子类的该方法;对象的类型是父类,则调用父类的该方法。
对于非静态的方法,在子类中可以重写,一旦重写,无论对象类型是子类还是父类,调用的都是子类中重写的方法
public class Application { public static void main(String[] args) { A a = new A(); a.test(); B b = new A(); b.test(); } } a extends b 其中当test方法为static时,运行结果
A=>test() B=>test() 当test方法不为static时,运行结果
A=>test() A=>test() 后者称为方法重写
只跟非静态方法有关
重写的条件:
1.需要有继承关系,子类重写父类的方法
2.方法名相同,参数列表相同
3.修饰符:范围可以扩大 public>protected>default>private
4.抛出的异常:可以缩小,但是不能扩大 ClassNotFoundException–>Exception
为什么需要重写?快捷键Alt+Insert 选Override
父类的功能子类不一定需要,或者不一定满足
不能重写的方法:
1.static方法,属于类,不属于实例
2.final常量池
3.private方法
6.多态
多态分为两种:重载和重写
重载:方法名和返回值相同,参数不同(个数和类型)
重写:上一节已写
另一种理解的多态:同一方法可以根据发送对象的不同而采取多种不同的行为方式;一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类,有关系的类)。
这种多态被称为父类的引用指向了子类的对象
比如:
Student s1 = new Student();//子类 Person s2 = new Student();//父类 Object s3 = new Student();
s2.run();
如果Student没有重写run,则调用Person的run,若重写了,则调用Student的run;若调用的是Person中没有的方法,则报错,需要进行强制类型转换。
比如,Person中没有eat(),但是Student中有eat()
则 s2.eat(); 报错,需要改成((Student)s2).eat();
注意点:
1.多态是方法的多态,和属性无关
2.类型转换要有继承关系。类型转换异常ClassCastException
3.多态存在的条件:继承关系、方法需要重写、父类引用指向子类对象 Father f1 = new Son();
4.不能重写的方法没有多态
7.instanceof和类型转化
7.1instanceof
判断某个对象所属的类是不是跟一个类有父子关系
或判断一个对象是否是一个类的实例
7.2类型转换
7.2.1显式转换
强制类型转换
向下转型
/*
类型之间的转化 父 子
*/ //高 低 Person student = new Student(); //student.go(); /*
编译报错,因为Person类型的student对象不能访问Student类的go方法
所以需要强制类型转换
*/ ((Student)student).go();
7.2.2隐式转换
子类转换为父类,可能会丢失一些方法(自己原本的)
向上转型
Student student = new Student(); //把Student类型转为Person类型 Person person = student;
8.static和静态代码块
用static修饰的域变量不属于任何一个类的具体对象,而专属于类。
因此,一个类的任何对象访问它时,存取到的都是相同的数值
static类优先加载,所以在非静态方法中可以调用static方法(因为static方法已经加载)
而static类中不能调用非静态方法,因为非静态方法还未加载,只有声明了对象才会加载
另外static修饰的方法和属性可以通过类名.属性/方法来访问,也可以正常访问
public class Student { private static int age=50; //静态变量 private int score=100; //非静态变量 public void run(){ System.out.println("run"); } public static void go(){ System.out.println("go"); } public static void main(String[] args) { Student s1 = new Student(); System.out.println(Student.age); //System.out.println(Student.score);//编译错误 System.out.println(s1.age); System.out.println(s1.score); s1.run();s1.go(); //Student.run();//编译错误 Student.go(); } } 可见static方法和属性可以通过类来调用,也可以通过对象来调用
匿名代码块和静态代码块
public class Person { {//2 /*代码块(匿名代码块)
一般用来赋初始值*/ System.out.println("匿名代码块"); } static{//1 /*静态代码块,只在加载类时执行一次
以后创建对象时都不会再执行*/ System.out.println("静态代码块"); } public Person(){//3 System.out.println("构造方法"); } public static void main(String[] args) { Person person1 = new Person(); System.out.println("============="); Person person2 = new Person(); } } 运行结果:
静态代码块
匿名代码块
构造方法 ============= 匿名代码块
构造方法
可见,static代码块只会在第一次创建对象时运行一次,以后都不会再运行,同时,代码块的优先级高于构造方法
静态导入包
//静态导入包 import static java.lang.Math.random; import static java.lang.Math.PI; public class Test { public static void main(String[] args) { System.out.println(random()); System.out.println(PI); } }
这样再调用的时候就不用输入Math.random()/Math.PI
9.final
以final修饰类属性,则该属性为常量;
如果修饰方法,则该方法为最终方法,再子类当中不能被覆盖(多态),可防止子类修改该方法,保证了程序的安全性和正确性;
如果修饰类,则该类不会有子类,其他类extends该类会报错
10.抽象类
用abstract修饰符修饰的方法称为抽象方法;
用abstract修饰符修饰的类称为抽象类;
一旦有了抽象方法,则必为抽象类;
但是抽象类里可以有普通的方法
package com.oop.demo08; //抽象类 public abstract class Action { //约束,有人帮我们实现 //抽象方法,只有方法名字,没有方法的实现 public abstract void doSomething(); //1.不能new抽象类,只能靠子类去实现它;约束 }
package com.oop.demo08; /*抽象类的所有抽象方法,都必须由继承了它的子类去实现
除非,子类也是抽象类,则依次往下传*/ public class A extends Action{ @Override public void doSomething() { } }
11.接口
只能用来约束,不能实现方法
做到约束和实现分离:面向接口编程
接口的本质是契约
接口内的方法都是 public abstract
属性都是 public static final //常量
接口不能被实例化,因为接口中没有构造方法
利用接口可以实现多继承 implements,即可以有一个类去实现多个接口
//接口UserService //接口都需要有实现类 public interface UserService { /*接口中定义的方法其实都是public abstract
所以可以不用写public abstract
public void run();*/ /*属性都是常量
public static final int AGE=99;*/ int AGE=99; void add(String name); void delete(String name); void update(String name); void query(String name); }
//实现类,同时实现了两个接口 //类可以实现接口implements 接口 //实现了接口的类,需要重写接口中的方法 //利用接口实现了多继承 public class UserServiceImpl implements UserService,TimeService { @Override public void add(String name) { } @Override public void delete(String name) { } @Override public void update(String name) { } @Override public void query(String name) { } @Override public void timer() { } }
12.内部类
定义:在A类的内部定义一个类B,则B为A的内部类
public class Outer{ private int id=10; public void out(){ System.out.println("这是外部类的方法"); } public class Inner{ public void in(){ System.out.println("这是内部类的方法"); } //获得外部类的私有属性 public void getId(){ System.out.println(id); } } }
//内部类的实例化 public class Application { public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.in(); } } //运行结果: //这是内部类的方法
静态内部类不能调用外部类的私有属性,因为静态内部类是先再内存中出现
此时此私有属性还未被分配空间,所以无法调用,除非私有属性也是静态的
局部内部类
public class Outer { public void method(){ //局部内部类 class Inner{ public void in(){ } } } }
匿名内部类
在类中需要实例化这个类的地方,定义一个没有名字的类
public class Test { public static void main(String[] args) { //没有名字初始化类,不用将实例保存到变量中 new Apple().eat(); //此时产生的是类A的子类对象 new Apple(){ //方法体 }; //此时产生的是接口A的实现类对象 new Pear(){ @Override public void drink() {} }; } } class Apple{ public void eat(){ System.out.println("1"); } } interface Pear{ public void drink(); }
三、异常
1.定义和分类
含义:异常是正常状况以外的事件,具有不确定性。异常发生在程序运行期间,影响了正常的程序执行流程。
分类:
1.检查性异常(程序员能检查出来的)
最具代表的检查性异常时用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略
2.运行时异常(运行时才能发现的)
运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略
3.错误ERROR
错误不是异常!!!而是脱离程序员控制的问题,错误在代码中通常被忽略,例如,当栈溢出时,一个错误就发生了,它们在编译时也检查不到
错误发生时,程序会直接终止。
2.异常处理
用try ctach finally去捕获异常
public class Test { public static void main(String[] args) { int a = 1; int b = 0; try {//try监控区域 System.out.println(a/b); }catch(ArithmeticException e){//捕获异常catch(想要补货的异常类型) System.out.println("程序出现异常,变量b不能为0"); }finally{//处理善后工作 System.out.println("finally"); } //finally可以不要,假设IO,资源,关闭等善后工作才用finally } public void a(){ b(); } public void b(){ a(); } } 运行结果:
程序出现异常,变量b不能为0 finally ArithmeticException异常不会警告,而是被捕获以后运行catch里的代码 finally作为善后工作,无论是否捕获到异常都会执行
若要捕获多个异常
用try catch catch catch finally
catch中的异常从小到大,不然会使异常捕获的精度变小
使用Ctl+Alt+T快捷键可以生成try catch
throw关键字
if(b==0){//主动抛出异常 throw new ArithmeticException(); }
throws关键字
public class Test2 { public static void main(String[] args) { try { new Test2().test(3,0); } catch (ArithmeticException e) { e.printStackTrace(); } } //假设这个方法中处理不了这个异常,则在方法上抛出 public void test(int a,int b) throws ArithmeticException { if(b==0){//主动抛出异常,一般在方法中使用 throw new ArithmeticException(); } } }
3.自定义异常
一般不写大型系统或开源框架不会用到自定义异常
因为Java内置的异常类已经可以描述在编程时出现的大部分异常情况
用户自定义异常类,只需继承Exception类即可
步骤:
1.创建自定义异常类
2.在方法中通过throw关键字抛出异常
3.如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
4.在出现异常方法的调用者中捕获并处理异常
//自定义的异常类 public class MyException extends Exception { //传递数字>10; private int detail; public MyException(int a) { this.detail=a; } //toString: 异常的打印信息 @Override public String toString() { return "MyException{" + "detail=" + detail + '}'; } }
//可能会出现异常的方法 public class Test { //可能会存在异常的方法 static void test(int a) throws MyException { System.out.println("传递的参数为"+a); if(a>10){ throw new MyException(a);//抛出 } System.out.println("OK"); } public static void main(String[] args) { try { test(9); } catch (MyException e) { System.out.println("MyException=>"+e); } } }
//运行结果 传递参数为9 OK
传递参数为11 MyException=>MyException{detail=11}
4.小结
1.用throws往上传递,用throw抛出,用try-catch捕获
2.处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理异常
3.在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
4.对于不确定的代码,也可以加上try-catch,处理潜在的异常
5.切忌只是简单地调用printStackTrace()去打印输出
6.尽量添加finally语句块去释放占用的资源
四、常用类库
1.Java类库概述
Java包下的一些常用子包
Java.lang 语言包
Java.util 使用包
Java.awt 抽象窗口工具包
Java.applet Applet包
Java.text 文本包
Java.io 输入/输出包
Java.net 网络功能包
Java.rmi 远程方法调用功能包
Java.sql JDBC接口包
1.语言包
lang包主要提供Java语言最基础的类
①Object类,是Java的根类,是所有类的共同祖先。因此Object类中的方法在任何类中都可以使用
②数据类型包装类(the data type wrapper):对应着八个基本数据类型,包装类有Byte、Short、Integer、Long、Float、Double、Character、Boolean
③字符串类:Java将字符串作为类来应用,主要有String和StringBuffer两个类
④数字型Math类:提供一组静态常量和静态方法,包括E(e)和PI(Π)常数,求绝对值的abs方法,计算三角函数的sin和cos方法,求最值的min和max方法,求随机数的random方法等。
⑤系统运行时类System、Runtime:可利用它们访问系统和运行时环境资源
⑥类Class:为类提供运行时信息,如名字、类型以及父类信息
⑦错误和异常处理类:Throwable、Exception、Error
⑧线程类:Thread
⑨过程类:Process 得到其他进程控制对象,实现不同进程间的相互通信
⑩反射包类:提供方法获得类和对象的反射信息,也就是类或对象的描述信息
2.实用包
①日期类:包括Date、Calendar类,它们描述日期和时间,提供对日期的操作方法,如获得当前日期,比较两个日期,判断日期的先后等
②集合类:包括多种集合Collection(无序集合)、Set(不重复集合)、List(有序集合)、Enumeration(集合类枚举操作)、Iterator(集合类迭代操作),以及表示数据结构的多个类,如LinkedList(链表)、Vector(向量)、Stack(栈)、Hashtable(散列表)、TreeSet(树)等
3.抽象窗口工具包
抽象窗口工具包(AWT)用来构建和管理应用程序的图形用户界面
①java.awt包:是用来构建图形用户界面(GUI)的类库,它包括许多界面元素和资源,主要在三个方面提供界面设计支持:
低级绘图操作类Graphics;图形界面组件和布局管理,如Checkbox类、Container类、LayoutManager接口等;用户界面交互控制和事件响应,如Event类
②java.awt.event包:定义了许多事件类和监听接口
③java.awt.image包:用来处理和操纵来自网上的图片
4.Applet包
包含用来实现运行于浏览器的Applet和一些相关接口
5.文本包
包中的Format、DateFormat、SimpleDateFormat等类提供各种文本或日期格式
6.输入/输出流包
包含了实现Java程序与操作系统、用户界面以及其他Java程序做数据交换所使用的类,如基本输入/输出流,文件输入/输出流、过滤输入/输出流、管道输入/输出流等。凡是需要完成与操作系统有关的、较底层的出入/输出操作都要用到包中的类。
7.网络功能包
用来实现Java的网络功能,主要有低层的网络通信(如实现套接字通信的Socket类、ServerSocket类)和高层的网络通信(如基于http新应用的URL类及URLConnection)。
8.远程方法调用功能包
这三个包用来实现远程方法调用(Remote Method Invocation,RMI)功能。利用RMI,用户程序可以在本地计算机上以代理的方式使用远程计算机上的对象提供的服务
9.JDBC接口包
提供JDBC(Java database connection)规范中的主要接口和一些常用类。利用包中的接口以统一的方式访问不同类型的数据库,从而使Java程序在具有平台无关性的同时,也具有以相同的逻辑无差异访问数据库的能力。
2.String与StringBuffer
2.1String
String的初始化方式
String str = "Hello!"; String str = new String("Hello");
String的特点是一旦赋值,便不能更改其指向的字符对象。如果更改,则会指向一个新的字符对象。
2.1.1String作为参数传递的特点
public class Test { public void changePara(String s){ s = s+"a"; System.out.println("changePara:"+s); } public void invoke(){ String s = "b"; changePara(s); System.out.println("invoke:"+s); } public static void main(String[] args) { new Test().invoke(); } } 运行结果:
changePara:ba
invoke:b
invoke结果是b而不是ba,原因是invoke中的s与changePara中的s不是指向同一个对象。changePara中的s指向一个新的字符串ba
2.1.2字符串的值比较和引用比较
public class StringEqualTest { private static String s = new String("Hello"); private static String t = new String("Hello"); public static void main(String[] args) { StringEqualTest stringEqualTest = new StringEqualTest(); System.out.println("字符串s :"+s); System.out.println("字符串t :"+t); System.out.print("引用比较结果:"); stringEqualTest.yinyongCompare(); System.out.print("值比较结果:"); stringEqualTest.zhiCompare(); } public void yinyongCompare(){//引用地址比较 if(s==t){ System.out.println("相等"); }else{ System.out.println("不相等"); } } public void zhiCompare(){//值比较 if(s.equals(t)){ System.out.println("相等"); }else{ System.out.println("不相等"); } } } 运行结果:
字符串s : Hello
字符串t : Hello
引用比较结果:不相等
值比较结果:相等
引用比较比较的是地址,所以不相等,字符串的值比较一定要用equals()方法
如果要忽略大小写,应调用方法equalsIgnoreCase()
2.2StringBuffer
public static StringBuffer changeStr(StringBuffer s){ return s.append("英雄联盟"); } public static void main(String[] args) { StringBuffer s = new StringBuffer("欢迎来到"); System.out.println(changeStr(s)); } 运行结果:
欢迎来到英雄联盟
结论:显然StringBuffer作为参数传递以后做的改变不会指向新的对象
这一点与String是截然不同的
public static void main(String[] args) { StringBuffer a = new StringBuffer("A"); StringBuffer b = new StringBuffer("B"); operate(a,b); System.out.println(a+","+b); } private static void operate(StringBuffer x, StringBuffer y){ x.append(y); y = x; } 运行结果:
AB,B
a=“AB” 因为StringBuffer不会指向新的对象,所以引用对象本身被拼接了
b!=“AB” 因为String类型一经改变会指向新的对象,改变的是y,而不是b
2.3StringBuffer与String的相互转化
StringBuffer的构造方法可将String转化为StringBuffer,而StringBuffer的toString方法可将StringBuffer转化为String
public class StringBufferTest { //去除字符串中的汉字 public static String deal(String s){ //将s由String转为StringBuffer的sb StringBuffer sb = new StringBuffer(s); //se用于存放处理后的结果 StringBuffer se = new StringBuffer(); char c; for(int i = 0; i<sb.length(); i++){ c=sb.charAt(i); //如果是ASCII码,则保存到se中 if(c>40 && c<127){se.append(c);} } return se.toString();//se 转换为String } public static void main(String[] args) { String str = "a英b雄c联d盟e"; System.out.println(deal(str)); } } 运行结果:
abcde
3.系统类和时间类
3.1System类
3.2Runtime类
每个Java程序有且只有一个Runtime类的实例,允许应用程序与其进行的环境进行交互。要得到该类的实例,不能用new,只能调用该类的getRuntime方法
它是System类中许多静态方法的真正执行者
3.3Date类
3.4Calender类
对实践操作的主要类,同样不能用new,要调用其静态方法getInstance,之后再利用相应的对象方法
package com.calendar; import java.util.Calendar; import java.util.Date; public class CalengarTest { public static void main(String[] args) { Calendar cd = Calendar.getInstance(); // Date d = cd.getTime(); System.out.println(cd.getTime().toString()); } } 运行结果:
Tue Aug 04 11:09:51 CST 2020
4.格式化类
4.1格式化数字
4.1.1NumberFormat类
public class NumberFormatTest { /*NumberFormat类,该类提供了格式化4种数字的方法:
* 整数、小数、货币和百分比,通过静态方法getIntegerInstance、
* getNumberInstance、getCurrencyInstance、getPercentInstance
* 方法获得相应格式化类的实例*/ public static void main(String[] args) { NumberFormat integer = NumberFormat.getIntegerInstance(); NumberFormat number = NumberFormat.getNumberInstance(); NumberFormat currency = NumberFormat.getCurrencyInstance(); NumberFormat percent = NumberFormat.getPercentInstance(); System.out.println(integer.format(888.88)); System.out.println(number.format(888.88)); System.out.println(currency.format(888.88)); System.out.println(percent.format(888.88)); } } 运行结果: 889 888.88 ¥888.88 88,888%
4.1.2DecimalFormat类
public class DecimalFormatTest { public static void main(String[] args) { DecimalFormat df1 = new DecimalFormat("000"); DecimalFormat df2 = new DecimalFormat("#,##0"); DecimalFormat df3 = new DecimalFormat("0.0#%"); System.out.println(df1.format(12345.54)); System.out.println(df1.format(54)); System.out.println(df2.format(12345)); System.out.println(df3.format(0.4)); } } 运行结果: 12346 054 12,345 40.0%
4.2格式化日期
SimpleDateFormat类,其对象的format方法是将Date转为指定日期格式的String,而parse方法是将String转为Date
public class SimpleDateFormatTest {
public static void main(String[] args) {
SimpleDateFormat sdf1 = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
System.out.println(sdf1.format(new Date()));
SimpleDateFormat sdf2 = new SimpleDateFormat(
"yy-M-d HH:mm:ss");
System.out.println(sdf2.format(new Date()));
SimpleDateFormat sdf3 = new SimpleDateFormat(
"yy-MMM-d HH:mm:ss");
System.out.println(sdf3.format(new Date()));
}
}
运行结果:
2020-08-04 17:15:20
20-8-4 17:15:20
20-八月-4 17:15:20
5.BigDecimal和BigInteger
BigDecimal:常用来解决精确的浮点数运算
BigInteger:常用来解决超大的整数运算
创建对象:
BigDecimal.valueOf(2);
查用方法:
add(BigDecimal bd): 做加法运算 substract(BigDecimal bd) : 做减法运算 multiply(BigDecimal bd) : 做乘法运算 divide(BigDecimal bd) : 做除法运算 divide(BigDecimal bd,保留位数,舍入方式):除不尽时使用 setScale(保留位数,舍入方式):同上 pow(int n):求数据的几次幂
//测试BigDecimal类 public class TestBigDecimal { public static void main(String[] args) { //键入a,b double a = new Scanner(System.in).nextDouble(); double b = new Scanner(System.in).nextDouble(); System.out.println(a+b); System.out.println(a-b); System.out.println(a*b); System.out.println(a/b);//不精确 System.out.println("===上面的除法不精确==="); BigDecimal bd1 = BigDecimal.valueOf(a); BigDecimal bd2 = BigDecimal.valueOf(b); BigDecimal bd3; bd3=bd1.add(bd2); System.out.println(bd3.doubleValue()); bd3=bd1.subtract(bd2); System.out.println(bd3.doubleValue()); bd3=bd1.multiply(bd2); System.out.println(bd3.doubleValue()); // bd3=bd1.divide(bd2);//报错除不尽 //保留位数和舍入方式 bd3=bd1.divide(bd2,5,BigDecimal.ROUND_HALF_UP); bd3=bd3.setScale(2, BigDecimal.ROUND_HALF_UP);//保留两位 System.out.println(bd3.doubleValue()); } } //运行结果: 10 3 13.0 7.0 30.0 3.3333333333333335 ===上面的除法不精确=== 13.0 7.0 30.0 3.33
五、线程
1.线程简介
程序:是指令和数据的有序集合,本身并没有运行的含义,是一个静态的概念
进程:是程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位
线程:是cpu调度和执行的基本单位。
一个进程中可以包含一个或多个线程
注意:真正的多线程是由多个cpu实现的,即多核,如服务器;很多多线程都是模拟出来的,在同一个时间点,cpu只能执行一个代码,但因为切换的很快,所以看起来就像并发执行。
2.线程实现
线程的实现方式有三种:
Thread类、Runnable接口、Callable接口
2.1Thread类
通过继承Thread类并重写其run方法
1.自定义线程类继承Thread类
2.重写run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程
//创建线程方式一:继承Thread类 public class TestThread1 extends Thread{ @Override public void run() { //run方法线程体 for (int i = 0; i < 20; i++) { System.out.println("我是run "+i); } } public static void main(String[] args) { //main方法主线程 //创建一个线程对象,调用start方法 TestThread1 testThread1 = new TestThread1(); testThread1.start(); for (int i = 0; i < 20; i++) { System.out.println("我是main "+i); } } } 运行结果:
我是main -363997 我是main -363998 我是main -363999 我是main -364000 我是run -327111 我是run -327112 我是run -327113 我是run -327114 我是run -327115
因为cpu运行太快,在切换之前循环就已经结束,因此改成死循环测试
以下是部分测试结果,可见main和run在宏观上是并发执行的
注意:线程开启不一定立即执行,由cpu调度执行
2.2Runnable接口
实现Runnable接口,并将实现类对象作为参数传递给Thread类的构造方法
1.定义MyRunnable类实现Runnable接口
2.实现run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程
//创建线程方式二:实现Runnable接口,实现run方法 public class TestThread2 implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("我是run:"+i); } } public static void main(String[] args) { //创建Runnable接口的实现类对象 TestThread2 testThread2 = new TestThread2(); //创建线程对象,通过线程对象来开启我们的线程 Thread thread = new Thread(testThread2); thread.start(); for (int i = 0; i < 20; i++) { System.out.println("我是main:"+i); } } } 部分运行结果:
部分运行结果:
我是main:17 我是main:18 我是main:19 我是run:0 我是run:1 我是run:2 先运行main,因为线程在启动,如果循环次数够多,main和run就会交替执行了
注意:推荐使用Runnable对象,因为单继承的局限性,如果定义的类已经继承了另一个类,则没办法再继承Thread类;
Runnable避免单继承的局限性,方便同一个对象被多个线程使用
模拟抢票代码:
//多个线程同时操作同一个对象 //买火车票 public class TestThread3 implements Runnable{ private int ticketNums = 10;//票数//票数 @Override public void run() { while(true){ if(ticketNums<=0){ break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票"); } } public static void main(String[] args) { TestThread3 ticket = new TestThread3(); new Thread(ticket,"小明").start(); new Thread(ticket,"小红").start(); new Thread(ticket,"黄牛").start(); } } 运行结果:
黄牛拿到了第10张票
小红拿到了第9张票
小明拿到了第8张票
小红拿到了第7张票
黄牛拿到了第6张票
小明拿到了第5张票
黄牛拿到了第4张票
小红拿到了第4张票
小明拿到了第3张票
黄牛拿到了第2张票
小红拿到了第1张票
小明拿到了第0张票
黄牛拿到了第-1张票
可见出现了问题,多个线程操作同一个资源的情况,线程不安全,数据紊乱
应用并发去处理
2.3Callable接口
实现Callable接口 目前只做了解
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行
Future<Boolean> result1 = ser.submit(t1);
6.获取结果
boolean r1 = result1.get();
7.关闭服务
ser.shutdownNow();
2.4实例:龟兔赛跑
1.首先来个赛道距离,要离重点越来越近
2.判断比赛是否结束
3.打印出胜利者
4.要模拟兔子睡觉
package com.thread.demo01; //模拟龟兔赛跑 public class Race implements Runnable { //胜利者 private static String winner; private static int speed=1; @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"米"); //判断比赛是否结束 boolean flag = gameOver(i); if(flag){ break; } //模拟兔子睡觉 if(Thread.currentThread().getName().equals("兔子") && i%80==0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } //判断是否完成比赛 private boolean gameOver(int steps) { //判断是否有胜利者 if (winner != null) {//已经存在胜利者了 return true; } { if (steps >= 100) { winner = Thread.currentThread().getName(); System.out.println("胜利者是" + winner); return true; } } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); } } 部分运行结果:
乌龟-->跑了90米
乌龟-->跑了91米
乌龟-->跑了92米
乌龟-->跑了93米
乌龟-->跑了94米
乌龟-->跑了95米
乌龟-->跑了96米
乌龟-->跑了97米
乌龟-->跑了98米
乌龟-->跑了99米
乌龟-->跑了100米
胜利者是乌龟
兔子-->跑了81米
兔子跑到80米时会睡觉,所以大概率会输,因为cpu执行时间远远低于睡觉的时间
2.5.静态代理
//静态代理模式总结: /*真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事情
类似于线程的Runnable实现,可以看到两者的形式几乎一样
*/ public class StaticProxy { public static void main(String[] args) { // new Thread(new TestThread1()).start(); new WeddingCompany(new You()).HappyMarry(); // WeddingCompany weddingCompany = new WeddingCompany(new You()); // weddingCompany.HappyMarry(); } } interface Marry{ void HappyMarry(); } //真实角色,结婚的人 class You implements Marry{ @Override public void HappyMarry() { System.out.println("结婚了"); } } //代理角色,婚庆公司 class WeddingCompany implements Marry{ //代理谁->真实目标角色 private Marry target; public WeddingCompany(Marry target) { this.target = target; } @Override public void HappyMarry(){ before(); this.target.HappyMarry(); after(); } private void before() { System.out.println("结婚之前,布置现场"); } private void after() { System.out.println("结婚之后,收尾款"); } } 运行结果:
结婚之前,布置现场
结婚了
结婚之后,收尾款
2.6.Lambda表达式
避免匿名内部类过多
其实质属于函数式编程的概念
去掉了一堆没有意义的代码,只留下核心的逻辑,让代码看起来很简洁
理解Function Interface(函数式接口)是学习Java8 lambda表达式的关键所在。
函数式接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
以下代码为lambda表达式的推导,逐步简化的过程
//常规的实现接口和实例化实现类 /*
推导lambda表达式
*/ public class TestLambda1 { public static void main(String[] args) { ILike like = new Like(); like.lambda(); } } //1.定义一个函数式接口 interface ILike{ void lambda(); } //2.实现类 class Like implements ILike{ @Override public void lambda() { System.out.println("I like lambda"); } }
//用静态内部类实现 public class TestLambda1 { //3.静态内部类 static class Like2 implements ILike{ @Override public void lambda() { System.out.println("I like lambda2"); } } public static void main(String[] args) { // ILike like = new Like(); // like.lambda(); ILike like2 = new Like2(); like2.lambda(); } } //1.定义一个函数式接口 interface ILike{ void lambda(); }
//用局部内部类实现
public class TestLambda1 {
public static void main(String[] args) {
//局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
ILike like3 = new Like3();
like3.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//用匿名内部类实现 public class TestLambda1 { public static void main(String[] args) { //匿名内部类 ILike like4 = new ILike() { @Override public void lambda() { System.out.println("I like lambda4"); } }; like4.lambda(); } } //1.定义一个函数式接口 interface ILike{ void lambda(); }
//用lambda简化 public class TestLambda1 { public static void main(String[] args) { //lambda简化 ILike like5 = ()->{ System.out.println("I like lambda5"); };//整个()->{};语句相当于原来的new Like();以及Like的定义 //所以返回的是一个对象 like5.lambda(); } } 其中()表示接口中唯一的方法,{}中为方法的实现(方法体),->为语法
//最简化格式
ILike like = (int a)->{
System.out.println("I like lambda"+a);
}
//去掉参数类型
ILike like = (a)->{
System.out.println("I like lambda"+a);
}
//去掉()
ILike like = a->{
System.out.println("I like lambda"+a);
}
//去掉花括号
ILike like = a->System.out.println("I like lambda"+a);
这样,仅仅用一行语句就完成了函数式接口的声明,实现,和实例化的过程
总结:
1.lambda表达式只能在只有一行方法体代码的情况下才能简化成一行;如果有多行,就用代码块包裹。
2.必须是函数式接口才能用lambda表达式
3.多个参数也可以去掉参数类型,要去掉就都得去掉,但是括号就得保留
3.线程状态
3.1五大状态
3.2线程的方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,不推荐用 |
boolean iaAlive() | 测试线程是否处于活动状态 |
3.3停止线程
/*测试stop
1.建议线程正常停止--->利用次数,不建议死循环
2.建议使用标志位--->设置一个标志位
3.不要使用stop或destroy等过时或者JDK不建议使用的方法
*/ public class TestStop implements Runnable { //1.设置一个标志位 private boolean flag=true; @Override public void run() { int i=0; while(flag){ System.out.println("run...Thread"+i++); } } //2.设置一个公开得到方法停止线程,转换标志位 public void stop(){ this.flag=false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main"+i); if(i==900){ //调用stop方法切换标志位,让线程停止 testStop.stop(); System.out.println("线程该停止了"); } } } } 部分运行结果:
main899
main900
线程该停止了
main901
main902 900以后主线程在继续递增,但是Trread线程已经不再运行了
3.4线程休眠
sleep指定当前线程阻塞的毫秒数
sleep存在异常InterruptedExcecption
sleep时间达到后线程进入就绪状态
sleep可以模拟网络延时,倒计时等
//模拟网络延时: //模拟网络延时:放大问题的发生性 public class TestSleep implements Runnable{ private int ticketNums = 10;//票数//票数 @Override public void run() { while(true){ if(ticketNums<=0){ break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票"); } } public static void main(String[] args) { TestSleep ticket = new TestSleep(); new Thread(ticket,"小明").start(); new Thread(ticket,"小红").start(); new Thread(ticket,"黄牛").start(); //问题,多个线程操作同一个资源的情况,线程不安全,数据紊乱 } //每次抢票之间加了200ms延时,因此不至于被一个线程抢走所有票
//模拟倒计时: //模拟倒计时 public class TestSleep2 { //模拟倒计时 public static void tenDown() throws InterruptedException { int number=10; while(true){ Thread.sleep(1000); System.out.println(number--); if(number<=0){ break; } } } public static void main(String[] args) { try { tenDown(); } catch (InterruptedException e) { e.printStackTrace(); } } } //运行结果为十秒倒计时
//动态打印当前时间: public static void main(String[] args) { //打印当前系统时间 Date starttime = new Date(System.currentTimeMillis());//获取系统当前时间 while(true){ try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(starttime)); starttime = new Date(System.currentTimeMillis());//更新当前时间 } catch (InterruptedException e) { e.printStackTrace(); } } } //运行结果: 00:16:43 00:16:44 00:16:45 00:16:46 00:16:47 //这里手动停止了,无限打印的
每个对象都有一个锁,sleep不会释放锁
3.5线程礼让
让当前执行的线程暂停,但不阻塞
从运行状态转为就绪状态
cpu重新进行调度,所以礼让不一定成功
//测试线程礼让 //礼让不一定成功 public class TestYield { public static void main(String[] args) { MyYield myYield = new MyYield(); new Thread(myYield,"A").start(); new Thread(myYield,"B").start(); } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName()+"线程停止执行"); } } //运行结果: A线程开始执行
B线程开始执行
A线程停止执行
B线程停止执行 //礼让成功
3.6线程强制执行
join方法:待此线程执行完成后,再执行其他线程,其他线程阻塞,就像插队
public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("线程vip来了"+i); } } public static void main(String[] args) throws InterruptedException { //启动线程 TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); //主线程 for (int i = 0; i < 1000; i++) { if(i==200){ thread.join();//插队 } System.out.println("main"+i); } } } //运行结果: main197
main198
main199
线程vip来了0 线程vip来了1 线程vip来了2 到200时插队,一直等该线程结束,main线程才能继续
3.7观测线程状态
//测试线程的状态 public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(""); }); //观测状态 是一个变量 Thread.State state = thread.getState(); System.out.println(state);//new //观测启动后 thread.start();//启动后 state = thread.getState(); System.out.println(state);//run while(state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态 Thread.sleep(100); state = thread.getState(); System.out.println(state);//输出状态 } } } //运行结果: NEW RUNNABLE TIMED_WAITING
这里输出了五秒的TIMED_WAITING
一秒钟10次 ... TIMED_WAITING TERMINATED
3.8线程优先级
线程优先级用数字表示,范围从1~10
Thread.MIN_PRIORITY=1; Thread.MAX_PRIORITY=10; Thread.NORM_PRIORITY=5;
使用getPriority()或者setPriority(int xxx)获取或修改优先级
//测试线程优先级 public class TestPriority implements Runnable{ public static void main(String[] args) { //主线程默认优先级 System.out.println(Thread.currentThread().getName() +"-->"+Thread.currentThread().getPriority()); TestPriority testPriority = new TestPriority(); Thread t1 = new Thread(testPriority); Thread t2 = new Thread(testPriority); Thread t3 = new Thread(testPriority); Thread t4 = new Thread(testPriority); Thread t5 = new Thread(testPriority); Thread t6 = new Thread(testPriority); //先设置优先级再启动 t1.setPriority(1); t1.start(); t2.setPriority(Thread.MAX_PRIORITY); t2.start(); t3.setPriority(9); t3.start(); t4.setPriority(Thread.MIN_PRIORITY); t4.start(); t5.setPriority(4); t5.start(); t6.setPriority(6); t6.start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() +"-->"+Thread.currentThread().getPriority()); } } //运行结果: main-->5 Thread-1-->10 Thread-2-->9 Thread-5-->6 Thread-4-->4 Thread-0-->1 Thread-3-->1 //优先级低知识意味着获得调度的概率低,并不是不会先被调用,这都是看cpu的调度
3.9守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不必等待守护线程执行完毕
如,后台记录操作日志,监控内存,垃圾回收等待
//测试守护线程 public class TestDaemon { public static void main(String[] args) { God god = new God(); You you = new You(); Thread godThread = new Thread(god); //默认时false表示是用户线程,正常的线程都是用户线程 godThread.setDaemon(true); godThread.start();//上帝守护线程启动 Thread youThread = new Thread(you); youThread.start(); } } //上帝 class God implements Runnable{ @Override public void run() { while(true){ System.out.println("上帝保佑着你"); } } } //你 class You implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("你一生都开心的活着"); } System.out.println("=====goodbye"); } } //youThread线程死亡以后,godThread线程也会死亡,程序不会无限运行下去
4.线程同步
并发:同一个对象被多个线程同时操作
同步:并发发生时,当某些线程还想修改这个对象,这时候就需要线程同步,多个需要同时访问此对象的而线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
队列:就是线程排队形成的序列
锁:当有线程在对对象进行修改时,需要上锁
加入锁机制(synchronized)有以下问题:
1.一个线程持有锁会导致其他所有需要此锁的线程挂起
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
4.1三大不安全案例
//不安全买票 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station,"A").start(); new Thread(station,"B").start(); new Thread(station,"C").start(); } } class BuyTicket implements Runnable{ //票 private int ticketNums=10; boolean flag = true; @Override public void run() { //买票 while(flag){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } buy(); } } private void buy(){ //判断是否有票 if(ticketNums<=0){ flag=false; return; } //买票 System.out.println(Thread.currentThread().getName() +"拿到第"+ticketNums--+"张票"); } } //运行结果: C拿到第10张票
B拿到第10张票
A拿到第10张票
C拿到第9张票
B拿到第8张票
A拿到第7张票
C拿到第6张票
B拿到第5张票
A拿到第4张票
B拿到第2张票
C拿到第3张票
A拿到第3张票
B拿到第1张票
C拿到第1张票 //不安全的取钱 //不安全的集合ArrayList public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } } //运行结果: 9995
4.2同步方法
同步方法:
public synchronized void method(int args){}
同步方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:如果将一个大的方法申明为synchronized会影响效率
方法里面需要修改内容才需要锁,锁太多了,浪费资源
同步块:synchronized(obj){}
使用同步方法或者同步块,只需要把公共资源放到synchronized下就行了,具体实现Java已经写好了
//如买票的例子,只需要在run()前价格synchronized修饰符,就可以了 private synchronized void buy(){ //判断是否有票 if(ticketNums<=0){ flag=false; return; } //买票 System.out.println(Thread.currentThread().getName() +"拿到第"+ticketNums--+"张票"); } //运行结果: C拿到第10张票
B拿到第9张票
A拿到第8张票
C拿到第7张票
B拿到第6张票
A拿到第5张票
A拿到第4张票
B拿到第3张票
C拿到第2张票
C拿到第1张票
//测试JUC安全类型的集合 //此集合本身就是安全的,因此不用加synchronized修饰 public class TestJUC { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
4.3死锁
两个或者两个以上线程都在等待对方释放资源,都停止执行的清醒。某一个同步块同时拥有两个以上对象的锁时,就可能会发生死锁的问题
//两个女生化妆,需要镜子和口红 //死锁 public class DeadLock { public static void main(String[] args) { Makeup g1 = new Makeup(0,"g1"); Makeup g2 = new Makeup(1,"g2"); g1.start(); g2.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } class Makeup extends Thread{ //用static保证两个对象共用一个资源 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice;//选择 String girlName;//名字 public Makeup(int choice, String girlName) { this.choice = choice; this.girlName = girlName; } // Makeup(int choice,String girlName){ // this.choice = choice; // this.girlName = girlName; // } @Override public void run() { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } private void makeup() throws InterruptedException { if(choice==0){ synchronized(lipstick){ System.out.println(this.girlName+"获得口红的锁"); Thread.sleep(1000); synchronized(mirror){ System.out.println(this.girlName+"获得镜子的锁"); } } } else{ synchronized(mirror){ System.out.println(this.girlName+"获得镜子的锁"); Thread.sleep(1000); synchronized(lipstick){ System.out.println(this.girlName+"获得口红的锁"); } } } } } //运行结果: g1获得口红的锁
g2获得镜子的锁
g1获得口红以后还未释放,镜子就被g2拿走了,这样g1得不到镜子,g2也得不到口红,程序就会卡住
死锁产生的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
4.4Lock(锁)
通过显式定义同步锁对象来实现同步
Lock接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
//测试Lock public class TestLock implements Runnable{ int ticketNums = 10; //定义Lock ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ lock.lock();//加锁 if(ticketNums>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--); }else{ break; } }finally{ //解锁 lock.unlock(); } } } public static void main(String[] args) { TestLock testLock = new TestLock(); new Thread(testLock,"A").start(); new Thread(testLock,"B").start(); new Thread(testLock,"C").start(); } } //运行结果: A-->10 B-->9 C-->8 A-->7 B-->6 C-->5 C-->4 A-->3 B-->2 B-->1
4.5synchronized和Lock对比
1.Lock是显式锁,手动开启和关闭,synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
4.优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)
5.线程通信
5.1生产者消费者问题
问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
分析
这是一个线程同步问题,生产者和消费者共享一个资源,并且两者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
对于消费者,再消费之后,要通知生产者已经结束消费,需要生产新的产品一共消费
在生产者消费者问题中,仅有synchronized是不够的,因为synchronized不能用来实现不同线程之间的消息传递(通信)
解决
Java提供了几个方法解决线程之间的通信问题
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度 |
以上方法均为Object类的方法,且只能在同步方法或同步代码块中使用,否则会抛出异常IIIegalMonitorStateException
5.2解决方式1–管程法
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
缓冲区:消费者不能直接使用生产者的数据,他们之间有个"缓冲区"
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
5.3解决方式2–信号灯法
通过一个boolean标志位判断是否应该等待
//测试生产者消费者问题-->信号灯法 public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Player(tv).start(); new Watcher(tv).start(); } } //生产者-->演员 class Player extends Thread{ TV tv; public Player(TV tv){ this.tv=tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if(i%2==0){ this.tv.play("快乐大本营播放中"); }else{ this.tv.play("抖音:记录美好生活"); } } } } //消费者-->观众 class Watcher extends Thread{ TV tv; public Watcher(TV tv){ this.tv=tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品-->节目 class TV{ //演员表演,观众等待 //观众观看,演员等待 String voice;//表演的节目 boolean flag = true; //表演 public synchronized void play(String voice){ if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了:"+voice); this.notifyAll(); //通知观众观看 this.voice = voice; this.flag = !flag; } //观看 public synchronized void watch(){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观看了:"+voice); //通知演员表演 this.notifyAll(); this.flag = !flag; } } 运行结果:
演员表演了:快乐大本营播放中
观看了:快乐大本营播放中
演员表演了:抖音:记录美好生活
观看了:抖音:记录美好生活
演员表演了:快乐大本营播放中
观看了:快乐大本营播放中 ...
5.4线程池
因为线程的经常创建和销毁对性能会造成影响
所以提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回池中。
可以避免频繁地创建销毁、实现重复利用。类似于共享单车
好处:
1.提高响应速度(直接调用肯定比创建快)
2.降低资源消耗
3.便于线程管理(一些方法)
corePoolSize 核心池的大小
maximumPoolSize 最大线程数
keepAliveTime 线程没有任务时最多保持多长时间后会终止
一些类:
ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor void execute(Runnable command):执行任务,没有返回值,一般用来执行Runnable <T>Future<T> submit(Callable<T> task):执行任务,有返回值。一般用来执行Callable void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用来创建并返回不同类型的线程池
//测试线程池 public class TestPool { public static void main(String[] args) { //1.创建线程池 //newFixedThreadPool 参数为线程池大小 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //2.关闭连接 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { //for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()); //} } } //运行结果: pool-1-thread-2 pool-1-thread-4 pool-1-thread-3 pool-1-thread-1
六、集合类
1.概述
1.1引入集合
目前程序中,如果出现了多个数据需要存储,解决方案就是数组,但是数组的缺点却很明显:
– 长度固定,数组一旦创建长度不可改变
– 数组里元素的类型太单调,都是不统一的
– 数组的遍历方式太单一,用下标遍历
– 如果有大量的数据需要存储,可以使用集合
– 集合工具类,都在java.util.*包里
1.2继承结构
– Collection是*接口
– List接口:
--ArrayList实现类
--LinkedList实现类
– Set接口:
– HashSet实现类
– TreeSet实现类
– Map接口
2.Collection
2.1介绍
– Collection层次结构中的根接口,Collction表示一组对象,这些对象也称为collection的元素。一些collection允许有重复的元素,而另一些则不允许。一些collection是有序的,而另一些则是无序的。
常用方法
七、I/O输入输出
1.IO简介
1.1继承结构
in/out是相对于程序而言的输入(读取)和输出(写出)的过程。
在Java中,根据处理的数据单位不同,分为字节流和字符流
//在java.io包下 //字节流:针对二进制文件 InputStream --FileInputStream --BufferedInputStream --ObjectInputStream
OutputStream --FileOutputStream --BufferedOutputStream --ObjectOutputStream //字符流:针对文本文件。读写容易发生乱码,在读写时最好指定编码集为utf-8 Writer --BufferedWriter --OutputStreamWriter
Reader --BufferedReader --InputStreamReader --PrintWriter/PrintStream
1.2流的概念
数据的读写抽象成数据在管道中流动
1.流只能单方向流动
2.输入流用来读取(in)
3.输出流用来写出(out)
4.数据只能从头到尾顺序的读写一次
2.File文件流
2.1概述
封装一个磁盘路径字符串,对这个路径可以执行一次操作,可以用来封装文件路径、文件夹路径、不存在的路径
2.2创建对象
File file = new File(String pathname) //通过将给定路径名字符串转换为抽象路径名来创建一个新File实例
2.3一些方法
//文件、文件夹属性 length();文件的字节量 exists();是否存在,返回布尔值 isFile();是否为文件,返回布尔值 isDirectory();是否为文件夹,返回布尔值 getName();获取文件名、文件夹名 getParent();获取父文件夹路径 getAbsolutePath();获取文件的完整路径 //创建、删除 createNewFile();新建文件,文件夹不存在会异常,文件已存在返回false mkdirs();新建多层不存在的文件夹\a\b\c mkdir();新建单层不存在的文件夹\a delete();删除文件,删除空文件夹 //文件夹列表 list();返回String[],包含文件名 listFile();返回File[],包含文件对象
//利用上述方法可以实现对文件的一系列操作 public class TestFile { //测试文件类 //Junit单元测试方法: //@Test + public + void + 没有参数 public static void main(String[] args) throws IOException { //1,创建File对象,读取了指定位置的文件 File f = new File("D:\\teach\\a.txt"); //文件夹列表list() listFiles() String[] names = f.list();//文件名 System.out.println(Arrays.toString(names));//null File[] files = f.listFiles();//推荐,更常见 System.out.println(Arrays.toString(files));//null //TODO常用方法 //true System.out.println(f.createNewFile());//新建文件,文件夹不存在会异常,文件已经存在返回false //false System.out.println(f.mkdir());//新建单层不存在的文件夹 //false System.out.println(f.mkdirs());//新建多层不存在的文件夹 //true System.out.println(f.delete());//删除文件,删除空文件夹 System.out.println(); //0 System.out.println(f.length());//文件的字节量 //false System.out.println(f.exists());//是否存在,存在返回true //false System.out.println(f.isFile());//是否为文件,是文件返回true //false System.out.println(f.isDirectory());//是否为文件夹,是文件夹返回true //a System.out.println(f.getName());//获取文件/文件夹名 //D:\teach System.out.println(f.getParent());//获取父文件夹的路径 //D:\teach\a System.out.println(f.getAbsolutePath());//获取文件的完整路径 } }
递归求目录总字节数:
//递归:统计文件大小 //求目录的总大小: //1、把指定目录封装成File对象 //2、把文件夹列表列出来 //3、判断,如果是文件,直接把f.length()相加 //4、判断,如果是文件夹,继续列表,继续判断,如果是文件相加,如果又是文件夹,继续列表,继续判断,如果是文件相加...... //5、如果是文件夹,递归调用方法本身的业务逻辑 //递归求目录总大小 public class TestFileConut { public static void main(String[] args) { // 1、把指定目录封装成File对象 File file = new File("D:\\teach\\a"); int size =count(file); System.out.println(size); } private static int count(File file) { // 2、把文件夹列表列出来 File[] files = file.listFiles(); //2.1 遍历数组里的每个资源 int sum = 0;//记录文件的大小 for (int i = 0; i < files.length; i++) { // 3、判断,如果是文件,直接把f.length()相加 // files[i]表示每次遍历到的资源 if(files[i].isFile()) { sum += files[i].length();//求文件的和 }else if(files[i].isDirectory()){ // 4、判断,如果是文件夹,继续列表,继续判断,如果是文件相加,如果又是文件夹,继续列表,继续判断,如果是文件相加...... // 5、如果是文件夹,递归调用方法本身的业务逻辑 sum += count(files[i]);//把当前遍历到的文件夹继续循环判断求和 } } return sum ; } }
递归删除文件夹
public class TestFileDelete { public static void main(String[] args) { File file = new File("D:\\teach\\a"); delete(file); System.out.println("成功删除该路径"); } private static void delete(File file) { //列出文件夹列表并接收 File[] files = file.listFiles(); //遍历 for (int i = 0; i < files.length; i++) { if(files[i].isFile()){ files[i].delete(); } else if(files[i].isDirectory()){ delete(files[i]); files[i].delete(); } } } }
3.扩展:常用字符编码表
编码 | 说明 | 编码范围 | 字节量 |
---|---|---|---|
ASC-II | 英文,标点,基本指令 | 0–127 | 单字节 |
ISO-8859-1 | ASC-II扩展,西欧字符 | 128-255 | 单字节 |
ANSI,CJK | |||
GBK | 中国国标码 | 最大65535 | 英文单字节,中文双字节 |
UNICODE | 统一码,Java的char类型字符,就采用UNICODE编码 | 100万+编码位,分为常用字符表,生僻字符表等,我们用常用表 | 常用字符表所有字符都采用双字节 |
UTF-8 | Unicode传输格式,Unicode Transformations Format为了解决Unicode英文字符字节量翻倍的问题,提出的一种变长的编码格式 | 英文单字节,某些字符双字节,中文三字节,一些特殊字符四字节 |
其中ANSI,CJK说明:ANSI是一种字符编码规范,设计为了使计算机支持更多语言,英文用单字节便阿门,其他字符用双字节编码。CJK表示亚洲字符编码的统称(中日韩)
public class Test{ public static void main(String[]args) throws UnsupportedEncodingException { String s = "我爱你中国"; System.out.println(s.getBytes("utf-8").length); //15--unicode/u8一个汉字3字节存储 System.out.println(s.getBytes("gbk").length); //10--中文双字节 System.out.println(s.getBytes("unicode").length); //12--双字节+2 System.out.println(s.getBytes("iso-8859-1").length); //5--单字节 } }
4.字节读取流
字节流:对所有二进制文件都可以进行读写操作,更加常用!
读取:指把磁盘中的数据 读取 到程序中 .in
写出:指把程序中准备好的数据 写出 到磁盘中 .out
InputStream
– 字节读取流的抽象父类,不能new,只能学习他的共性方法
常用方法:
void close();//关闭此输入流并释放与该流关联的所有系统资源 abstract int read();//从输入流中读取数据的下一个字节 int read(byte[] b);//从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中 int read(byte[] b,int off,int len);//将输入流中最多len个数据字节读入byte数组
FileInputStream
– 学习子类创建对象,从文件系统中的某个文件中获得输入字节
//创建对象 new FileInputStream(String pathname)//路径,用InputSteam类型去接 new FileInputStream(File file)//new File()返回类型是File,所以可以new File(路径)
BufferedInputStream
– 为另一个输入流添加一些功能,会创建与一个内部缓冲区数组
//创建对象 new BufferedInputStream(InputStream in); 例如:
InputStream in = new BufferedInputStream(new FileInputSteam(路径));//传参为InputStream类型 创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用 BufferedInputStream(InputStream in, int size); 创建具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流in
//测试 字节读取流 //FileInputStream BufferedInputStream都可以用来读取 //效率上来讲: BufferedInputStream > FileInputStream //原因是:FileInputStream 是一个一个字节的读取 //BufferedInputStream是一个数组一个数组的读取/缓冲流/高级流 //本质上,底层就是维护了一个byte[]数组叫 buf,用来缓冲数据,默认大小是8192,8*1024字节=8K数据. //把数组里的数据一次性的给程序读取进来.减少了程序冲流里获取数据的次数,提高了效率 public class TestInputStream { public static void main(String[] args) throws IOException { FIS();//FileInputStream读取流 //BIS();//BufferedInputStream读取流 } //BufferedInputStream读取流 private static void BIS() throws IOException { //1,创建对象 InputStream in = new BufferedInputStream( new FileInputStream("D:\\iotest\\1.txt") ); //2,开始读取 int b = 0 ;//定义变量,记录read()返回的值 while( ( b = in.read() ) != -1) { System.out.println(b); } //3,释放资源 in.close(); } //FileInputStream读取流 public static void FIS() throws IOException { //1,创建多态对象 -- 读取指定位置的文件 //触发了FileInputStream的String类型参数的构造方法 InputStream in = new FileInputStream("D:\\iotest\\1.txt"); //触发了FileInputStream的File类型参数的构造方法 // File file = new File("D:\\iotest\\1.txt"); // InputStream in2 = new FileInputStream(file); InputStream in2 = new FileInputStream( new File("D:\\iotest\\1.txt") ); //2,开始读取 // int b = in.read() ;//读取方法返回值是整数 // System.out.println( b );//a->97 // System.out.println( in.read() );//98 // System.out.println( in.read() );//99 // //--问题1:如果没数据了,还能读吗?--可以读,永远返回-1!! // System.out.println( in.read() );//-1 // System.out.println( in.read() );//-1 //---改造,重复执行read() int b = 0 ;//b记录读取到的数据 while( ( b = in.read() ) != -1) {//-1代表没数据可读了 System.out.println(b);//打印读取到的数据 } //3,释放资源 in.close(); in2.close(); } }
5.字节写出流
OutputStream
– 字节写出流的抽象父类,不能new,只能学习他的共性方法
常用方法
void close();//关闭此输出流并释放与此流有关的所有系统资源 void flush();//刷新此输出流并强制写出所有缓冲的输出字节 void write(byte[] b);//将b.length个字节从指定的byte数组写入此输出流 void write(byte[] b,int off,int len);//将指定byte数组中从偏移量off开始的len个字节写入此输出流 abstract void write(int b);//将指定的字节写入此输出流
FileOutputStream
– 用于将数据写出到文件中的输出流
创建对象
new FileOutputStream(String name);//返回值是OutputStream类型 FileOutputSteram(File file); FileOutputStream(File file,boolean append); //false (默认)数据覆盖模式 true 数据追加模式(尾插) FileOutputStream(String name,boolean append);
BufferedOutputSteram
– 该类实现缓冲的输出流
创建对象
new BufferedOutputStream(OutputStream out);//传参为OutputSteram类型,返回值也是OutputStream类型 //创建一个新的缓冲输出流,以将数据写入指定的底层输出流 BufferedOutputStream(OutputSteram out,int size); //创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
//测试 字节写出流 //1,效率上来讲:BufferedOutputStream > FileOutputStream //2,原因: //FileOutputStream是一个一个字节向磁盘输出 //BufferedOutputStream是一个数组一个数组向磁盘输出/高级流/缓冲流 //本质上,底层维护了一个byte[] buf,用来缓冲数据.默认大小是8192,相当于8K public class TestOutputStream { public static void main(String[] args) throws IOException { //FOS();//FileOutputStream写出流 BOS();//BufferedOutputStream写出流 } //BufferedOutputStream写出流 private static void BOS() throws IOException { //1,创建对象--把数据写出到指定文件中 //默认的数据覆盖模式 //OutputStream out = new BufferedOutputStream( // new FileOutputStream("D:\\iotest\\1.txt")); //TODO 数据追加模式 OutputStream out = new BufferedOutputStream( new FileOutputStream("D:\\iotest\\1.txt",false)); //2,开始写出 out.write(49); out.write(49); out.write(49); //3,释放资源 out.close(); } //FileOutputStream写出流 private static void FOS() throws IOException { //1,创建对象--指把数据写出到磁盘的哪个文件中去 //OutputStream out = new FileOutputStream("D:\\iotest\\1.txt");//默认就是数据覆盖模式 OutputStream out = new FileOutputStream("D:\\iotest\\1.txt",true);//数据追加模式 //OutputStream out2 = new FileOutputStream(new File("D:\\iotest\\1.txt")); //2,开始写出 out.write(97); out.write(98); out.write(99); //3,释放资源 out.close(); // out2.close(); } }
6.字符读取流
字符流:只能对字符文件txt进行读写操作
读取:指把磁盘中的数据 读取 到程序中 .in
写出:指把程序中准备好的数据 写出 到磁盘中 .out
Reader
– 用于读取字符流的抽象父类,不能new,只能学习他的共性方法
常用方法
abstract void close();//关闭该流并释放与之相关的所有资源 int read();//读取单个字符 int read(char[] cbuf);//将字符读入数组 abstract int read(char[] cbuf,int off,int len); //将字符读入数组的某一部分
FileReader
– 用来读取字符文件的便捷类
创建对象
new FileReader(String fileName);//返回类型Reader FileReader(File file);
BufferedReader
– 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取,可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
创建对象
BufferedReader(Reader in); //创建一个使用默认大小输入缓冲区的缓冲字符输入流。
//测试 字符读取流 //效率上谁快:BufferedReader > FileReader //原因: //FileReader 是一个一个字符的读取 //BufferedReader 是一个数组一个数组的读取 //本质上,就是因为底层维护了一个char[] cb,用来缓冲数据,默认大小是8192相当于8K public class TestReader { public static void main(String[] args) throws IOException { // FR();//FileReader读取流 BR();//BufferedReader读取流 } //BufferedReader读取流 private static void BR() throws IOException { //1,创建对象 Reader in = new BufferedReader(new FileReader("D:\\iotest\\1.txt")); //2,开始读取 int b = 0 ; while( ( b=in.read() ) != -1) { System.out.println(b); } //3,释放资源 in.close(); } //FileReader读取流 public static void FR() throws IOException { //1,创建对象 Reader in = new FileReader("D:\\iotest\\1.txt");//触发了String参数的构造 // Reader in = new FileReader( new File("D:\\iotest\\1.txt") );//触发了File参数的构造 //2,开始读取 // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // //问题1:没数据也可以继续读取,不会抛出异常,只不过永远返回-1 // System.out.println( in.read() ); // System.out.println( in.read() ); //TODO --改造 int b = 0 ;//定义变量,记录读取到的数据 while( ( b=in.read() ) != -1) {//只要有数据就一直读,如果是-1就代表没数据了 System.out.println(b); } //3,释放资源 in.close(); } }
7.字符写出流
Writer
– 字符写出流的抽象父类,不能new,只能学习他的共性方法
常用方法:
abstract void close();//关闭此流,但要先刷新它 abstract void flush();//刷新该流的缓冲 void write(char[] cbuf);//写入字符数组 abstract void write(char[] cbuf,int off,int len); //写入字符数组的某一部分 void write(int c);//写入单个字符 void write(String str);//写入字符串 void write(String str,int off,int len);//写入字符串的某一部分
FileWriter
– 用来写入字符文件的便捷类
创建对象
FileWriter(File file); FileWriter(String fileName); FileWriter(File file,boolean append); FileWriter(String fileName,boolean append);
BufferedWriter
– 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入,可以指定缓冲区的大小,或者接受默认大小的缓冲区。在大多数情况下,默认大小就足够大了
创建对象
BufferedWriter(Writer out); //创建一个使用默认大小输出缓冲区的缓冲字符输出流
//测试 字符写出流 public class TestWriter { public static void main(String[] args) throws IOException { FW();//FileWriter写出流 // BW();//TODO BufferedWriter写出流 } //FileWriter写出流 private static void FW() throws IOException { //1,创建对象 // Writer out = new FileWriter("D:\\iotest\\1.txt");//默认就是数据覆盖模式 Writer out = new FileWriter("D:\\iotest\\1.txt",true);//数据追加模式 // Writer out = new FileWriter( new File("D:\\iotest\\1.txt") ); //2,开始写出 out.write(97); out.write(97); out.write(97); out.write("大家好,我叫皮皮霞!!!"); //3,释放资源 out.close(); } }
8.序列化/反序列化
9.编码转换流
10.BIO、NIO、AIO的区别
八、Java网络通信
九、案例:贪吃蛇小游戏
创建对象
new FileReader(String fileName);//返回类型Reader FileReader(File file);
BufferedReader
– 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取,可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
创建对象
BufferedReader(Reader in); //创建一个使用默认大小输入缓冲区的缓冲字符输入流。
//测试 字符读取流 //效率上谁快:BufferedReader > FileReader //原因: //FileReader 是一个一个字符的读取 //BufferedReader 是一个数组一个数组的读取 //本质上,就是因为底层维护了一个char[] cb,用来缓冲数据,默认大小是8192相当于8K public class TestReader { public static void main(String[] args) throws IOException { // FR();//FileReader读取流 BR();//BufferedReader读取流 } //BufferedReader读取流 private static void BR() throws IOException { //1,创建对象 Reader in = new BufferedReader(new FileReader("D:\\iotest\\1.txt")); //2,开始读取 int b = 0 ; while( ( b=in.read() ) != -1) { System.out.println(b); } //3,释放资源 in.close(); } //FileReader读取流 public static void FR() throws IOException { //1,创建对象 Reader in = new FileReader("D:\\iotest\\1.txt");//触发了String参数的构造 // Reader in = new FileReader( new File("D:\\iotest\\1.txt") );//触发了File参数的构造 //2,开始读取 // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // System.out.println( in.read() ); // //问题1:没数据也可以继续读取,不会抛出异常,只不过永远返回-1 // System.out.println( in.read() ); // System.out.println( in.read() ); //TODO --改造 int b = 0 ;//定义变量,记录读取到的数据 while( ( b=in.read() ) != -1) {//只要有数据就一直读,如果是-1就代表没数据了 System.out.println(b); } //3,释放资源 in.close(); } }
7.字符写出流
Writer
– 字符写出流的抽象父类,不能new,只能学习他的共性方法
常用方法:
abstract void close();//关闭此流,但要先刷新它 abstract void flush();//刷新该流的缓冲 void write(char[] cbuf);//写入字符数组 abstract void write(char[] cbuf,int off,int len); //写入字符数组的某一部分 void write(int c);//写入单个字符 void write(String str);//写入字符串 void write(String str,int off,int len);//写入字符串的某一部分
FileWriter
– 用来写入字符文件的便捷类
创建对象
FileWriter(File file); FileWriter(String fileName); FileWriter(File file,boolean append); FileWriter(String fileName,boolean append);
BufferedWriter
– 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入,可以指定缓冲区的大小,或者接受默认大小的缓冲区。在大多数情况下,默认大小就足够大了
创建对象
BufferedWriter(Writer out); //创建一个使用默认大小输出缓冲区的缓冲字符输出流
//测试 字符写出流 public class TestWriter { public static void main(String[] args) throws IOException { FW();//FileWriter写出流 // BW();//TODO BufferedWriter写出流 } //FileWriter写出流 private static void FW() throws IOException { //1,创建对象 // Writer out = new FileWriter("D:\\iotest\\1.txt");//默认就是数据覆盖模式 Writer out = new FileWriter("D:\\iotest\\1.txt",true);//数据追加模式 // Writer out = new FileWriter( new File("D:\\iotest\\1.txt") ); //2,开始写出 out.write(97); out.write(97); out.write(97); out.write("大家好,我叫皮皮霞!!!"); //3,释放资源 out.close(); } }
8.序列化/反序列化
9.编码转换流
10.BIO、NIO、AIO的区别
八、Java网络通信
九、案例:贪吃蛇小游戏
本文地址:https://blog.csdn.net/weixin_46142977/article/details/108035976