1. Thread 类(续), 线程池(重点)
1.1 如何解决多线程安全问题?------(同步机制)
1.1.1 检验多线程安全问题的标准:
1)是否存在多线程环境
2)是否有共享数据
3)是否存在多条语句对共享数据进行操作 更改这个标准
1.1.2 方法
1: 同步代码块:
使用synchronized(锁对象){ -----Lock 接口
多条语句对共享数据进行操作
}
2:
volatile关键字:也可以解决线程安全
3: 等待唤醒机制:
wait()+notify():等待唤醒机制:也属于同步机制一种
对共享数据进行操作,使用锁操作,别的线程没有方法访问,除非当前锁被释放了,然后才能互相抢占CPU执行权!
4: 同步方法
1.2 什么是同步方法,锁对象是谁?
跟所谓成员方法定义格式一样,就是在当前成员方法中,如果方法体的内容是一个同步代码块,可以进行优化,
将synchronized定义方法声明上---同步方法,------锁对象:就是this
静态的同步方法跟类相关,锁对象--->当前类名.class属性---->Class 字节码文件对象(class 包名.类名)
1.3 wait()和notify()为什么定义在Object类中,不定义在Thread类中
wait()和notify():
代表线程处于等待或者线程唤醒,他们的调用是要通过同步机制中的锁对象来访问的,(锁对象可以是任意的Java类对象) 当前某个线程持有锁的时候,调用wait(),处于等待过程,但是立即释放锁,然后可以通过锁对象.notify()唤醒对方线程,解决死锁问题;
等待唤醒机制:
生产者线程不断的产生数据,没有数据了,等待先产生数据,有数据了之后,需要通知消费者线程来使用数据;
消费者线程不断的使用数据,有数据了,先等待使用数据,当数据使用完毕,没有数据了,需要通知生产者线程产生数据;
1.4 sleep()和wait()方法的区别 (面试题)
1)来源不同
sleep()来自于Thread类中,表示线程睡眠 sleep(long time):时间毫秒值
wait()来自于Object类中,表示线程等待,需要被锁对象来访问
2)是否会释放锁
sleep(long time):属于一个普通方法,调用该方法,不会去释放锁,只是导致线处于睡眠(线程阻塞),当线程的睡眠时间到了,就继续执行线程;
wait():属于锁方法,被锁对象访问之后,会立即释放锁,才能够使用同步等待唤醒机制解决死锁问题,通过锁对象调用notify(),唤醒对方线程;
3)这两个方法都会抛出异常(中断异常),都是属于本地方法,底层非Java语言实现
当线程睡眠过程中,睡眠还没到,导致的睡眠状态被打断,就会出现InterruptedException
当线程处于等待状态,被中断,就抛出这个异常!
public static native void sleep(long millis) throws InterruptedException
public final native void wait(long timeout) throws InterruptedException;
1.5 Thread类中:
public final ThreadGroup getThreadGroup():获取当前线程所属的线程组
ThreadGroup
public final String getName():获取线程组名称
ThreadGroun的构造方法:
public ThreadGroup(String name)
示例:
MyRunnable implents Runnable(){}
..
// 创建runnable对象
MyRunnable myRunnable = new MyRunnable() ;
//创建线程
Thread t1 = new Thread(myRunnable) ;
//获得该线程所爱的线程池
ThreadGroup tg1 = t1.getThreadGroup();
1.6 第三种方式实现线程的创建:使用线程池 (重点)
工厂类: 里面提供了一个静态方法: 固定线程池
public static ExecutorService newFixedThreadPool(int nThreads);创建一个固定的可重复的线程数的线程池
ExecutorService:接口 它可以进行多个异步任务的执行
<T> Future<T> submit(Callable<T> task) :提交异步任务, 这些异步任务就需要执行Callable中的call方法:
V call() throws Exception :
A: 如果仅仅是要实现多个线程并发执行,那么调用sumbit的方式,返回值可以不写
B: 如果是要计算具体的结果,那么就需要将Future返回,它表示异步计算的结果值是什么
1.6.1 A 示例: 不计算结果,仅仅展示:多个线程并发执行
// CALLABLE 子实现类
public class MyCallable implements Callable {
//不计算结果,仅仅展示:多个线程并发执行
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
}
//测试类
public class ThreadPoolTest {
public static void main(String[] args) {
//public static ExecutorService newFixedThreadPool(int nThreads)
//创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//<T> Future<T> submit(Callable<T> task)
//接口多态
Callable callable = new MyCallable() ;
//两个线程分别执行异步任务, 没有返回值, 只是连个线程分别执行
threadPool.submit(callable) ;
threadPool.submit(callable) ;
//使用完毕,关闭线程池
//线程需要归还到线程池中
//void shutdown()
threadPool.shutdown() ;
}
}
1.6.2 B : 两个线程分别进行求和!----计算结果 使用线程池完成
/*<T> Future<T> submit(Callable<T> task)
* Future:计算的结果 接口
* V get()获取结果
*/
// callable子实现类
public class MyCallable implements Callable<Integer> {
private int num ;
public MyCallable(int num){
this.num = num ;
}
@Override
public Integer call() throws Exception {
//定义最终结果变量
int sum = 0 ;
for(int x = 1 ; x <= num ; x ++){
sum += x ;
}
return sum;
}
}
//测试类
public class ThreadTest {
public static void main(String[] args) throws
ExecutionException, InterruptedException {
//创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);// 线程数2 的固定线程池
//提交异步任务进计算
Future<Integer> f1 = threadPool.submit(new MyCallable(100));
Future<Integer> f2 = threadPool.submit(new MyCallable(200));
// 有返回值, 两个线程分别进行计算
Integer i1 = f1.get();// 计算0-100之和
Integer i2 = f2.get();// 计算0-200之和
System.out.println(i1) ;
System.out.println(i2) ;
//关闭线程池
threadPool.shutdown() ;
}
}
1.7 线程池的7大参数(重点)
线程池的的优化:
同过参数进行优化.
7大参数:
private volatile int corePoolSize: 核心线程数量
private volatile int maximumPooSize: 允许最大线程池数:
当 corePoolSize 数量已经最大,且WorkQueue已经塞满,继续创建线程
private volatile long keepAliveTime:临时线程存活时间
如果当前线程数量达到核心线程数量,开启临时线程.
private final BlockQueue<Runnable>workQueue: 工作队列, 先进先出
当已达到最大核心线程数量,所有线程都会进入工作队列, 当前工作队列中所有线程塞满了, 处于阻塞状 态, 等待被使用; 本质的底层原理: collection
private volatile ThreadFactory threadFactory: 线程工厂.
创建线程实例的子实现类
private volatile RejectedExecutionHandler handler: 线程池的拒绝策略.
当线程池中线程数量达到了最大线程数量, 就会调用此方法,启用拒绝策略,保证当前线程池中所有线程可 重复用.
TimeUnit unit: unit 是 keepAliveTime的计量单位.
1.8 多线程安全问题:
1.8.1 安全问题的校验标准:
1)检查你的程序是否为多线程环境 是
2)是否存在共享数据: 存在
3)是否有多条语句对共享数据进行操作 也存在
1.8.2 解决方法
现在环境就需要使用多线程实现 --> 1)不能更改
必须存在共享数据,因为三个线程共享一个内存区域 --> 2)标准不能被更改
Java提供了:使用同步代码块 将 多条对共享数据的操作包裹起来,需要 ---> 更改3)
1.8.2.1 解决方法1: 同步代码块
synchronized(锁对象){
多条语句对共享数据进行操作
}
锁对象:
可以是任意的Java类对象(jdk提供的任意类或者自定义类型)
多个线程使用的必须是 同一个锁对象! 理解为:"火车上上厕所的门开和关!"
1.8.2.2 解决方法1: 同步方法
如果一个方法,一进来就是同步代码块,我们就可以把synchronized,提到方法声明上, 将这个方法变为同步方法.
权限修饰符 synchronized 返回值类型 方法名(形式参数列表){
...
}
同步方法:
都指定的非静态的同步方法
隐藏的锁对象:
this:代表当前对象的地址值引用
一个静态方法,可以定义静态的同步方法:
锁对象:
跟类的字节码文件有关系:类名.class
1.9 死锁现象:
原因:多个线程在互相抢占资源数据的时候,出现了线程互相等待的情况!
解决办法:
必须保证多个线程 "共享同一个资源对象"------>生成者消费者模式思想操作
1.10 生产者和消费者模式
生产者和消费者共同使用同一个资源对象.
生产者:
产生数据,等待消费者使用. wait()---> 产生数据了,通知消费者线程来消费数据--->notify()
消费者:
消费数据,等待生产者生产. wait()---> 消费完数据了,通知生产者线程生产数据--->notify()
1.11 示例: 模拟生产者和消费者模式
(生产者消费者模式 + 等待唤醒机制)
ThreadDemo类:main线程:开启两个线程
Student类: 学生数据 (姓名和年龄)
SetThread: 生产者资源类:产生学生数据
GetThread:消费者资源类: 使用学生数据
分析:
1) 需要使用同一个资源对象(student)
2) 同步代码块 解决数据紊乱问题(姓名和年龄不符!)
3) 等待唤醒机制,信号灯法,解决线程抢占问题(消费者使用数据:一次性打印一大片,想出现依次打印
高圆圆 42
李云迪 39)
示例:
//学生类
public class Student {
String name ; //姓名
int age ;//年龄
boolean flag ; //默认没有数据,通过这标记信号:表示是否存在数据
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 生产者资源类
public class SetThread implements Runnable {
private Student s ; //声明学生变量s
public SetThread(Student s){
this.s = s ;
}
//统计变量
private int x = 0 ;
@Override
public void run() {
while(true){
//t1
synchronized (s){
//判断: 如果当前生产者没有数据,先等待产生数据
if(s.flag){
try {
//wait()方法为什么定义Object类中? 因为锁对象可以是任意的Java类对象 (包括Object)
//wait()一旦被调用,会立即释放锁
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x % 2 ==0){
s.name ="高圆圆" ;
s.age = 42 ;
}else {
s.name = "李云迪";
s.age = 39;
}
//改变标记:有数据了
//改变标记
s.flag = true ;
//调用通知对方:唤醒对方线程
s.notify() ;
}
x++ ;//原子性操作
}
}
}
// 消费者资源类
public class GetThread implements Runnable {
//声明学生变量s
private Student s ;
// 成员变量赋值给局部变量
public GetThread(Student s) {
this.s = s ;
}
//Student s= new Student() ;
@Override
public void run() {
//使用学生数据
//System.out.println(s.name+"-"+s.age) ;
//不断的去使用数据
while(true){
synchronized (s){
//如果当前消费者存在数据
if(!s.flag){
//等待将产生的数据消费掉
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//改变标记
s.flag= false ;
//通知生成者线程,产生数据
s.notify() ;
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建一个学生对象
Student s = new Student() ;
//创建生产者资源类对象
SetThread st = new SetThread(s) ;
//创建消费者资源类对象s
GetThread gt = new GetThread(s) ;
//创建线程类对象
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(gt) ;
//启动线程
t1.start() ;
t2.start() ;
}
}
2. 代理–静态/ 动态代理代理
2.1 什么代理?
就是代理角色帮助真实角色完成一些事情!
举例:
过年回家,让朋友代买火车票
2.2 静态代理:
通过代理角色帮助真实角色完成一些事情,真实角色只只专注于自己的事情!
前提条件:代理角色和真实角色必须是同一个接口!
举例:
结婚这件事情
真实角色:You
代理角色:WeddingCompany:婚庆公司
2.3 jdk动态代理
jdk动态代理----必须存在接口 ----通过反射方式完成:
需要将业务功能和系统监控的功能,分离起来!
UserDao接口
void add() ;
void updte() ;
void delete() ;
void find() ;
java.lang.reflect.Proxy
public static Object newProxyInstance(
ClalssLoader loader,
Class[] interfaces,
InvocationHandler handler) ; //接口 基于代理的处理程序,需要对业务功能代码增强
自定义类实现InvocationHandler ,重写invoke方法,完成方法增强!
3. 等待唤醒机制–信号灯法:
3.1 将等待唤醒机制:解决死锁问题代码进行优化 (“信号灯法”)
1)将数据:Student类的成员加入私有化
2)在Student类中提供set方法,对学生数据进行赋值,加入同步方法
3)在生产者资源类中,只需要调用当前set方法进行赋值
4)在Student类中提供get方法,获取学生数据,加入同步方法
5)在GetThread:消费者资源类中,调用get方法即可!
示例:
//学生类:
public class Student {
private String name ; //姓名
private int age ;//年龄
private boolean flag ; //默认没有数据,通过这标记信号:表示是否存在数据
/**
* 给学生数据进行赋值
* @param name 姓名
* @param age 年龄
*/
public synchronized void set(String name,int age){
//如果方法一进来就是一个同步代码块,需要将synchronized定义在方法声明上,锁对象this
//判断:
//如果当前生产者没有数据,先等待产生数据
if (this.flag) {
try {
this.wait(); //wait()方法为什么定义Object类中? 因为锁对象可以是任意的Java类对象 (包括Object)
// //wait()一旦被调用,会立即释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//赋值
this.name = name ;
this.age = age ;
//改变标记:有数据了
//改变标记
this.flag = true ;
//调用通知对方:唤醒对方线程
this.notify() ;
}
/**
* 获取学生数据
*/
public synchronized void get(){
//如果当前消费者存在数据
if (!this.flag) {
//等待将产生的数据消费掉
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name + "---" + this.age);
//改变标记
this.flag= false ;
//通知生成者线程,产生数据
this.notify() ;
}
}
// 生产者资源类
public class SetThread implements Runnable {
//Student s = new Student() ;
private Student s ; //声明学生变量s
public SetThread(Student s){
this.s = s ;
}
//统计变量
private int x = 0 ;
@Override
public void run() {
while(true){
if(x % 2 ==0){
s.set("高圆圆",42);
}else {
s.set("赵又廷",45);
}
x++ ;//原子性操作
}
}
}
//消费者资源类
public class GetThread implements Runnable {
//声明学生变量s
private Student s ;
public GetThread(Student s) {
this.s = s ;
}
//Student s= new Student() ;
@Override
public void run() {
//使用学生数据
//System.out.println(s.name+"-"+s.age) ;
//不断的去使用数据
while(true){
s.get();
}
}
}
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//创建一个学生对象
Student s = new Student() ;
//创建生产者资源类对象
SetThread st = new SetThread(s) ;
//创建消费者资源类对象s
GetThread gt = new GetThread(s) ;
//创建线程类对象
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(gt) ;
//启动线程
t1.start() ;
t2.start() ;
}
}
4. LOCK 锁
jdk5以后提供了一个juc包下(java.util.concurrent.locks )
Lock接口 比 synchronized具有扩展性的功能
子实现类ReentrantLock:可重入的互斥锁
public void lock()获取锁
public void unlock()释放锁
API举例
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
处理异常: try...catch...finally(释放资源:代码一定会执行/除非执行之前jvm退出了) 捕获异常的标准格式
try...catch...catch...
try...finally
throws :抛出异常:抛出在方法声明上
示例 : 使用这个Lock:模拟电影院三个窗口同时卖票100张
// runable 子实现类
public class SellTicket implements Runnable {
//票数
private static int tickets = 100 ;
//创建一个锁:Lock ---子实现类:可重入的互斥锁
private ReentrantLock lock = new ReentrantLock() ;
@Override
public void run() {
//模拟一直有票
while(true){
//获取锁
lock.lock() ;
try{
if(tickets > 0){
//睡眠100毫秒 (模拟网络延迟睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}finally {
//释放资源代码块: 结合捕获异常 try...catch...finally一块使用:变形格式 try...finally
lock.unlock();
}
}
}
}
// 测试类
public class LockDemo {
public static void main(String[] args) {
//创建资源类对象
SellTicket st = new SellTicket() ;// st: 共享资源类
//创建三个线程
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start() ;
t2.start() ;
t3.start() ;
}
}
5. TIMER 定时器
java.util.Timer :JavaSE的定时器 :处理一次或者重复执行的任务( TimerTask:抽象类 )
JavaEE的定时任务框架Quartz----后面可以"Spring"整合
5.1 构造方法:
public Timer():创建一个新的定时器,不是守护线程!
5.2 成员方法:
public void cancel():取消定时器
public void schedule(TimerTask task,long delay):经过多少毫秒后执行一次定时任务
public void schedule(TimerTask task,long delay,long period)
经过delay毫秒后指向定时任务,每经过period重复执行定时任务!
public void schedule(TimerTask task,Date time) :在指定时间的时候(精确到毫秒)执行定时任务
5.3 示例:
public class TimerDemo {
public static void main(String[] args) {
//创建一个定时器
//空参构造
Timer timer = new Timer( );
//开启定时任务
/* timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("bom...");
}
}, 3000);*/
// public void schedule(TimerTask task,long delay,long period)
timer.schedule(new MyTask(timer),2000,3000);
}
}
//定时任务
class MyTask extends TimerTask{
//声明定时器变量
private Timer t ;
public MyTask(Timer t){
this.t = t ;
}
@Override
public void run() {
System.out.println("bom...");
t.cancel() ;
}
}
6. 设计模式和原则
6.1 简单工厂模式
简单工厂模式:
也称为"静态工厂方法模式",主要作用负责某些类的实例的创建过程!
优点:
通过工厂类完成各个类的对象的创建
弊端:
当有新的类型增加,需要不断的修改工厂类,维护性难度大!
6.2 工厂模式
工厂方法模式:
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点:
具体的工厂类负责创建当前具体的实例,结构层次很清晰,有具体类/工厂类/抽象工厂(接口)
弊端:
有新的类型增加,需要编写外的代码,所有代码增加,导致工作量增加了
6.3 单例模式
单例:
一个类的实例在内存中有且仅有一个!
在程序内存中,始终只有一个对象!
6.3.1 饿汉式:
饿汉式:当类一加载,就创建了当前类对象,而且始终只有一个
1)自定义类:具体类
2)无参构造方法私有化,目的:外界不能创建对象了
3)在当前类的成员位置:创建自己本身对象
4)在当前类中提供一个对外的公共的静态的功能,返回值是它本身
示例:
public class SingleObjectPattern {
public static void main(String[] args) {
/* Student s1 = new Student() ;
Student s2 = new Student() ;
//多例了;对象不同
System.out.println(s1==s2);
System.out.println(s1.getClass()==s2.getClass());*/ //获取字节码文件对象*/
//Student.s = null ;//没有意义:直接赋值空对象,不安全
Student s1 = Student.getInstance();
Student s2 = Student.getInstance();
Student s3 = Student.getInstance() ;
System.out.println(s1==s2) ; // true: 都是同一个student对象
System.out.println(s2==s3) ; // true
}
}
// 学生类
public class Student {//具体类
//成员位置:创建当前类的实例
private static Student s = new Student() ;
//无参构造私有化
private Student(){}
//在当前类中提供一个对外的公共的静态的功能,返回值是它本身
public static Student getInstance(){
return s ;//静态只能访问静态
}
}
6.3.2 懒汉式( 重点):
懒汉式:
1)自定义类:具体类
2)无参构造方法私有化,外界不能创建当前类对象
3)需要在成员位置,声明当前类型的变量
4)提供一个对外公共的并且静态的访问方法,返回值就是当前类本身
可能出现安全问题的一种单例模式
1)懒加载 (延迟加载) Mybatis框架:延迟加载(用户表和账户表)
2)可能存在一种多线程安全问题
面试官都会问:"什么懒汉式",如果保证懒汉式安全的?
同步方法,解决安全问题
public class SingleObjectPattern2 {
public static void main(String[] args) {
//测试
Teacher t1 = Teacher.getInstance() ;
Teacher t2 = Teacher.getInstance() ;
System.out.println(t1== t2) ; // true : 初始值都是null,所以new的同一个teacher
}
}
// 老师类
public class Teacher {//具体类
private static Teacher t ; //声明 默认值null
//无参构造方法私有化
private Teacher(){
}
//对外提供一个静态的公共访问方法,返回值是当前类本身
//每一个用户都需要请求getInstance():每一个用户相当于线程
//t1,t2,t3 都执行这个代码
public static synchronized Teacher getInstance(){ //静态的同步方法
//先判断
//如果当前t变量为null,说明并没有创建当前类实例 (在使用的时候才创建)
if(t == null){
t = new Teacher() ; //创建对象
}
return t ;
}
}
6.4 比较重要的设计原则
开闭原则:
对代码的修改关闭, 对扩展开放;
单一职责原则:
"低耦合,高内聚", 所有设计必须遵循.
接口分离:
接口和接口之间必须互相独立, 代表的是某个"模块".
依赖注入:
面向接口或者抽象类进行编程.
迪米特原则:
降低耦合性----反射.
7. Runtime 类
Runtime类:
每个Java程序都有自己的运行环境,通过Runtime创建的实例表示:当前系统的运行环境
7.1 示例:
public class RunTimeDemo {
public static void main(String[] args) throws IOException {
//创建当前运行环境的实例
Runtime runtime = Runtime.getRuntime() ;
//获取计算机cpu的处理器数量
//public int availableProcessors()
System.out.println(runtime.availableProcessors());
//操作指令
// public Process exec(String command) throws IOException
// System.out.println(runtime.exec("calc")); //打开计算器
// System.out.println(runtime.exec("mspaint")); //打开计算器
// runtime.exec("notepad") ;
// runtime.exec("qq") ;
//关机指令
// runtime.exec("shutdown -s -t 300") ; //300秒后关机
runtime.exec("shutdown -a") ; //取消关机
}
}
8. 递归
8.1 什么是递归
方法调用方法本身的一种现象! 而不是方法嵌套方法
8.2 递归的思想
1)必须定义一个方法(函数)
2)有一定的规律
3)必须有出口条件(结束条件) ,如果没有结束条件就是死递归!
构造方法不存在递归;
8.3 示例1: 求5的阶乘!
/* 分析: a)必须有一个方法
* b)有规律
* c)有结束条件(出口条件) */
public class DiGuiTest {
public static void main(String[] args) {
System.out.println("5的阶乘是:"+getResult(5)) ;
}
//定义方法
private static int getResult(int n) {//5
//判断n的值
if(n==1){
return 1 ; //出口条件(结束条件)
}else{
return n*getResult(n-1) ;//5*getResult(4) ;
}
}
}
示例2: 需求:
* 有一对兔子,从出生后第三个月起每个月产生一对兔子,小兔子长到第三个月后每个月又产生一对兔子;如果兔子都不死,
* 第二十个月兔子的对数是多少? (不死神兔)
* * 已知就是前两个月的兔子的对数都是1
public class DiGuiTest2 {
public static void main(String[] args) {
System.out.println("第二十个月的兔子对数是:"+getRabbit(20)) ;
}
//定义一个方法
public static int getRabbit(int n){//第n个月
//出口条件
//n==1或者n==2: 兔子的对数1
if(n==1|| n==2){
return 1 ;
}else{
return getRabbit(n-1) + getRabbit(n-2) ;
}
}
}
9. IO流的File类
描述的一个文件/目录的抽象路径名的形式
File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname) :直接描述地址路径
File(String parent, String child)
9.1 成员方法:
创建文件/创建文件夹相关的方法
public boolean createNewFile() throws IOException:创建文件,如果文件不存在,就会自动创建,创建了,返回true
public boolean mkdir():创建文件夹(目录),如果文件夹不存在,则自动创建;创建成功,返回true,否则false;
public boolean mkdirs():如果父目录不存在,会自动创建:针对带多级目录创建
public boolean delete():删除文件或者目录,如果File表示的目录,删除必须为空目录
如果没有带盘符呢,创建的文件或者文件夹去哪了?
默认就是在当前项目下
9.2 需求:
9.1.1 使用File表示E盘下的demo文件夹的a.txt文件
//方式1:File(String pathname)
File f1 = new File("E:\\demo\\a.txt") ;
//方式2:File(File parent, String child)
File f2 = new File("E:\\demo") ;
File f3 = new File(f2,"a.txt") ;
//方式3:File(String parent, String child)
File f4 = new File("E:\\demo","a.txt") ;
9.1.2 成员方法
public boolean createNewFile() throws IOException:创建文件,如果文件不存在,就会自动创建,创建了,返回true
public boolean mkdir():创建文件夹(目录),如果文件夹不存在,则自动创建;创建成功,返回true,否则false;
public boolean mkdirs():如果父目录不存在,会自动创建:针对带多级目录创建
public boolean delete():删除文件或者目录,如果File表示的目录,删除必须为空目录
如果没有带盘符呢,创建的文件或者文件夹去哪了?
默认就是在当前项目下
示例: 要删除当前D盘下所有的以.jpg结尾的文件
public class FileTest {
public static void main(String[] args) {
// 1)表示d盘
File file = new File("d:\\") ;
//2)需要将d盘下的里面的文件以及文件夹的名称都获取到
File[] fileArray = file.listFiles() ;
//3)先判断fileArray是否为空,防止空指针
if(fileArray!=null){
//遍历
for(File f :fileArray){
//f就是获取到的每一个file对象: 文件还是文件夹? 判断
//1)判断当前File对象所表示的抽象路径名是否是一个文件
if(f.isFile()){
//是文件
//需要满足,它的文件名称必须以.jpg结尾
if(f.getName().endsWith(".jpg")){
//就满足,删除文件
System.out.println(f.getName()+"----"+f.delete());
}
}
}
}
}
}
10. IO流
10.1 分类
IO流按流的方向:
输入 / 输出
按类型
字节:先有
字节输入 :InputStream
字节输出 :OutputStream
字符:后有
字符输入 :Reader
字符输出 :Writer
抽象类: 它的子类
InputStream XXXInputStream
OutputStream XXXOutputStream
Reader XXXReader
Writer XXXWriter
10.2 字节输入流
10.2.1 字节输入流读数据的方式
构造方法:
FileInputStream
public FileInputStream(String name) throws FileNotFoundException:创建文件字节输入流对象
成员方法:
read这些方法:都是阻塞式方法:只要文件没有读完,一直等待要读取!
abstract int read():一次读取一个字节,返回的读取的字节数
int read(byte[] b) :一次读取一个字节数组
A: 一次读取一个字节
public class FileInputStreamDemo3 {
public static void main(String[] args) throws IOException {
//创建文件字节输入流对象
//FileInputStream fis = new FileInputStream("fis.txt") ;//当前项目下的fis.txt
//读取的是当前项目下的java文件FileOutputStreamDemo2.java
FileInputStream fis = new FileInputStream("FileOutputStreamDemo2.java") ;
int by = 0 ;
while((by=fis.read())!=-1){
System.out.print((char)by);
}
//3)释放资源
fis.close() ;
}
}
B: 一次读取一个字节数组
public class FileInputStreamDemo4 {
public static void main(String[] args) throws IOException {
//读取是当前项目下的fis2.txt文件
//创建文件字节输入流对象
FileInputStream fis = new FileInputStream("fis2.txt") ;
byte[] bytes = new byte[1024] ;//提供字节缓冲区
//实际的长度:根据内容判断 :获取实际字节数
int len = 0 ;
//判断和获取一块使用
while((len=fis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len)); //每次从0开始获取实际字节数---转换成字符串
}
//释放资源
fis.close() ;
}
}
10.3 字节输出流
字节输出流--->如何在写入文件的时候,进行换行操作呢?
windows操作系统中:写入数据的时候,换行符号 "\r\n"
FileOutputStream的构造方法
public FileOutputStream(String pathname,boolean append):如果第二个参数为true,则自动后面追加
10.3.1 throws ,throw和 try…catch…finally:
1)使用位置不同
throws:跟在方法上,而且可以跟多个异常类名,中间逗号隔开
throw:方法体的语句中
2)处理方式不同
throws:它的处理交给调用者处理,谁调用这个带throws的方法,谁就需要进行处理
throw:它的处理交给方法体中逻辑语句处理:
if(xx){
}else{
throw new XXXException() ;
}
3)是否出现的可能性
throws:可能出现问题
throw:表示执行某段代码一定会出现这个异常
4)后面是否跟的是对象
throws:后面跟的异常类名
throw:后面的异常对象 new XXXExeption() ;
try...catch...finally:
捕获----通过业务逻辑代码(业务层代码:service层)
业务层数据service----->来源于dao层代码(数据访问对象: JDBC)
dao层代码抛出,业务service代码调用dao层代码,需要捕获异常!
try{
可能出现问题的代码;---一旦这有问题:Jvm 就会创建异常对象
} catch (异常类名 e) { //如果和当前异常类名匹配了
//处理异常
//System.out.println("数组角标越界了") ;
e.printStackTrace() ;//可以看到你自己写的代码哪块出错了以及底层原码
}finally{
//释放资源
}
try...catch
try...catch...catch...catch
try...finally
示例:
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {//自己使用可以去抛异常
//创建文件字节输出流对象
// FileOutputStream fos = new FileOutputStream("fos.txt") ;
//public FileOutputStream(String pathname,boolean append)
FileOutputStream fos = new FileOutputStream("fos.txt",true) ;
//写数据
//写一个字节数组
for(int x = 0 ; x < 10 ; x++){
fos.write(("hello"+x).getBytes()) ;
//换行符号 "\r\n"
fos.write("\r\n".getBytes());
}
//释放资源
fos.close() ;
}
}
10.4 面试题: 什么是阻塞式流:
阻塞式IO:BIO
IO即input/output,阻塞式IO指的是“一旦输入/输出工作没有完成,则程序阻塞,直到输入/输出工作完成”。
非阻塞式IO:
非阻塞式IO其实也并非完全非阻塞,通常都是通过设置超时来读取数据的。未超时之前,程序阻塞在读写函数上;超 时后,结束本次读取,将已读到的数据返回。
10.5 字节高效流
10.5…1 字节缓冲输入流:BufferedInputStream
构造方法:
BufferedInputStream(InputStream in)
成员方法:
使用InputStream的read的功能:
一次读取一个字节/一次读取一个字节数组
10.5.2 字节缓冲输出流:BufferedOutputStream
这个流仅仅是在内部提供了一个缓冲区(字节数组),针对文件的输出并且同时写入数据使用的还是底层流OutputStream
构造方法:
public BufferedOutputStream(OutputStream out):构造一个默认的缓冲区大小,通过底层流进行输出(写入数据)
一般情况:默认的缓冲区大小足够大了
示例:
public class BufferedOutputSteamDemo {
public static void main(String[] args) throws IOException {
//write();
//读
read() ;
}
//读取 当前项目下的bos.txt的内容
private static void read() throws IOException {
//创建字节缓冲输入流对象
//BufferedInputStream(InputStream in)
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("bos.txt")) ;
//一次读取一个字节
/* int by = 0 ;
while((by=bis.read())!=-1){
//展示出来
System.out.print((char)by);
}*/
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
//展示
System.out.println(new String(bytes,0,len));
}
//释放资源
bis.close() ;
}
private static void write() throws IOException {
//创建字节缓冲输出流对象
//public BufferedOutputStream(OutputStream out):形式参数是一个抽象类,需要子类对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")) ;
//写数据:使用的底层流的OutputStream的方法
bos.write("hello,BufferedStream".getBytes());
//释放资源
bos.close();
}
}
10.6 合并流: SequenceInpuStream
是InputStream的子类:它这个类只能操作源文件. 可以实现a.txt/b.txt/c.txt----->复制到一个文件中
构造函数:
public SequenceInputStream(Enumeration<? extends InputStream> e)
public SequenceInputStream(InputStream s1,InputStream s2):将两个字节输入流对象指向的文件进行合并
示例:
需求:
将当前项目下的FileOutputStreamDemo.java/FileOutputStreamDemo2.java
----复制到D:\EE_2110\day26\code\\Copy.java
当两个以上的文件进行复制,
public SequenceInputStream(Enumeration<? extends InputStream> e)
public class SequenceInpuStreamTest {
public static void main(String[] args) throws IOException {
//method1();
method2() ;//多个文件进行复制(两个以上的文件)
}
//方法二:
private static void method2() throws IOException {
//当前项目下的FileOutputStreamDemo.java/FileOutputStreamDemo2.java/InputAndOutputStreamCopy.java
//复制到D:\EE_2110\day26\code\\MyCopy.java
// public SequenceInputStream(Enumeration<? extends InputStream> e)
//创建Vector集合<InputStream>
Vector<InputStream> v = new Vector<>() ;
v.add(new FileInputStream("FileOutputStreamDemo.java")) ;
v.add(new FileInputStream("FileOutputStreamDemo2.java")) ;
v.add(new FileInputStream("InputAndOutputStreamCopy.java")) ;
//类似于迭代器:获取Enumeration枚举组件接口对象
Enumeration<InputStream> enumeration = v.elements();
//直接创建合并流对象
SequenceInputStream sis = new SequenceInputStream(enumeration) ;
//封装目的地文件
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("D:\\EE_2110\\day26\\code\\MyCopy.java")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=sis.read(bytes))!=-1){
bos.write(bytes,0,len) ;
bos.flush() ;
}
//释放
bos.close() ;
sis.close();
}
// 方法一: 合并流
private static void method1() throws IOException {
//创建两个字节文件输入流对象:指向两个文件
InputStream in = new FileInputStream("FileOutputStreamDemo.java") ;
InputStream in2 = new FileInputStream("FileOutputStreamDemo2.java") ;
//封装到合并流中
SequenceInputStream sis = new SequenceInputStream(in,in2) ;
//目的地文件D:\EE_2110\day26\code\Copy.java
//BufferedOutputStream流
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("D:\\EE_2110\\day26\\code\\Copy.java")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=sis.read(bytes))!=-1){
bos.write(bytes,0,len) ;
bos.flush() ;
}
//释放
bos.close() ;
sis.close();
}
}
10.7 字符流
字符流出现的原因:
在字节流基础上,它会提供字符集,解决乱码问题!
如果使用记事本打开一个文件,(文本文件):
优先采用的就是字符流!
图片/音频/视频:
只能使用字节流
10.7.1 字符输出流 Writer:
Writer:抽象类----->子类 :
字符转换输出流 OutputStreamWriter:里面包装的是字节流
构造方法:
OutputStreamWriter(OutputStream out) :使用平台默认的编码字符集进行编码--->写入数据
OutputStreamWriter(OutputStream out,String charsetName )使用指定的编码字符集进行编码--->写入数据
写的功能:
write(int ch):写一个字符
write(char[] chs):写字符数组
write(char[] chs,int index,int len):写字符数组的一部分
writer(String str):写字符串
writer(String str,int index,int len):写字符串的一部分
示例:
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
//创建字符输出流对象
//OutputStreamWriter(OutputStream out)
// Writer out = new OutputStreamWriter(new FileOutputStream("out.txt")) ;
/*OutputStreamWriter oos = new OutputStreamWriter(
new FileOutputStream("oos.txt")) ;*///默认平台 utf-8 编码
OutputStreamWriter oos = new OutputStreamWriter(
new FileOutputStream("oos.txt"),"GBK") ;// 指定GBK 编码
oos.write("hello,字符流");
//释放资源
oos.close() ;
}
}
10.7.2 字符输入流:Reader:
Reader:抽象类--->子类:
InputStreamReader:字符缓冲输入流:通向字节输入流的桥梁
构造方法:
InputStreamReader(InputStream in) :使用平台默认的字符集进行解码,里面包装的字节流
InputStreamReader(InputStream in,String charsetName):使用指定的字符集进行解码
读取:
读一个字符read()
读一个字符数组read(char[] chs)
示例:
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//需要读取oos.txt
//创建字符缓冲输入流对象
//InputStreamReader(InputStream in)
InputStreamReader isr = new InputStreamReader(
new FileInputStream("oos.txt"),"GBK") ;
//一次读取一个字符
/* int ch = 0 ;//字符数 ()
while((ch=isr.read())!=-1){
System.out.print((char)ch);
}*/
//一次读取一个字符数组
char[] chs = new char[1024] ;
int len = 0 ;
while((len=isr.read(chs))!=-1){
System.out.print((new String(chs,0,len))) ;
}
//释放资源
isr.close() ;
}
}
10.7.3 FileReader/FileWriter
使用Reader和Writer的子类进行操作:字符转换流(InputStreamReader/OutputStreamWriter)
复制文件,太麻烦了!
所以提供了便捷类: FileReader/FileWriter 可以直接操作文件
FileReader(String pathname):使用平台默认字符集:进行解码
FileWriter(String pathname)使用平台默认字符集:进行编码
示例: 复制文本文件
public class CopyFileTest {
public static void main(String[] args) {
//捕获异常
//创建字符输入流对象:操作源文件
FileReader fr = null ;
FileWriter fw = null ;
try {
fr = new FileReader("FileOutputStreamDemo.java") ;
fw = new FileWriter("My.java") ;
//读写操作
//一次读取一个字符数组
char[] chs = new char[1024] ;
int len = 0 ;//实际字符数
while((len=fr.read(chs))!=-1){
//写
fw.write(chs,0,len) ;
fw.flush() ;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fw!=null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr!=null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
10.8 字节缓冲流
10.8.1 BufferedWriter:字符缓冲输出流 (字符流的高效流)
仅仅提供缓冲区:
BufferedWriter(Writer out) :提供默认缓冲区大小,默认值足够大:8192个长度(底层是一种字符数组)
特有功能:
public void newLine() throws IOException :写入行的分隔符号
示例:
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt")) ;
//写数据
bw.write("hello");
//调用 public void newLine() throws IOException :写入行的分隔符号
bw.newLine() ;// 特有功能换行
bw.write("world");
bw.newLine() ;
bw.write("java");
bw.newLine() ;
//字符流的使用:在关闭前最好刷新流
//public void flush()
bw.flush() ;
//关闭
bw.close();
}
}
10.8.2 BufferedReader:字符缓冲输入流
提供默认的缓冲区大小 缓冲区足够大
BufferedReader(Reader in)
特有功能:
tring readLine() 读取一行内容
示例:
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象:读取bw.txt
BufferedReader br = new BufferedReader(new FileReader("bw.txt")) ;
//bw.txt---拷贝D盘:字符缓冲输出流 my.txt
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\my.txt")) ;
String line = null ;
while((line=br.readLine())!=null){
//System.out.println(line);
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
}
}
11. 面试题: lock和synchronized区别
synchronized是一个关键字 :
底层实现是一些指令:监视器锁(monitor),是一种悲观锁(独占锁),当某个线程持有这个锁的时候,其他线程访问不到;应用场景:少量同步代码使用synchronized;默认的锁的释放是需要等待执行完毕同步方法或者同步代码块
当一个线程持有这把锁的时候,其他线程不能够修改当前共享数据的!
执行效率就低了,而且可能如果资源类对象不是同一个,还可能会造成死锁!
Lock是接口:
基于子实现类(ReentrantLock)进行实现:可重入的互斥锁,底层实现原理采用CAS(比较和交换)算法,本质属于乐观锁;应用场景:针对大量的同步代码使用Lock;本身就提供具体的功能,lock()/unlock()
12. 网络编程(重点)
12.1 网络编程三要素
12.1.1 ip地址:
ip地址使用的一种"点分十进制法": 10.12.159.xxx
A类: 国家部门
前一个号段:网络号段
后面三个号段:主机号段
B类: 大学/教育部门:
前两个号段:网络号段
后面两个号段:主机号段
C类:私人地址 (家庭/单位...)
前三个号段:网络号段
后面这个号段:主机号段
windows系统:查看ipconfig
Linux系统:查看ifconfig
12.1.2 端口(port)
port:端口号:360软件---可以查看计算机中所有软件的端口!
范围:
0-65535
0-1024属于保留端口
常见端口:
tomcat服务器 默认:8080 (极域软件:端口号8080)
mysql:3306
redis(NOSQL数据库)客户端端口:6575
key:value
12.1.3 协议
12.1.3.1 UDP协议
1)属于不可靠协议
2)不需要建立连接通道,所以执行效率高,但是安全性低!
3)发送内容的时候,有大写数据!
4)数据大小限制 64kb
12.1.3.2 TCP协议/IP协议
1)属于可靠协议
2)需要建立连接通道,服务器端监听客户端连接,如果客户端没有跟服务器端口进行绑定,服务器一直等待,执行效率低,但 是安全性高
3)发送内容的时候,数据大小没有限制!
4)三次握手
12.2 InetAddress
静态方法:
public static InetAddress getByName(String host)throws UnknownHostException通过主机名称获取ip地址对象.
InetAddress的其他成员方法:
public String getHostName():获取ip地址对象所描述的主机的名称(计算机名称)
public String getHostAddress():获取ip地址:以字符串形式展示的
12.3 UDP协议方式
12.3.1 UDP协议方式发送端
1)创建UDP发送端的Socket对象
2)创建数据报包
3)发送
4)释放资源
socket对象.close()
12.3.2 UDP协议方式接收端
1)创建接收端的Socket对象
2)创建一个接收容器: 自定义一个缓冲区byte[] (创建数据包报)
3)接收
4)解析真实数据
5)展示ip和发送的内容
示例:
改进:UDP的发送端和接收端在一个窗口中进行聊天
UDP的发送端不断键盘录入数据,接收端不断的接收数据并且站数据数据:一个窗口中聊天呢
多线程: 使用多线程实现方式2来实现
分析
1)SendThread:发送端的线程资源类 实现 Runnable接口 ,实现run方法
2)ReveiveThread:接收端的线程资源类 实现Runnable接口 ,实现run方法汇总
3)创建资源类对象
4)创建Thread类对象,将上面的资源类对象作为参数传递
// 聊天室类
public class ChatRoomDemo {
public static void main(String[] args) throws SocketException {
//主线程中
//分别此处创建发送端的Socket以及接收端的Socket
//发送端的Socket
DatagramSocket sendDs = new DatagramSocket() ;
//接收端的Socket
DatagramSocket receiveDs = new DatagramSocket(8888) ;
//创建发送端的资源类对象
SendThread st = new SendThread(sendDs) ;
//创建接收端的资源类对象
ReceiveThread rt = new ReceiveThread(receiveDs) ;
//创建线程
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(rt) ;
t1.start() ;
t2.start() ;
}
}
//发送端线程
public class SendThread implements Runnable {
private DatagramSocket ds ;
public SendThread(DatagramSocket ds) {
this.ds = ds ;
}
@Override
public void run() {
try {
//创建一个BufferedReader流对象:键盘录入
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)) ;
String line = null ;
while((line=br.readLine())!=null){
//自定义结束条件
if("886".equals(line)) {
break;
}
DatagramPacket dp = new DatagramPacket(line.getBytes(),line.getBytes().length,
InetAddress.getByName("10.12.159.190"),8888) ;
//3)发送
//public void send(DatagramPacket p)
ds.send(dp) ;
}
} catch (IOException e) {
e.printStackTrace();
}
//4)释放资源
ds.close() ;
}
}
//服务端线程
public class ReceiveThread implements Runnable {
private DatagramSocket ds ;
public ReceiveThread(DatagramSocket ds) {
this.ds = ds ;
}
@Override
public void run() {
try {
while(true){
//创建一个接收容器: 自定义一个缓冲区byte[]
byte[] bytes = new byte[1024] ;
int length = bytes.length ;
//(创建数据包报)
DatagramPacket dp = new DatagramPacket(bytes,length) ;
//3)接收
// public void receive(DatagramPacket p)
ds.receive(dp);
//获取实际内容
String str = new String(dp.getData(),0,dp.getLength()) ;
//获取ip地址
// public InetAddress getAddress()
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from "+ip +",content is :"+str) ;
}
} catch (IOException e) {
e.printStackTrace();
}
//释放资
// ds.close() ;
}
}
12.4 TCP/IP 协议
12.4.1 TCP/IP 协议客户端
1)创建客户端的Socket对象
2)获取客户端所在的通道内的字节输出流OutputStream
3)给客户端通道内的流中写入数据
4)释放客户端的资源对象
12.4.2 TCP/IP 协议服务端
1)创建服务器端的Socket对象
2)监听客户端的连接 ---一旦监听到了,就获取到那个客户端的Socket
3)获取连接的客户端的通道内的输入流,读取数据
4)展示数据
5)释放资源
12.4.2.1 示例1: 复制本地图片到指定文路径
分析:
TCP客户端的图片,服务器端将文件复制,到指定文件中(当前项目下)
客户端将D盘下的高圆圆.jpg操作,服务器端将内容复制到当前项目下 mm.jpg
服务器端将内容复制到当前项目下 mm.jpg
发现问题:
复制过来的图片缺失,文件大小不一致,图片文件本身就存在缓存数据,需要通过字节输出流,强制刷新流中缓存的字节数
public void flush()
//服务端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 服务端socket
ServerSocket ss = new ServerSocket(12306) ;
//监听客户端
Socket socket = ss.accept();
//读取通道内过来的数据:封装通道内的输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()) ;
//输出到当前项目mm.jpg
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mm.jpg")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
//强制刷新
bos.flush() ;
}
//释放资源
bos.close() ;
ss.close() ;
}
}
//客户端
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建socket
Socket socket = new Socket("10.12.159.190",12306) ;
//BuferedInputStream 读取d盘下的图片文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d:\\高圆圆.jpg")) ;
//封装通道内的字节输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len) ;
//刷新
bos.flush() ;
}
//释放资源
bis.close();
socket.close() ;
}
}
12.4.2.2 示例2: 复制文本文件到指定路径
TCP客户端的文本文件,服务器端将文件复制到指定文件(当前项目下),加入服务器端反馈...
发现问题:
文件是已经完毕了,但是客户端并没有收到服务器端的反馈,服务器也没关闭; 出现了互相阻塞了
针对 文件的复制,如果是文本文件,我们通过BufferedReader的readLine的返回值是否为null,判断文件是否复制完毕
null.表示文件复制完了,但是对于Server服务器端来说,他不知道通道内的流中是否还有数据需要在写过来;等待客户端通知
我们的服务器端,文件已经复制完毕;客户端在等着服务器端的反馈的消息,所以就出现了互相等待;
解决方案:
1)自定义结束条件,只要服务器端读取到这个自定义内容,就打破了阻塞,可以给客户端反馈
如果当前这个文件:一开头"over",直接服务器端读取到这个就结束了,可能没复制完毕!
2)直接使用客户端的功能完成:
public void shutdownOutput():禁用客户端的输出流;不会写在流中写入数据了,服务器端就得到了通知,没有数据写过了
示例:
//客户端
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建socket
Socket socket = new Socket("10.12.159.190",12306) ;
//读取当前项目下的Copy.java文件, 使用BufferedReader 封装源文件
BufferedReader br = new BufferedReader(new FileReader("Copy.java")) ;
//封装通道内的字节输出流
//分步走
/*OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream)) ;*/
//一步走
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())) ;
//一次读取一行进行读写复制,将文件中的内容写到封装通道 的流对象中 bw
String line = null ;
while((line=br.readLine())!=null){ //readLine()阻塞式方法
bw.write(line) ;
bw.newLine() ; //换行
bw.flush() ; //刷新
}
//方式1
//自定义结束条件
/* bw.write("over") ;
bw.newLine() ;
bw.flush() ;*/
socket.shutdownOutput();
//读取服务器端的的反馈
//获取客户端所在的通道内的字节输入流
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024] ;
int len = inputStream.read(bytes);
String fkMsg = new String(bytes,0,len) ;
System.out.println("接收的服务器端反馈的数据是:"+fkMsg);
//释放资源
br.close() ;
socket.close() ;
}
}
//服务端
public class ServerDemo {
public static void main(String[] args) throws IOException {
//服务端socket对象
ServerSocket ss = new ServerSocket(12306) ;
//监听客户端
Socket socket = ss.accept(); //监听:阻塞式方法
//获取当前客户端的Socket通道内的输入流InputStream
//分步走
/*InputStream inputStream = socket.getInputStream();
//封装BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(inputStream)) */;
//一步走
//封装通道内的字节输入流
BufferedReader br = new BufferedReader(
new InputStreamReader(
socket.getInputStream())) ;
//将读取的内容,输出在当前项目下的My.java文件中
BufferedWriter bw = new BufferedWriter(new FileWriter("Hello.java")) ;
//一次读取一行
String line = null ;
while((line=br.readLine())!=null){ //阻塞式方法
/* if("over".equals(line)){
break ;
}*/
bw.write(line) ;
bw.newLine() ;
bw.flush();
}
//服务器端需要进行反馈
//获取通道内的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("文件已经复制完毕;".getBytes());
//释放资源
bw.close() ;
ss.close() ;
}
}
12.4.3 模拟网络聊天室
12.5 Object流
12.5.1 序列化
什么是序列化---->ObjectOutputStream:
就是在将Java对象(实体类:User/Product/Order...),变成一种流数据,他们里面的数据存储流中,就可以在网络(服务器集群中:共享)中传输!
构造方法:
public ObjectOutputStream(OutputStream out)
成员方法:
public final void writeObject(Object obj) throws IOException:写入一个实体对象
12.5.2 反序列化
什么是反序列化---->ObjectInputStream
就是将流数据(存储的一些相关的实体类数据)-----还原成Java对象!
构造方法:
public ObjectInputStream(InputStream in) throws IOException:将文件中的数据,进行读取
成员方法:
public final Object readObject()throws IOException,ClassNotFoundException
12.5.3 示例:
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// write() ;
read() ;
}
//反序列
private static void read() throws IOException, ClassNotFoundException {
//创建反序列化流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt")) ;
// public final Object readObject()
Object object = ois.readObject();
System.out.println(object) ;
//关闭资源
ois.close() ;
}
//序列化
private static void write() throws IOException {
//将Person对象中里面成员信息永久存储到指定文件中:序列化,前提条件:Person类型必须实现标记接口序列化!
//文件中乱码,不影响读取,通过反序列化---可以还原回来
//创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("oos.txt")) ;
//创建Person类对象
Person p = new Person() ;
p.name = "高圆圆" ;
p.age = 42 ;
//public final void writeObject(Object obj)
oos.writeObject(p) ;
//释放资源
oos.close() ;
}
}
12.5.4 异常: 未实现序列化接口的异常
java.io.NotSerializableException: com.qf.objectstream_01.Person:未实现序列化接口的异常
* 只有支持java.io.Serializable接口的对象才能写入流中.需要在Person类实现接口(标记接口)
* 刚才已经实现了反序列:将流中数据---读取出来,解析成对象了
* 但是,当我更改了Person类的成员的时候,再次反序列化报错
*
* java.io.InvalidClassException: com.qf.objectstream_01.Person;
* local class incompatible: stream classdesc serialVersionUID = 5810188958553179434,
* local class serialVersionUID = -45582393571180500
*
* 导致类的数据在文本文件中的前后的版本ID不一样 (序列化的时候---需要给类进行签名!)
*/
public class Person implements Serializable {
//固定的序列化版本Id
private static final long serialVersionUID = 6815664471916807752L;
public transient String name ;//transient可以标记某个成员变量不参与序列化
int age ;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
12.6 Properties:属性集合类(属性列表)
12.6.1 Properties
是一个Map,但是没有泛型,键和值都是String.
特有功能:
public Object setProperty(String key,String value):添加键和值
public Set<String> stringPropertyNames():获取所有 键的集合
public String getProperty(String key):获取键对应的值
12.6.1.1 功能使用, 示例:
public class PropertiesDemo {
public static void main(String[] args) {
//public PropertiesTest()
//创建属性集合类对象
Properties properties = new Properties() ;
/*//put方法
properties.put("张三","30") ;
properties.put("高圆圆","42") ;
properties.put("张佳宁","31") ;
properties.put("张三丰","50") ;
//遍历:keySet()---->获取所有的键
Set<Object> set = properties.keySet();
for(Object obj :set){
//通过键获取值
Object value = properties.get(obj);
System.out.println(obj+"---"+value);
}*/
/**
* public Object setProperty(String key,String value):添加键和值
* public Set<String> stringPropertyNames():获取所有 键的集合
* public String getProperty(String key):获取键对应的值
*/
//添加元素
properties.setProperty("1","张三") ;
properties.setProperty("2","李四") ;
properties.setProperty("3","王五") ;
properties.setProperty("4","赵六") ;
Set<String> keySet = properties.stringPropertyNames();//获取所有 键的集合
for(String key:keySet){
String value = properties.getProperty(key); //获取键对应的值
System.out.println(key+"---"+value);
}
}
}
12.6.1.2 成员方法
public void store(Writer writer, String comments):将属性列表中的内容保存指定的文件中
参数2:给属性列表一个描述
public void load(Reader reader):将文件中的内容加载属性列表中
示例:
public class PropertiesTest {
public static void main(String[] args) throws IOException {
// myLoad() ;
//有一个属性列表
Properties prop = new Properties() ;
prop.setProperty("孙杰","30") ;
prop.setProperty("臧永青","25") ;
prop.setProperty("陈荣昌","28") ;
prop.setProperty("陈琛","28") ;
prop.setProperty("遆子林","25") ;
prop.setProperty("于水利","26") ;
//将属性列表中的内容保存指定文件中
Writer w = new FileWriter("name.txt") ;
prop.store(w,"name's list") ; //store方法
//释放资源
w.close() ;
}
//将文件中加载进来,将属性列表中内容可以遍历
private static void myLoad() throws IOException {
Reader r = new FileReader("Lucky.txt") ;
//创建空的列表
Properties prop = new Properties() ;
//System.out.println(prop);
prop.load(r); //load方法
//System.out.println(prop);
//获取所有的值
/* Collection<Object> values = prop.values();
for(Object obj :values){
System.out.println(obj);
}*/
Set<String> keySet = prop.stringPropertyNames(); //获取键集合
for(String key :keySet){
String value = prop.getProperty(key); //根据键获取对应的值
System.out.println(key+"="+value) ;
}
}
}
12.6.2 如何读取src 文件夹路径下的properties文件
public class Test{
public static void main(String args[]){
//调用方法,读取properties文件
method();
}
//创建读取properties方法
private static void method() throws IOException {
//文件在哪个了中要读:
//1)获取当前了类的字节码文件对象
/*Class c = Test.class ;
//2)获取类加载器
ClassLoader classLoader = c.getClassLoader();
//3)通过类加载获取当前src目录下面的配置文件所在的输入流对象
InputStream inputStream = classLoader.getResourceAsStream("name.properties");*/
//链式编程,将文件读取到输入流中
InputStream inputStream =
Test.class.getClassLoader().getResourceAsStream("name.properties");
//创建Properties
Properties prop = new Properties() ;
// 将输入流中的文件加载进properties中
prop.load(inputStream);
System.out.println(prop) ;
}
}
12.7 面试题: UDP 和 TCP/IP 的区别
1)是否是一种可靠连接
UDP,不可靠 TCP可靠连接
2)是否安全,执行效率是否高
UDP不安全,不同步,所以执行效率高; TCP安全,同步,所以执行效率低
3)传输文件是否大小限制
UDP是有大小限制,发送端数据报包(DatagramPacket),一般不超过64kb;
而TCP发送文件数据没有限制,
底层通道的流(最基本的字节流)的方式;
TCP:采用三次握手(SYNC(信号)/ACK:确认字符)
12.8 针对文本文件的读写复制操作有几种,列出流类型即可
FileInputStream/FileOutputStream:一次读取一个字节
FileInputStream/FileOutputStream:一次读取一个字节数组
BufferedInputStream/BufferedOutputStream:一次读取一个字节
BufferedInputStream/BufferedOutputStream:一次读取一个字节数组
InputStreamReader/OutputStreamWriter:解决乱码(编码和解码必须字符集统一:采用默认平台字符集)
=
InputStreamReader/OutputStreamWriter: 一次读取一个字符数组
FileReader/FileWriter:一次读取一个字符
FileReader/FileWriter:一次读取一个字符数组
BufferedRreader/BufferedWriter:一次读取一个字符
BufferedRreader/BufferedWriter:一次读取一个字符数组不错v不那么,65特容易786546789087654321··123456980
0哦IP【】9redRreader/BufferedWriter:一次读取一行/每次写一行然后换行,,。、
12.9 IO流的分类有哪些(重点):
BIO
BufferedInputStream/BufferedOutputStream
BufferedReader(Reader r)
readLine():读取一行
1)BufferedReader(new FileReader("xxx.java/.txt..")) ;//操作源文件
2)BufferedReader(new InputStreamReader(System.in)) ; 键盘录入
BufferedWriter
FileReader
FileWriter
ObjectInputStream/ObjectOutputStream
DataInputStream/DataOutputStream :内存操作流 :底层一种将局部变量临时存储;
13. 数据库
13.1 数据库分类
存储数据的仓库,就是个文件夹
之前的存储:
数组,StringBuffer,集合 ,IO流永久存储---->耗时
一种关系型数据:mysql,oracel,SqlServer,Sqllite...
mysql:
轻量级 :中小型企业使用居多 (免费的)
oracle:
大型企业使用的居多 (收费的) 买人家的数据库软件+买人家第三方服务+买服务器...银行类的企业Oralce
13.2 DDL(数据库定义)语句:
show databases; -- 查询当前所有的数据库名称;
create database 库名; -- 创建自己的数据库
create database if not exists 库名;-- 判断再去创建
drop database 库名 -- 删除库
drop database if exists 库名; -- 判断再去删除
show create database 库名; -- 查询创建数据库的默认的字符集格式
show variables like '%character%'; -- 默认查询当前数据库中所有的带字符集的字符集格式
alter database myjavaee character set gbk ;
-- 修改数据库的默认的字符集
13.2.1 mysql字段类型:
varchar(字符个数) ---代表String 字符串
int类型:整数类型
int:默认的字符个数int(11):当前真实age年龄的长度 (所占的实际的字符数) 整数默认int(11)
举例: age int,
7
int(指定字符个数):int(4)
age int(4)
0007
date :日期 仅仅是日期
datetime :日期+时间
timestamp:时间戳 (即时时间)
当你在执行给这个字段插入某个指定的时候,当前的那个日期+时间
double(3,2) :这个字段值是3位数,小数后保留2位
举例:3.56
double
999.0
13.2.2 DDL之表的增删查改
use ee_2110; -- 创建表的前提,先选择库,进入到库中
create table 表名(
字段名称1 字段类型1,
字段名称2 字段类型2,
字段名称3 字段类型3
...
) ; -- 创建表的语法
desc 表名; -- 查询表的结构
alter table 表名 change 旧字段名称 新字段名称 数据类型;
-- 修改表的字段名称
(例: alter table student change gender sex varchar(5) ;)
alter table 表名 modify 字段名称 新的字段类型;
-- 修改字段类型
(例:alter table student modify address varchar(100);) alter table 表名 add 新的字段名称 类型;
-- 添加一个新的列(新的字段)
(例:alter table student add socre double(3,1);) alter table 表名 drop 字段名称;
(例: alter table student drop email;)
create table 新的表名 like 以前的表名;
-- 复制一张新的表跟以前的表结构相同
(例: create table teacher like student ;) alter table 以前的表名 rename to 新的表名;
-- 修改的表的名称
(例: alter table teacher rename to tea;) drop table 表名 ;
drop table if exists 表名; -- 删除表