基础

MAP集合

特点:

1.map集合是一个双列集合,一个元素包含两个值(一个key,一个value)

2.map集合中的元素,key和value的数据类型可以相同,可以不同

3.Map集合中的元素,key是不允许重复的,value是可以重复的

4.Map集合中的元素,key和value是11对应的

常用子类:

HashMap集合 HashMap<K,V>

HashMap<String, String> map = new HashMap<String, String>();

特点:

1.HashMap集合底层是哈希表,查询的速度特别的快(JDK1.8之前:数组+单向链表,JDK1.8之后:数组+单项链表/红黑树 提高了查询的速度)

2.HashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致

3.HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快

可以存储null值,null键

LinkedHashMap<K,V>

特点:

1.LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序)

2.LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的

LinkedHashMap (key不允许重复 有序)

LinkedHashMap<String,String> linked=new LinkedHashMap<>();
linked.put(“aaa”,“ccc”);
linked.put(“bbb”,“ddd”);
Set<Map.Entry<String,String>> set=linked.entrySet();

//使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中

Hashtable<K,V>

Hashtablie:底层是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢

不能存储null值,null键

Hashtablie<String,String> map=new Hashtable<>();

tips:

Map接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数据类型可以相同,也可以不同。

Map常用方法

public V put(K key, V value) :

把指定的键与指定的值添加到Map集合中。 返回值:v

​ 存储键值对的时候,key不重复,返回值是null

​ 存储键值对的时候,key重复,会使用新的value替换map中重复的value,返回被替换的value值

​ Map<String,String> map=new HashMap<>();
​ String s1=map.put(“李晨”,“范冰冰1”);
​ sout(s1)//返回null;
​ String s2=map.put(“李晨”,“范冰冰2”);
​ sout(s2)//返回范冰冰1
​ sout(map)//返回李晨 范冰冰2

public V remove(Object key) :

把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。返回值:v

key存在,v返回被删除的值

key不存在,v返回null
Map<String,Integer> map=new HashMap<>();
​ map.put(“赵丽颖”,“168”);
​ map.put(“林志玲”,“178”);
​ map.put(“杨颖”,“165”);
​ String s1=map.remove(“林志玲”);//返回178

public V get(Object key)

根据指定的键,在Map集合中获取对应的值。
Map<String,Integer> map=new HashMap<>();
​ map.put(“赵丽颖”,“168”);
​ map.put(“林志玲”,“178”);
​ map.put(“杨颖”,“165”);
Integer v1=map.get(“杨颖”)//返回165
Integer v2=map.get(“a”)//key不存在返回null

boolean containsKey(Object key)

判断集合中是否包含指定的键。
Map<String,Integer> map=new HashMap<>();
​ map.put(“赵丽颖”,“168”);
​ map.put(“林志玲”,“178”);
​ map.put(“杨颖”,“165”);
boolean b1=map.containsKey(“赵丽颖”);
sout(b1)//返回true
boolean b1=map.containsKey(“赵颖”);
sout(b2)//返回false

public Set keySet() :

获取Map集合中所有的键,存储到Set集合中。
Map<String,Integer> map=new HashMap<>();
​ map.put(“赵丽颖”,“168”);
​ map.put(“林志玲”,“178”);
​ map.put(“杨颖”,“165”)
Set set=map.keySet();
Integer value=map.get(key);
while(it.hasNext()){
String key=map.get(key);
sout(key+value)
}

public Set<Map.Entry<K,V>> entrySet() :

获取到Map集合中所有的键值对对象的集合(Set集合)。

实现步骤:

1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中

2.遍历Set集合,获取每一个Entry对象

3.使用Entry对象中的方法getKey()和getValue()获取键与值Map<String,Integer> map=new HashMap<>();
​ map.put(“赵丽颖”,“168”);
​ map.put(“林志玲”,“178”);
​ map.put(“杨颖”,“165”)
//1.
Set<Map.Entry<String,Integer>> set=map.entrySet();
//2.
Iterator<Map.Entry<String,Integer>> it=set.iterator();
while(it.hasNext()){
Map.Entry<String,Integer> entry= it.next();
//3.
String key =entry.getKey();
Integer value=entry.getValue();

}

异常

Java异常处理的五个关键字:try、catch、fifinally、throw、throws

异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止

异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:

java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception

Throwable体系:

Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。 必须修改源代码程序才能执行

/*error
        int[] arr=new int[1024*1024*1024];
        //OutOfMemoryError: Java heap space内存溢出错误
        //必须修改代码使创造的数组小一点
*/

Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎

编译期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)

/*编译期异常
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd");
Date date= sdf.parse("1999-09-09");
System.out.println(date);*/

RuntimeException:运行期异常

/*运行期异常
int[] arr={1,2,3};
try{
    //可能会出现异常的代码
    System.out.println(arr[3]);
}
catch (Exception e){
    //异常的处理逻辑
    System.out.println(e);
}

 */ 

Throwable方法

public void printStackTrace() :打印异常的详细信息。 (JVM默认打印此方法,其打印的信息时最全面的)

包含了异常的类型*,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace*。

public String getMessage() :获取发生异常的原因。

提示给用户的时候*,*就提示错误原因。

public String toString() :获取异常的类型和异常描述信息(不用)

异常产生过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tx3Cplq3-1614606989359)(F:\ceshi\异常产生过程.png)]

关键字

throw

throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行

使用格式:throw new 异常类名(参数); //

throw new NullPointerException(“要访问的arr数组不存在”);

throw new ArrayIndexOutOfBoundsException(“该索引在数组中不存在,已超出范围”);

注意:

1.throw关键字必须写在方法的内部

2.throw关键字后面new的对象必须是Exception或Expection的子类对象

3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象

​ throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理默认交给JVM处理(打印异常对象,中断程序)

​ throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try…catch

public static void main(String[] args) {
    int[] arr = {1,2,3,4};
    int e = getElement(arr, -1);
}
public static int getElement(int[] arr, int index) {
    if (arr==null){
        throw new NullPointerException("传递的数组值为空");//空指针异常是运行期异常
    }
    if(index<0||index>arr.length){
        throw new IndexOutOfBoundsException("传递的索引超出使用范围");//运行期异常
    }
    int ele=arr[index];
    return ele;

}

Objects非空判断

public static T requireNonNull(T obj) :查看指定引用对象不是null。

public static void main(String[] args) {
    methoddemo2(null);
}

private static void methoddemo2(Object obj) {
    Objects.requireNonNull(obj);
}

声明异常throws

throws:异常处理的第一种方式,交给别人处理

声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理,那么必须通过throws进行声明,让调用者去处理

作用:当方法内部抛出异常对象的时候,我们就必须处理这个异常对象

可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理(中断处理)

使用方式:在方法声明时使用

修饰符 返回值类型 方法名(参数列表) throws 异常类名1,异常类名2…{

throw new AAAException(“产生原因”);

}

注意:

1.throws关键字必须写在方法声明处

2.throw是关键字后边声明的异常必须是Exception或者说Exception的子类

3.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常

public static void main(String[] args) throws FileNotFoundException,IOException {
    readFile("c:\\a.tx");
}

private static void readFile(String fileName)throws FileNotFoundException, IOException {//
    if(!fileName.equals("c:\\a.tx")){
        throw new FileNotFoundException("错误");//文件路径异常
    }
    if(!fileName.endsWith(".txt")){
        throw new IOException("后缀名不对");
    }
    System.out.println("文件路径正确");
}

try…catch

try…catch异常处理的第二种方式,自己处理异常

格式:try{

​ 可能产生异常的代码

}catch(定义一个异常的变量,用来接收try中抛出的异常对象){

​ 异常的处理逻辑,产生异常对象之后怎么处理异常对象

​ 一般在工作中,会把异常的信息记录在日志中

}

…//catch可以有多个

catch(异常类名 变量名){

}

注意:

1.try中可能会抛出多个异常对象,那么就会执行catch中的异常处理逻辑,执行完毕catch中的异常处理逻辑,继续执行try…catch之后的代码

2.如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕的catch中的处理逻辑,继续执行try…catch之后的代码

如果try中没有产生异常,那么就不会执行catch中的处理逻辑,执行完try中的代码,继续执行try…catch之后的代码

如果异常出现的话,会立刻终止程序,所以我们得处理异常:

\1. 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。

\2. 在方法中使用try-catch的语句块来处理异常。

try-catch的方式就是捕获异常。

捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

finally代码块

fifinally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而fifinally就是解决这个问题的,在fifinally代码块中存放的代码都是一定会被执行的。

fifinally的语法:

try…catch…fifinally:自身需要处理异常,最终还得关闭资源。

注意:fifinally不能单独使用。

异常捕获处理:

1.多个异常分别处理

2.多个异常一次捕获,多次处理

3.多个异常一次捕获一次处理

//1.多个异常分别处理
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
    e.printStackTrace();
}
try{
    List<Integer> list=List.of(1,2,3);
    list.get(3);
}catch (IndexOutOfBoundsException e){
    e.printStackTrace();
}
//2.一次捕获多次处理
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);
    List<Integer> list=List.of(1,2,3);
    list.get(3);
}catch (ArrayIndexOutOfBoundsException e){
    e.printStackTrace();
}catch (IndexOutOfBoundsException e){
    e.printStackTrace();
}

注意:一个try 多个catch注意事项

​ catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须卸载上边,否则将会报错ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException

//多个异常一次捕获一次处理
 try {
     int[] arr = {1, 2, 3};
     System.out.println(arr[3]);
     List<Integer> list=List.of(1,2,3);
     list.get(3);
 }catch (Exception e){
     e.printStackTrace();
 }

注意:

1.运行时异常被抛出可以不处理。即不捕获也不声明抛出。

2.如果fifinally有return语句,永远返回fifinally中的结果,避免该情况.

3.如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。

4.父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

自定义异常:

什么是自定义异常类: 在开发中根据自己业务的异常情况来定义异常类.
自定义一个业务逻辑异常: RegisterException。一个注册异常类
格式:public calss XXXException extends Exception|RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法}
注意:
1.自定义异常类一般是以Exception结尾,说明该类是一个异常类
2.自定义异常类必须继承Exception或者RuntimeException
            继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部跑出了这个编译期异常就必须处理这个异常要么throws要么try..catch
            继承RuntimeException 那么自定义的异常类就是一个运行期异常,无需处理交给虚拟机(中断处理)
public class RegisterException extends Exception {
    //添加一个空参数的构造方法
    public RegisterException(){
        super();
    }
    //添加一个带异常信息的构造方法查看源码发现所有的异常类都会有一个带异常信息的构造方法,
    // 方法内部会调用父类带异常信息的构造方法让父类来处理这个异常信息
    public RegisterException(String message){
        super(message);
    }
public class RegisterPracticedemo {
    private static String[] name={"aaa","bbb"};
    public static void main(String[] args) {
        try {
           // checkuser("aaa");
            checkuser("aaa");
        } catch (RegisterException e) {
            e.printStackTrace();
        }

    }
    //判断当前注册账号是否存在
    // 因为是编译期异常,又想调用者去处理 所以声明该异常
    public static boolean checkuser(String uname) throws  RegisterException {
        for (String s:name
             ) {
            if (s.equals(uname)){
                throw new RegisterException("账号存在");
            }
        }
        return true;
    }
}

线程

并发:指两个或多个事件在同一个时间段内发生。 (交替执行)

并行:指两个或多个事件在同一时刻发生(同时发生)。(同时进行)

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多

个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创

建、运行到消亡的过程。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程

中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HBQFsaD1-1614606989362)(F:\ceshi\04_线程概念.bmp)]

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为 抢占式调度。

多线程

每创建一个线程类对象就会多一条通往cpu的路径

好处:多个线程之间互不影响(线程在不同的栈空间)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aMSjjFc0-1614606989364)(F:\ceshi\01_多线程随机性打印结果.bmp)]

创建方式:1

1:创建Thread类的子类

java.lang.Thread 是描述线程的类 想要实现多线程就必须继承Thread类

实现步骤:1:创建Thread类的子类

​ 2:在创建的Thread类的子类中重写run()方法,设置线程任务(开启线程要做什么)

​ 3:创建Thread类的子类对象

​ 4:调用Tread类中的start()方法,开启新的线程,执行run()方法

void start()使线程开始执行,JVM虚拟机调用该线程中的run()方法

结果是两个线程并发的运行,当前的线程(main线程)和另一个线程(创建的新线程,执行其run()方法)

多次启动一个线程是非法的,当这个线程结束运行后,不能重新启动

MyThread mt=new MyThread();
mt.start();

start()方法用来开辟新的栈空间来执行run()方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CSkm2fj0-1614606989366)(F:\ceshi\02_多线程内存图解.bmp)]

创建方式:2

1.创建一个Runnable接口的实现类
2.在实现类中重写run方法,并设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start()方法,开启新的线程执行run()方法
 //1.创建一个Runnable接口的实现类
public class RUNnableimpl implements Runnable {
    //创建线程的另一种方法是声明实现 Runnable 接口的类。
    //该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
    //2.在实现类中重写run方法,并设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }

    }
}
public static void main(String[] args) {
    //Thread(Runnable target)分配新的 Thread 对象。
    //Thread(Runnable target, String name)分配新的 Thread 对象。
    //3.创建一个Runnable接口的实现类对象
    RUNnableimpl r = new RUNnableimpl();
    //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    Thread t=new Thread(r);
    //5.调用Thread类中的start()方法,开启新的线程执行run()方法
    t.start();
}

Thread类构造方法:

public Thread() :分配一个新的线程对象。

public Thread(String name) :分配一个指定名字的新的线程对象。

public Thread(Runnable target) :分配一个带有指定目标新的线程对象。

public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

Thread常用方法:

public String getName() :获取当前线程名称。

//1.使用Thread中的getName()方法
//    String getName()返回线程的名称
public class MyThread extends Thread {
    @Override
    public void run() {
        String name = getName();
        System.out.println(name);
    }
}
 //2.可以先获取当前正在执行的线程,使用线程的方法getName()获取线程的名称
 //       static Thread currentThread()
 //         返回对当前正在执行的线程对象的引用。
public class MyThread extends Thread {
    @Override
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println(t);
    }
}
public static void main(String[] args) {/*
        //获取线程的名称
        //1.使用Thread中的getName()方法
        String getName()返回线程的名称
        //2.可以先获取当前正在执行的线程,使用线程的方法getName()获取线程的名称
        static Thread currentThread()
          返回对当前正在执行的线程对象的引用。
*/
        MyThread mt=new MyThread();
        mt.start();
        new MyThread().start();
        new MyThread().start();
    }

public void set(String name)

public class MyThreaddemo1set extends Thread {
   
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
 public static void main(String[] args) {
        MyThreaddemo1set mts=new MyThreaddemo1set();
        mts.setName("阿卡丽");
        mts.start();
    }

public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

public void run() :此线程要执行的任务在此处定义代码。

public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停

public static void main(String[] args) {
    for (int i = 1; i <=60 ; i++) {
        //模拟秒表
        System.out.println(i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Thread类和Runnable的区别

  1. 适合多个相同的程序代码的线程去共享同一个资源。

  2. 可以避免java中的单继承的局限性。

    ​ 一个类只能继承一个类,继承了Thread就不能继承其他类

  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

匿名内部类方式实现线程的创建

匿名:没有名字

内部类:写在其它类内部的类

匿名内部类的作用:简化代码

​ 把子类继承父类,重写父类的方法,创建子类对象一步完成

​ 把实现类实现接口,重写接口中的方法,创建实现类的方法一步合成

匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式: new 父类/接口(){

​ 重写父类/接口的方法

};

public static void main(String[] args) {
  //线程的父类是Thread
    new Thread(){
        @Override
        public void run() {
            for (int i = 0; i <20 ; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }.start();
}
//线程的接口Runnable
Runnable r=new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"1");
    }
};
new Thread(r).start();
//简化接口的方式
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"2");
    }
}).start();

线程安全问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEQfp22S-1614606989369)(C:\Users\22236\Desktop\java学习资料\深入\03_线程安全问题的概述.bmp)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-it4GNUzE-1614606989371)(C:\Users\22236\Desktop\java学习资料\深入\04_线程安全问题产生的原理.bmp)]

/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
 */
public class demotest {
    //创建Runnable接口的实现类对象
    public static void main(String[] args) {
        RunnableImplqa run=new RunnableImplqa();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0=new Thread(run);
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);
        t0.start();
        t1.start();
        t2.start();

    }
}
public class RunnableImplqa implements Runnable {
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){//票存在
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //卖票
                System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket+"张票");
                ticket--;
            }
        }
    }

线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决

案例:窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU 资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象

有三种方式完成同步操作:

\1. 同步代码块。

\2. 同步方法。

\3. 锁机制

同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式: synchronized(同步锁){

需要同步操作的代码

}

注意:

1.同步代码块中的锁对象,可以使用任意的对象

2.但是必须保证多个线程使用的锁是同一个

3.锁对象作用:

​ 把同步代码块锁住,只让一个线程在同步代码块中执行

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

\1. 锁对象 可以是任意类型。

\2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

public class RunnableImplqa implements Runnable {
    private int ticket=100;
    Object lock = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (lock) {
                if (ticket > 0) {//票存在
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //卖票
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}
同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:修饰符 synchronized 返回值类型 方法名(参数列表){

可能会产生线程安全问题的代码

​ }

例如:

public synchronized void method(){

可能会产生线程安全问题的代码

}

定义一个同步方法 同步方法也会把方法内部的代码锁住 只会让一个线程执行 同步方法的锁对象是实现类对象 new demo1mothod() 也就是this

public  void sellmothed(){
    synchronized (this){
        if(num>0){//有票可以卖
            System.out.println(Thread.currentThread().getName()+"卖出"+num);
            num--;
        }
    }
}
public class demo1mothod implements Runnable {
    private int num=100;
    @Override
    public void run() {
        while (true){
        sellmothed();}
    }
    public synchronized void sellmothed(){
        if(num>0){//有票可以卖
            System.out.println(Thread.currentThread().getName()+"卖出"+num);
            num--;
        }
    }
}

静态同步方法的锁对象是本类的class属性

public class demo1mothod implements Runnable {
    private int num=100;
    private static int num1=100;
    @Override
    public void run() {
        while (true){
        sellmothed1();}
    }
    /*public  void sellmothed(){
        synchronized (this){
            if(num>0){//有票可以卖
                System.out.println(Thread.currentThread().getName()+"卖出"+num);
                num--;
            }
        }
    }

     */
    public static synchronized void sellmothed1(){
        if(num1>0){//有票可以卖
            System.out.println(Thread.currentThread().getName()+"卖出"+num1);
            num1--;
        }
    }
    public synchronized void sellmothed(){
        if(num>0){//有票可以卖
            System.out.println(Thread.currentThread().getName()+"卖出"+num);
            num--;
        }
    }

}
Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

public void lock() :加同步锁。

public void unlock() :释放同步锁

使用步骤:

1.在成员位置创建一个ReentrantLock对象

2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

3…在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

public class demo1 implements Runnable {
    private int num=100;
    Lock lock=new ReentrantLock();//在成员位置创建一个ReentrantLock对象
    @Override
    public void run() {
        while(true){
            lock.lock();//锁
            if(num>0){//有票
                System.out.println(Thread.currentThread().getName()+"sell"+num);
                num--;
            }
            lock.unlock();//释放
        }
    }
}

线程状态

java.lang
枚举 Thread.State

  • NEW
    至今尚未启动的线程处于这种状态。
  • RUNNABLE
    正在 Java 虚拟机中执行的线程处于这种状态。
  • BLOCKED
    受阻塞并等待某个监视器锁的线程处于这种状态。
  • WAITING
    无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
  • TIMED_WAITING
    等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
  • TERMINATED
    已退出的线程处于这种状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EMDQCcJW-1614606989373)(C:\Users\22236\Desktop\java学习资料\深入\线程的状态图.bmp)]

等待唤醒案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-47IpdsFj-1614606989374)(C:\Users\22236\Desktop\java学习资料\深入\06_等待唤醒案例分析.bmp)]

java.lang.Object
Object类中的方法
void notify()
唤醒在此对象监视器上等待的单个线程。
void wait() 
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
public class demo1 {
    public static void main(String[] args) {
        /*
        等待唤醒案例:线程之间的通信
            创建一个顾客线程(消费者):告知老板想要吃的包子的种类和数量,
            调用wait()方法,放弃cpu的执行,进入到WAITING状态(无线等待)
            创建一个老板线程(生产者),花了5s做包子,做好包子后调用notify方法,唤醒顾客吃包子
         注意:
            顾客和老板必须使用同步代码块包裹起来,保证等待和唤醒只用一个在执行
            同步使用的锁对象必须保证唯一
            只有锁对象才能调用wait和notify方法
           Object类中的方法
            void notify()
          唤醒在此对象监视器上等待的单个线程。
          void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
         */
        //创建锁对象保证唯一
        Object obj=new Object();
        //创建一个顾客线程
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    System.out.println("告知老板的包子种类和数量");
                    //调用wait方法,放弃cpu的执行,进入到WAITING状态(无线等待)
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //唤醒之后的代码
                    System.out.println("包子已经做好了");
                }
            }
        }.start();
        //创建一个老板的线程
        new Thread(){
            @Override
            public void run() {
                //花费5s做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(obj){
                    System.out.println("告知顾客吃包子");
                    obj.notify();
                }
            }
        }.start();
    }
}
/*
进入到TimeWaiting(计时等待)有两种方式
        1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
        2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,
        还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
 */
public class demo2 {
    public static void main(String[] args) {
        //创建一个object
        Object obj=new Object();
        //创建一个顾客线程
        new Thread(){
            @Override
            public void run() {
                while(true) {
                    synchronized (obj) {
                        System.out.println("说明要吃的");
                        try {
                            obj.wait(5000);
                            System.out.println("1");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();

    }
}

等待与唤醒机制的代码实现

baozi类

public class baozi {
    //皮
    String pi;
    //馅
    String xian;
    //包子的状态,有true和false,设置初始值为false
    boolean flag=false;
}

包子铺老板类

public class boos extends Thread {
    private baozi bz;
    public  boos(baozi bz){
        this.bz=bz;
    }
    //设置线程任务:生产包子
    @Override
    public void run() {
        int count=0;
        while (true) {
            synchronized (bz){
                if(bz.flag==true){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后执行
                if(count%2==0){
                    bz.pi="薄皮";
                    bz.xian="三鲜";
                }else {
                    bz.pi="冰皮";
                    bz.xian="猪肉";
                }
                count++;
                System.out.println("正在生产包子"+bz.pi+bz.xian+"包子");
                try {
                    Thread.sleep(3000);//生产时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bz.flag=true;
                bz.notify();
                System.out.println("做好了"+bz.pi+bz.xian+"包子");
            }
        }

    }
}

吃包子的顾客类

public class customer extends Thread {
    private baozi bz;
    public customer(baozi bz){
        this.bz=bz;
    }
    @Override
    public void run() {
        //线程任务是吃包子
        while(true){
            synchronized(bz){
                if (bz.flag==false){
                    try {
                        bz.wait();//没有等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒之后
                System.out.println("吃"+bz.pi+bz.xian+"包子");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bz.flag=false;//吃完之后
                bz.notify();
                System.out.println("吃完了"+bz.pi+bz.xian+"该做下一个了");
            }
        }
    }
}

实现类

public class demotest {
    public static void main(String[] args) {
        baozi bz=new baozi();//创建包子对象
        new boos(bz).start();
        new customer(bz).start();
    }
}

线程池

java.util.concurrent 类 Executors

java.lang.Object
  java.util.concurrent.Executors

线程池原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-monm1sam-1614606989376)(C:\Users\22236\Desktop\java学习资料\深入\线程池原理.bmp)]

**线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0zUDpEc-1614606989377)(C:\Users\22236\Desktop\java学习资料\深入\02_线程池.bmp)]

static ExecutorService newFixedThreadPool(int nThreads) 
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

参数:

int nThreads:创建线程池中包含的线程数量

返回值:

ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)

Executors类中有个创建线程池的方法如下:

接口 ExecutorService

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

    Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

使用线程池的步骤:

  1. 使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool()生产一个指定线程数量的线程池

  2. 创建一个类,实现Runnable接口,重写run()方法,设置线程任务

  3. 调用ExcutorService中的方法submit()传递线程任务(实现类),开启线程,执行run方法

  4. 调用ExecutorService中的方法shutdown()销毁线程池(一般不做)。

    public class DemoImpl implements Runnable  {
        @Override
        public void run() {
            //2.重写run方法
            System.out.println(Thread.currentThread().getName());
        }
    }
    
public class demo1 {
    public static void main(String[] args) {
        //1.创建线程池
        ExecutorService es=Executors.newFixedThreadPool(2);
        //3.调用ExecutorService中的方法submit,传递线程任务(实现类),
        // 开启线程执行run()方法
        //线程池会一直开启,使用完了线程,会自动把县城归还给线程池,线程可以继续使用
        es.submit(new DemoImpl());
        es.submit(new DemoImpl());
        es.submit(new DemoImpl());
        //4.关闭线程池
        es.shutdown();
    }
}

Lambda表达式

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

面向对象的思想:

​ 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

函数式编程思想:

​ 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

new Thread(()-> {          System.out.println(Thread.currentThread().getName());
        }
).start();

格式:

Lambda省去面向对象的条条框框,格式由3个部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

(参数类型 参数名称) -> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。

  • ->是新引入的语法格式,代表指向动作,传递的意思,把参数传递给方法体

  • 大括号内的语法与传统方法体要求基本一致。

    无返回值的

public class DemoCooktest {
    public static void main(String[] args) {
        /*在下面的代码中,请使用Lambda的**标准格式**调用`invokeCook`方法,
        打印输出“吃饭啦!”字样:
         */
        /*invokeCook(new Cook() {
            @Override
            public void makeFood() {
                System.out.println("吃饭了");
            }
        });*/
        //使用Lambda表达式简化匿名内部类的书写
        //因为makeFood*()是个空参数所以是()->
        invokeCook(()->{
            System.out.println("吃饭了");
        });
    }
    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

public interface  Cook {
    /*
    给定一个厨子`Cook`接口,内含唯一的抽象方法`makeFood`,且无参数、无返回值。
     */
    public abstract void makeFood();
}

有返回值的

Person p1=new Person("a",11);
        Person p2=new Person("b",80);
        Person p3=new Person("c",66);
        Person p4=new Person("d",61);
        Person p5=new Person("e",69);
        Person[] arr={p1,p2,p3,p4,p5};
        /*Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });*/
        //使用lambda表达式
        Arrays.sort(arr,(Person o1, Person o2) -> {
            return o1.getAge()-o2.getAge();
        } );
        for (Person p:arr
             ) {
            System.out.println(p);
        }
    }
}

省略规则

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略;
  2. 如果小括号内有且仅有一个参,则小括号可以省略;
  3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。

public static void main(String[] args) {
invokeCook(() -> System.out.println(“吃饭啦!”));
}

Lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
  2. 使用Lambda必须具有上下文推断
    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

File类

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

重点记住的三个单词:

file:文件

directory:文件夹/目录

path:路径

public class Demo1 {
    public static void main(String[] args) {
        /*
static String pathSeparator
          与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar
          与系统有关的路径分隔符。
static String separator
          与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar
          与系统有关的默认名称分隔符。
          操作路径:路径不能写死了
          C:\develop\a\a.txt    windows
          C:/develop/a/a.txt    linux
          "C:"+"File.pathSeparator"+"develop"+"File.pathSeparator"+a+"File.pathSeparator"+"a.txt "
         */
        String pathSeparator= File.pathSeparator;
        System.out.println(pathSeparator);//路径分隔符 windows为分号,linux是冒号
        String separator=File.separator;
        System.out.println(separator);//文件名称分隔符
    }
}

路径:
    绝对路径:是一个完整的路径
        以盘符开始的路径
        C:\\a.txt
    相对路径:是一个简化的路径
        相对指的是相对于当前的项目根目录(C:\\Users\\itcast\\Ideaprojects\\sh.txt) 如果使用的是当前项目的根目录,路径可以简化书写(C:\\Users\\itcast\\Ideaprojects\\sh.txt)-->简化为sh.txt
注意:
盘符路径不区分大小写
路径中的文件名称分隔符windows是反斜杠反斜杠也是转义字符,两个反斜杠代表一个普通的反斜杠

构造方法:

  • public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。

    /*
    File(String pathname)
      通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例
     */
    String pathname="C:\\Users\\22236\\Desktop\\study course\\a.txt";
    File f1=new File(pathname);
    System.out.println(f1);
    
    
  • public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。

    /*
    File(String parent, String child)
      根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例
     */
    String parent="C:\\Users\\22236\\Desktop\\study course";
    String child="a.txt";
    File f2=new File(parent,child);
    System.out.println(f2);
    
    
  • public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

/*
File(File parent, String child)
  根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
 */
File paths=new File("C:\\Users\\22236\\Desktop\\study course");
File f3=new File(paths,"a.txt");
System.out.println(f3);

File类的常用方法:

获取功能的方法

  • public String getAbsolutePath() :返回此File的绝对路径名字符串。

  • public String getPath() :将此File转换为路径名字符串。

  • public String getName() :返回由此File表示的文件或目录的名称。

  • public long length() :返回由此File表示的文件的长度。

    public class FileGet {
        public static void main(String[] args) {
            File f = new File("d:/aaa/bbb.java");
            System.out.println("文件绝对路径:"+f.getAbsolutePath());
            System.out.println("文件构造路径:"+f.getPath());
            System.out.println("文件名称:"+f.getName());
            System.out.println("文件长度:"+f.length()+"字节");
    
            File f2 = new File("d:/aaa");
            System.out.println("目录绝对路径:"+f2.getAbsolutePath());
            System.out.println("目录构造路径:"+f2.getPath());
            System.out.println("目录名称:"+f2.getName());
            System.out.println("目录长度:"+f2.length());
        }
    }
    输出结果:
            文件绝对路径:d:\aaa\bbb.java
            文件构造路径:d:\aaa\bbb.java
            文件名称:bbb.java
            文件长度:636字节
    
            目录绝对路径:d:\aaa
            目录构造路径:d:\aaa
            目录名称:aaa
            目录长度:4096
    

判断功能的方法

  • public boolean exists() :此File表示的文件或目录是否实际存在。

  • public boolean isDirectory() :此File表示的是否为目录。

  • public boolean isFile() :此File表示的是否为文件。

    public class FileIs {
        public static void main(String[] args) {
            File f = new File("d:\\aaa\\bbb.java");
            File f2 = new File("d:\\aaa");
            // 判断是否存在
            System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
            System.out.println("d:\\aaa 是否存在:"+f2.exists());
            // 判断是文件还是目录
            System.out.println("d:\\aaa 文件?:"+f2.isFile());
            System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
        }
    }
    输出结果:
            d:\aaa\bbb.java 是否存在:true
            d:\aaa 是否存在:true
            d:\aaa 文件?:false
            d:\aaa 目录?:true
    
    

创建删除功能的方法

  • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

    当路径不存在时抛出IOException异常

    createNewFile声明抛出了IOException,我们调用这个方法,必须要处理这个异常,要么throws,要么try…catch

    只能创建文件不能创建文件夹

  • public boolean delete() :删除由此File表示的文件或目录。

    注意:delete()直接在硬盘删除文件/文件夹,不走回收站,删除要谨慎

  • public boolean mkdir() :创建由此File表示的目录。

    只能创建单级文件夹

  • public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

    即可以创建单级文件夹,也能创建多级文件夹

public class FileCreateDelete {
    public static void main(String[] args) throws IOException {
        // 文件的创建
        File f = new File("aaa.txt");
        System.out.println("是否存在:"+f.exists()); // false
        System.out.println("是否创建:"+f.createNewFile()); // true
        System.out.println("是否存在:"+f.exists()); // true

        // 目录的创建
        File f2= new File("newDir");
        System.out.println("是否存在:"+f2.exists());// false
        System.out.println("是否创建:"+f2.mkdir());    // true
        System.out.println("是否存在:"+f2.exists());// true

        // 创建多级目录
        File f3= new File("newDira\\newDirb");
        System.out.println(f3.mkdir());// false
        File f4= new File("newDira\\newDirb");
        System.out.println(f4.mkdirs());// true

        // 文件的删除
        System.out.println(f.delete());// true

        // 目录的删除
        System.out.println(f2.delete());// true
        System.out.println(f4.delete());// false
    }
    }

目录的遍历

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

    隐藏文件或文件夹也可以获取到

  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

    注意:

    list方法和listFiles方法遍历的是构造方法中给出的目录,如果构造方法中的目录路径不存在,会抛出空指针异常,如果构造方法中给出的路径不是一个目录,也会抛出空指针异常

    public class Demo4 {
        public static void main(String[] args) {
            File f1=new File("C:\\Users\\22236\\Desktop\\study course");
            String[] list = f1.list();
            for (String s:list
                 ) {
                System.out.println(s);
            }
        }
    }
    
    
     File f1=new File("C:\\Users\\22236\\Desktop\\study course");
    File[] files=f1.listFiles();
    for (File f:files
         ) {
        System.out.println(f);
    }
    
    

递归

  • 递归:指在当前方法内调用自己的这种现象。

  • 递归的分类:

    • 递归分为两种,直接递归和间接递归。
    • 直接递归称为方法自身调用自己。
    • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
  • 注意事项

    • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。

    • 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。

    • 构造方法,禁止递归

      递归计算和与阶乘

      public class Demo1 {
          public static void main(String[] args) {
              /*int sum=sum(3);
              System.out.println(sum);*/
              int n=jiecheng(5);
              System.out.println(n);
          }
          public static int sum(int n){
              if(n==1){
                  return 1;
              }
              return n+sum(n-1);
          }
          public static int jiecheng(int n){
              if(n==1){
                  return 1;
              }
              return n*jiecheng(n-1);
          }
      }
      
      

文件夹及文件的遍历

public class Demo2 {
    public static void main(String[] args) {
        File file=new File("C:\\Users\\22236\\Desktop\\study course");
        getAllFile(file);
    }
    public static void getAllFile(File dir){
        System.out.println(dir);
        File[] files=dir.listFiles();
        for (File f:files
             ) {
            if(f.isDirectory()){
                getAllFile(f);
            }else {
                System.out.println(f);
            }
        }
    }
}

搜索指定后缀文件

public class Demo3 {
        public static void main(String[] args) {
            File file=new File("C:\\Users\\22236\\Desktop\\study course");
            getAllFile(file);
        }
        public static void getAllFile(File dir){
            File[] files=dir.listFiles();
            for (File f:files
            ) {
                if(f.isFile()){
                    if(f.getName().endsWith(".ppt")){
                    System.out.println(f);}
                }else {
                    getAllFile(f);
                }
            }
        }
    }

文件过滤器

java.io.FileFilter是一个接口,是File的过滤器。 该接口的对象可以传递给File类的listFiles(FileFilter) 作为参数, 接口中只有一个方法。

​ 作用:用来过滤文件的方法(File对象)

boolean accept(File pathname)返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。

参数:File pathname 使用.ListFiles方法遍历目录,得到每一个文件对象

boolean accept(File pathname) :测试pathname是否应该包含在当前File目录中,符合则返回true。

boolean [accept](…/…/java/io/FilenameFilter.html#accept(java.io.File, java.lang.String))(File dir, String name)测试指定文件是否应该包含在某一文件列表中。

​ 参数:File dir:构造方法中传递的被遍历目录

​ String name:使用ListFiles方法遍历目录,获取每一个文件/文件夹的名称

public class Demo4 {
    public static void main(String[] args) {
        File file=new File("C:\\Users\\22236\\Desktop\\study course");
        getAllFile(file);
    }
    public static void getAllFile(File dir){
        File[] files=dir.listFiles(new FileFilterIMPL());
        for (File f:files
        ) {
            if(f.isFile()){
                    System.out.println(f);
            }else {
                getAllFile(f);
            }
        }
    }
}

public class FileFilterIMPL implements FileFilter {
    @Override
    public boolean accept(File pathname) {
        //如果pathname是文件夹,返回true,继续遍历
        if (pathname.isDirectory()){
            return true;
        }
        return pathname.getName().toString().endsWith(".pdf");
    }
    //创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则

}

lambda优化后

public static void printDir3(File dir) {
    // lambda的改写
    File[] files = dir.listFiles(f ->{
        return f.getName().endsWith(".java") || f.isDirectory();
    });

    // 循环打印
    for (File file : files) {
        if (file.isFile()) {
            System.out.println("文件名:" + file.getAbsolutePath());
        } else {
            printDir3(file);
        }
    }
}

IO

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

格局数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

概念:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Z4YfZHS-1614606989379)(C:\Users\22236\Desktop\java学习资料\深入\01_IO流的概念和分类.bmp)]

输入流输出流
字节流字节输入流
InputStream
字节输出流
OutputStream
字符流字符输入流
Reader
字符输出流
Writer

字节流

一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

字节输出流【OutputStream】

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。

  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。

  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。

    fos.write(48);
    
    
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。

    b:数组 off:开始的索引 len:写几个

    byte[] bytes={-65,-66,-67,68,69};
    fos.write(bytes,1,2);
    
    
  • public abstract void write(int b) :将指定的字节输出流。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

FileOutputStream类

OutputStream有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream类是文件输出流,用于将数据写出到文件。

构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。

  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

    FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\b.txt");
    
    

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

  • 构造举例,代码如下:
public class Demo1 {
    public static void main(String[] args) throws IOException {
        /*
        写入数据的原理(内存->硬盘)
                Java程序->JVM->OS->OS调用写数据的方法->把数据写到文件中
         字节输出流的使用步骤
         1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
         2.调用FileOutputStream对象中的方法,把数据写入到文件中
         3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
         */
        FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\a.txt");
        fos.write(97);//会把十进制的97转换为2进制的97
        fos.close();
    }
}

public class Demo2 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\b.txt");
        fos.write(49);
        fos.write(48);
        fos.write(48);
        byte[] bytes={-65,-66,-67,68,69};
        fos.write(bytes);
        fos.write(bytes,1,2);
        byte[] bytes1 = "你好".getBytes();//"".getBytes().var
        fos.write(bytes1);
        fos.close();
    }
}

数据追加续写

经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?

  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:

public class Demo3 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt",true);
        //true:创建对象不会覆盖原文件,继续在文件的末尾追加写数据
        //false:创建一个新文件,覆盖原文件
        fos.write("你好".getBytes());
        fos.close();
    }
}

写换行符号:

windows:\r\n

linux:/n

mac:/r

public class Demo3 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt",true);
        //true:创建对象不会覆盖原文件,继续在文件的末尾追加写数据
        //false:创建一个新文件,覆盖原文件

        for (int i = 0; i <10 ; i++) {
            fos.write("你好".getBytes());
            fos.write("\r\n".getBytes());
        }
        fos.close();
    }
}

字节输入流【InputStream】

java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

  • public abstract int read(): 从输入流读取数据的下一个字节。

    int len=0;//记录读取到的字节
    while((len=fis.read())!=-1){//未知循环次数
        //fis.read()每运行一次指针往后移一位
        System.out.println((char)len);
    }
    
    
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

FileInputStream类

java.io.FileInputStream类是文件输入流,从文件中读取字节。

读取数据的原理:java程序–>JVM–>OS–>OS读取数据的方法–>读取文件

构造方法
  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

  • FileInputStream fis = new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt");
    
    

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

public static void main(String[] args) throws IOException {
    FileInputStream fis=new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt");
    /*byte[] bytes=new byte[2];
    int len=fis.read(bytes);
    System.out.println(len);
    System.out.println(Arrays.toString(bytes));
    fis.close();*/
    byte[] bytes=new byte[1024];//存储读取到的多个字节
    int len=0;//记录每次读取的有效字节个数
    while((len=fis.read(bytes))!=-1){
        System.out.println(new String(bytes,0,len));
    }
}

练习:文件复制

public class Demo3 {
    public static void main(String[] args) throws IOException {
        /*
        文件复制:
            明确:
                数据源:
                数据的目的地
         文件复制的步骤:
         1.创建一个字节输入流对象FileInputStream,构造方法中绑定要读取的数据源
         2.创建一个字节输出流对象FileOutputStream,构造方法中绑定要读取的目的地
         3.使用字节输入该对象中的方法read读取文件
         4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
         5.释放资源
         */
        long s=System.currentTimeMillis();
        //1.
        FileInputStream fis=new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt");
        //2.
        FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\c.txt");
        int len=0;
        //一次读写一个字节
        /*while ((len=fis.read())!=-1){
            fos.write(len);
        }*/
        byte[] bytes=new byte[1024];
        while((len=fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        //先关闭写的,后关闭读的
        fos.close();
        fis.close();
        long e=System.currentTimeMillis();
        System.out.println(e-s);
    }
}
 

字符流

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。

字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流读取一个字符。
  • public int read(char[] cbuf): 从输入流中读取一些字

FileReader类

java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

使用步骤:

小贴士:

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。

idea中UTF-8

  1. 字节缓冲区:一个字节数组,用来临时存储字节数据。

构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

public static void main(String[] args) throws IOException {
    //1.创建FileReader对象,构造方法中传递写入数据源
    FileReader fr=new FileReader("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt");
    //2.使用FileReader对象中的方法read读取文件
    int len=0;
    char[] bytes = new char[1024];
    while ((len=fr.read(bytes))!=-1){

        System.out.println(new String(bytes,0,len));
    }
    //3.释放资源
    fr.close();
}

字符输出流【Writer】

java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf)写入字符数组。
  • abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str)写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush()刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。

FileWriter类

java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

使用步骤:

1.创建FileWriter对象,构造方法中绑定要写入数据的目的地

2.使用FileWriter中的方法write(),把数据写入到内存缓冲区(字符转换为字节的过程)

3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中

4.释放资源(会先把内存缓冲区中的数据刷新到文件中)

构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

  • 构造举例,代码如下:
public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);
      
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("b.txt");
    }
}

使用方法代码实现

 public static void main(String[] args) throws IOException {
        /*
        flush与close方法的区别
             flush:刷新缓冲区,流对象可以继续使用
             close:先刷新缓冲区,然后通知系统释放资源,该对象不可以在被使用了
         */
        /*
        - `void write(int c)` 写入单个字符。
- `void write(char[] cbuf) `写入字符数组。 
- `abstract  void write(char[] cbuf, int off, int len) `写入字符数组的某一部分,off数组的开始索引,len写的字符个数。 
- `void write(String str) `写入字符串。 
- `void write(String str, int off, int len)` 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
- `void flush() `刷新该流的缓冲。  
- `void close()` 关闭此流,但要先刷新它。
         */
        FileWriter fw=new FileWriter("C:\\Users\\22236\\Desktop\\practicedemo\\c.txt");
        //void write(int c)` 写入单个字符。
        fw.write(97);
        fw.flush();
        //`void write(char[] cbuf) `写入字符数组。 
        char[] chars={'a','b','c','d','e'};
        fw.write(chars);
        fw.flush();
        //void write(String str) `写入字符串。 
        fw.write("传智播客");
        //`void write(String str, int off, int len)` 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
        char[] cs="黑马程序员".toCharArray();
        fw.write(cs,2,2);//off:起始位置,len:长度
        fw.flush();
        fw.close();
    }

续写和换行

public static void main(String[] args) throws IOException {
    FileWriter fw=new FileWriter("C:\\Users\\22236\\Desktop\\practicedemo\\d.txt");
    for (int i = 0; i <10 ; i++) {
        fw.write("helloworld");
        fw.write("\r\n");
    }
    fw.close();
}

IO异常的处理

之前的入门练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try...catch...finally 代码块,处理异常部分

JDK7前的处理

public static void main(String[] args)  {
    FileWriter fw = null;
    try {
      fw = new FileWriter("C:\\Users\\22236\\Desktop\\practicedemo\\e.txt");
        for (int i = 0; i <10 ; i++) {
            fw.write("helloworld");
            fw.write("\r\n");
        }
       
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (fw!=null) {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

JDK7的处理(扩展知识点了解内容)

还可以使用JDK7优化后的try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

格式:

try (创建流对象语句,如果多个,使用';'隔开) {
	// 读写数据
} catch (IOException e) {
	e.printStackTrace();
}

代码使用演示:

public class HandleException2 {
    public static void main(String[] args) {
      	// 创建流对象
        try ( FileWriter fw = new FileWriter("fw.txt"); ) {
            // 写出数据
            fw.write("黑马程序员"); //黑马程序员
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

JDK9的改进(扩展知识点了解内容)

JDK9中try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。

改进前格式:

// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引入方式:创建新的变量保存
try (Resource r1 = resource1;
     Resource r2 = resource2) {
     // 使用对象
}

改进后格式:

// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");

// 引入方式:直接引入
try (resource1; resource2) {
     // 使用对象
}

改进后,代码使用演示:

public class TryDemo {
    public static void main(String[] args) throws IOException {
       	// 创建流对象
        final  FileReader fr  = new FileReader("in.txt");
        FileWriter fw = new FileWriter("out.txt");
       	// 引入到try中
        try (fr; fw) {
          	// 定义变量
            int b;
          	// 读取数据
          	while ((b = fr.read())!=-1) {
            	// 写出数据
            	fw.write(b);
          	}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

属性集

概述

java.util.Properties 继承于Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties 方法就是返回一个Properties对象。

Properties类

构造方法

  • public Properties() :创建一个空的属性列表。

基本的存储方法

  • public Object setProperty(String key, String value) : 保存一对属性。

  • public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。

  • public Set<String> stringPropertyNames() :所有键的名称的集合。

    代码使用:

    public static void main(String[] args) {
            show01();
        }
    
        private static void show01() {
            /*
            使用Properties集合存储数据,遍历Properties集合中的数据
            - `public Object setProperty(String key, String value)` : 保存一对属性。
    - `public String getProperty(String key) ` :使用此属性列表中指定的键搜索属性值。   此方法相当于Map中的get方法
             */
            //创建集合对象
            Properties prop=new Properties();
            //存储key与value
            prop.setProperty("a","168");
            prop.setProperty("b","166");
            prop.setProperty("c","170");
            //使用stringPropertyNames把集合中的key取出并存放在set集合
            Set<String> strings = prop.stringPropertyNames();
            //遍历set得到value
            for (String s:
                 strings) {
                System.out.println(prop.getProperty(s));
            }
        }
    
    

与流相关的方法

void store(Writer writer, String comments)
以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 
void store(OutputStream out, String comments)
以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。

public void load(InputStream inStream): 从字节输入流中读取键值对。

参数:
   OutputStream out:字节输出流,不能写入中文
   Writer writer:字符输出流,可以写中文
   String comments:注释,用来解释文件是用来干什么的
   不能使用中文,会产生乱码,默认是Unicode编码
   一般使用""空字符串
使用步骤:
1.创建Properties集合对象,添加数据
2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
3.使用Properties集合中的方法store,把集合中的临时数据持久化写入到硬盘中存储
4.释放资源
       FileWriter fw=new FileWriter("C:\\Users\\22236\\Desktop\\practicedemo\\f.txt");
        Properties prop=new Properties();
        //存储key与value
        prop.setProperty("a","168");
        prop.setProperty("b","166");
        prop.setProperty("c","170");
        prop.store(fw,"save data");
        fw.close();

load代码实现

private static void show03() throws IOException {
        /*
 void load(InputStream inStream)从输入流中读取属性列表(键和元素对)。
 void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
参数:InputStream inStream:字节输入流,不能读取含有中文的键值对
     Reader reader :字符输入流,能读取含有中文的键值对
     使用步骤:
        1.创建Properties集合对象
        2.使用Properties集合对象中的方法load读取保存键值对的文件
        3.遍历Properties集合
      注意:
        1.存储键值对的文件中,键与值默认的链接符号可以使用=,空格,其他符号
        2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会被读取
        3.
         */
        Properties prop=new Properties();
        prop.load(new FileReader("C:\\Users\\22236\\Desktop\\practicedemo\\f.txt"));
        Set<String> strings = prop.stringPropertyNames();
        for (String s :
                strings) {
            String vale = prop.getProperty(s);
            System.out.println(s+"="+vale);
        }
    }

缓冲流

昨天学习了基本的一些流,作为IO流的入门,今天我们要见识一些更强大的流。比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。

概述

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

字节缓冲流

BufferedOutputStream extends OutputStream

BufferedInputStream extends InputStream

构造方法

  • public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。

    使用步骤:

    1.创建FileInputStream对象,构造方法中指定要读取的数据源

    2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象效率

    3.使用BufferedInputStream对象中的方法read,读取文件

    4.释放资源

    public static void main(String[] args) throws IOException {
            FileInputStream fis=new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\g.txt");
            BufferedInputStream bis=new BufferedInputStream(fis);
            int len=0;
            byte[] bytes=new byte[1024];
            while((len=bis.read(bytes))!=-1){
                System.out.println(new String(bytes,0,len));
            }
           /* String(byte[] bytes, int offset, int length)
            通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。*/
            bis.close();
        }
    
    
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

    使用步骤:

    1.创建FileOutputStream对象,构造方法中指定要输出的目的地

    2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率

    3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区

    4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中

    5.释放资源

    public static void main(String[] args) throws IOException {
        FileOutputStream fos=new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\g.txt");
        BufferedOutputStream bos=new BufferedOutputStream(fos);
        bos.write("把数据写入内存缓冲区".getBytes());
        bos.flush();
        bos.close();
    }
    
    

构造举例,代码如下:

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

效率测试

查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率。

  1. 基本流,代码如下:
public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	FileInputStream fis = new FileInputStream("jdk9.exe");
        	FileOutputStream fos = new FileOutputStream("copy.exe")
        ){
        	// 读写数据
            int b;
            while ((b = fis.read()) != -1) {
                fos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
    }
}

十几分钟过去了...

  1. 缓冲流,代码如下:
public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
	     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
        // 读写数据
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流复制时间:"+(end - start)+" 毫秒");
    }
}

缓冲流复制时间:8016 毫秒

如何更快呢?

使用数组的方式,代码如下:

public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
      	// 记录开始时间
        long start = System.currentTimeMillis();
		// 创建流对象
        try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
          	// 读写数据
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
    }
}
缓冲流使用数组复制时间:666 毫秒

字符缓冲流

构造方法

  • public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
  • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

构造举例,代码如下:

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

特有方法

字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

  • BufferedReader:public String readLine(): 读一行文字。
  • BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

练习:文件排序

3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
        8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
        4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
        2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
        1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
        9.今当远离,临表涕零,不知所言。
        6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
        7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
        5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

public static void main(String[] args) throws IOException {
    HashMap<String,String> map=new HashMap();
    BufferedReader bfr=new BufferedReader(new FileReader("C:\\Users\\22236\\Desktop\\practicedemo\\i.txt"));
    BufferedWriter bfw=new BufferedWriter(new FileWriter("C:\\Users\\22236\\Desktop\\practicedemo\\iout.txt"));
    String s;
    while((s=bfr.readLine())!=null){
        String[] split = s.split("\\.");
        map.put(split[0],split[1]);
    }
    for(String a:map.keySet()){
        String value=map.get(a);
        s=a+""+value;
        bfw.write(s);
        bfw.newLine();
    }
    bfw.close();
    bfr.close();
}

转换流

原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iFS7Du9e-1614606989382)(C:\Users\22236\Desktop\java学习资料\深入\02_转换流的原理.bmp)]

字符编码和字符集

字符编码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

编码:字符(能看懂的)–字节(看不懂的)

解码:字节(看不懂的)–>字符(能看懂的)

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

    编码表:生活中文字和计算机中二进制的对应规则

字符集

  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RcSc3Zbs-1614606989383)(C:/Users/22236/Desktop/java学习资料/深入/1_charset.jpg)]

可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

  • ASCII字符集
    • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
    • 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
  • ISO-8859-1字符集
    • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
    • ISO-8859-1使用单字节编码,兼容ASCII编码。
  • GBxxx字符集
    • GB就是国标的意思,是为了显示中文而设计的一套字符集。
    • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
    • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
    • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
  • Unicode字符集
    • Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
    • 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
    • UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
      1. 128个US-ASCII字符,只需一个字节编码。
      2. 拉丁文等字符,需要二个字节编码。
      3. 大部分常用字(含中文),使用三个字节编码。
      4. 其他极少使用的Unicode辅助字符,使用四字节编码。

InputStreamReader类

转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。

  • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

    private static void show02() throws IOException {
        InputStreamReader isr=new InputStreamReader(new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\a.txt"),"utf-8");
        int len=0;
        while((len=isr.read())!=-1){
            System.out.println((char)len);
        }
        isr.close();
    }
    
    

OutputStreamWriter类

转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

构造举例,代码如下:

OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");

指定编码写出

public class OutputDemo {
    public static void main(String[] args) throws IOException {
      	// 定义文件路径
        String FileName = "E:\\out.txt";
      	// 创建流对象,默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 写出数据
      	osw.write("你好"); // 保存为6个字节
        osw.close();
      	
		// 定义文件路径
		String FileName2 = "E:\\out2.txt";
     	// 创建流对象,指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
        // 写出数据
      	osw2.write("你好");// 保存为4个字节
        osw2.close();
    }
}

转换流理解图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Amu3FpoW-1614606989385)(C:\Users\22236\Desktop\java学习资料\深入\02_转换流的原理.bmp)]

转换文件编码

将GBK编码的文本文件,转换为UTF-8编码的文本文件。

案例分析

  1. 指定GBK编码的转换流,读取文本文件。
  2. 使用UTF-8编码的转换流,写出文本文件。

案例实现

private static void show03() throws IOException {
   InputStreamReader isr=new InputStreamReader(new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\a.txt"),"utf-8");
   OutputStreamWriter opw=new OutputStreamWriter(new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\a01.txt"),"GBK");
   int len=0;
   while ((len=isr.read())!=-1){
       opw.write(len);
   }
   opw.close();
   isr.close();
}

序列化

概述

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvgEFG4c-1614606989386)(C:\Users\22236\Desktop\java学习资料\深入\3_xuliehua.jpg)]

transient关键字(瞬态关键字)

被transient修饰的成员变量不能被序列化

ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法

  • public ObjectOutputStream(OutputStream out): 创建一个指定OutputStream的ObjectOutputStream。

构造举例,代码如下:

FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

序列化操作

  1. 一个对象要想序列化,必须满足两个条件:
  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
public class Employee implements Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
    public void addressCheck() {
        System.out.println("Address  check : " + name + " -- " + address);
    }
}

2.写出对象方法

  • public final void writeObject (Object obj) : 将指定的对象写出。
public class SerializeDemo{
   	public static void main(String [] args)   {
    	Employee e = new Employee();
    	e.name = "zhangsan";
    	e.address = "beiqinglu";
    	e.age = 20; 
    	try {
      		// 创建序列化流对象
          ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
        	// 写出对象
        	out.writeObject(e);
        	// 释放资源
        	out.close();
        	fileOut.close();
        	System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
        } catch(IOException i)   {
            i.printStackTrace();
        }
   	}
}
输出结果:
Serialized data is saved

ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

  • public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。

反序列化操作1

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。
public class DeserializeDemo {
   public static void main(String [] args)   {
        Employee e = null;
        try {		
             // 创建反序列化流
             FileInputStream fileIn = new FileInputStream("employee.txt");
             ObjectInputStream in = new ObjectInputStream(fileIn);
             // 读取一个对象
             e = (Employee) in.readObject();
             // 释放资源
             in.close();
             fileIn.close();
        }catch(IOException i) {
             // 捕获其他异常
             i.printStackTrace();
             return;
        }catch(ClassNotFoundException c)  {
        	// 捕获类找不到异常
             System.out.println("Employee class not found");
             c.printStackTrace();
             return;
        }
        // 无异常,直接打印输出
        System.out.println("Name: " + e.name);	// zhangsan
        System.out.println("Address: " + e.address); // beiqinglu
        System.out.println("age: " + e.age); // 0
    }
}

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

反序列化操作2

**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。**发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

public class Employee implements java.io.Serializable {
     // 加入序列版本号
     private static final long serialVersionUID = 1L;
     public String name;
     public String address;
     // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
     public int eid; 

     public void addressCheck() {
         System.out.println("Address  check : " + name + " -- " + address);
     }
}

练习:序列化集合

  1. 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt ,并遍历集合,打印对象信息。

案例分析

  1. 把若干学生对象 ,保存到集合中。
  2. 把集合序列化。
  3. 反序列化读取时,只需要读取一次,转换为集合类型。
  4. 遍历集合,可以打印所有的学生信息
public class demo3 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person[] people={new Person("a",1,"aa"),new Person("b",12,"bb"),new Person("c",3,"cc")};
        ArrayList<Person> arr=new ArrayList<>();
        for (int i = 0; i < people.length; i++) {
            arr.add(people[i]);
        }
        //System.out.println(arr);
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("C:\\Users\\22236\\Desktop\\practicedemo\\a.txt"));
        out.writeObject(arr);
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("C:\\Users\\22236\\Desktop\\practicedemo\\a.txt"));
        Object o = ois.readObject();
        ArrayList<Person> p1=(ArrayList<Person>)o;
        for (Person p :
                p1) {
            System.out.println(p);
        }
        ois.close();
        out.close();
    }
}

打印流

概述

平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

PrintStream类

构造方法

  • public PrintStream(String fileName): 使用指定的文件名创建一个新的打印流。

构造举例,代码如下:

PrintStream ps = new PrintStream("ps.txt")

改变打印流向

System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向。

public class PrintDemo {
    public static void main(String[] args) throws IOException {
		// 调用系统的打印流,控制台直接输出97
        System.out.println(97);
      
		// 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("ps.txt");
      	
      	// 设置系统的打印流流向,输出到ps.txt
        System.setOut(ps);
      	// 调用系统的打印流,ps.txt中输出97
        System.out.println(97);
    }
}

TCP通信程序

概述

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。

两端通信时步骤:

  1. 服务端程序,需要事先启动,等待客户端的连接。
  2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

在Java中,提供了两个类用于实现TCP通信程序:

  1. 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
  2. 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。

Socket类

Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

构造方法

  • public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。

    小贴士:回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

构造举例,代码如下:

Socket client = new Socket("127.0.0.1", 6666);

成员方法

  • public InputStream getInputStream() : 返回此套接字的输入流。
    • 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
    • 关闭生成的InputStream也将关闭相关的Socket。
  • public OutputStream getOutputStream() : 返回此套接字的输出流。
    • 如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
    • 关闭生成的OutputStream也将关闭相关的Socket。
  • public void close() :关闭此套接字。
    • 一旦一个socket被关闭,它不可再使用。
    • 关闭此socket也将关闭相关的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的输出流。
    • 任何先前写出的数据将被发送,随后终止输出流。

ServerSocket类

ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。

构造方法

  • public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。

构造举例,代码如下:

ServerSocket server = new ServerSocket(6666);

成员方法

  • public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

简单的TCP网络程序

TCP通信分析图解

  1. 【服务端】启动,创建ServerSocket对象,等待连接。
  2. 【客户端】启动,创建Socket对象,请求连接。
  3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
  4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
  5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。

到此,客户端向服务端发送数据成功。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMXDkup8-1614606989388)(C:/Users/22236/Desktop/java学习资料/深入/img/5_简单通信.jpg)]

自此,服务端向客户端回写数据。

  1. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
  2. 【客户端】Scoket对象,获取InputStream,解析回写数据。
  3. 【客户端】释放资源,断开连接。

客户端向服务器发送数据

服务端实现:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 1.创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3.通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 4.一次性读取数据
      	// 4.1 创建字节数组
        byte[] b = new byte[1024];
      	// 4.2 据读取到字节数组中.
        int len = is.read(b)// 4.3 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5.关闭资源.
        is.close();
        server.close();
    }
}

客户端实现:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端 发送数据");
		// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
		Socket client = new Socket("localhost", 6666);
		// 2.获取流对象 . 输出流
		OutputStream os = client.getOutputStream();
		// 3.写出数据.
		os.write("你好么? tcp ,我来了".getBytes());
		// 4. 关闭资源 .
		os.close();
		client.close();
	}
}

服务器向客户端回写数据

服务端实现:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 1.创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2.接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3.通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 4.一次性读取数据
      	// 4.1 创建字节数组
        byte[] b = new byte[1024];
      	// 4.2 据读取到字节数组中.
        int len = is.read(b)// 4.3 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
      	// =================回写数据=======================
      	// 5. 通过 socket 获取输出流
      	 OutputStream out = server.getOutputStream();
      	// 6. 回写数据
      	 out.write("我很好,谢谢你".getBytes());
      	// 7.关闭资源.
      	out.close();
        is.close();
        server.close();
    }
}

客户端实现:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端 发送数据");
		// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
		Socket client = new Socket("localhost", 6666);
		// 2.通过Scoket,获取输出流对象 
		OutputStream os = client.getOutputStream();
		// 3.写出数据.
		os.write("你好么? tcp ,我来了".getBytes());
      	// ==============解析回写=========================
      	// 4. 通过Scoket,获取 输入流对象
      	InputStream in = client.getInputStream();
      	// 5. 读取数据数据
      	byte[] b = new byte[100];
      	int len = in.read(b);
      	System.out.println(new String(b, 0, len));
		// 6. 关闭资源 .
      	in.close();
		os.close();
		client.close();
	}
}

文件上传案例

文件上传分析图解

  1. 【客户端】输入流,从硬盘读取文件数据到程序中。
  2. 【客户端】输出流,写出文件数据到服务端。
  3. 【服务端】输入流,读取文件数据到服务端程序。
  4. 【服务端】输出流,写出文件数据到服务器硬盘中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGeRU48P-1614606989390)(C:/Users/22236/Desktop/java学习资料/深入/img/6_upload.jpg)]

基本实现

服务端实现:

public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器 启动.....  ");
        // 1. 创建服务端ServerSocket
      	ServerSocket serverSocket = new ServerSocket(6666);
  		// 2. 建立连接 
        Socket accept = serverSocket.accept();
      	// 3. 创建流对象
      	// 3.1 获取输入流,读取文件数据
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
        // 3.2 创建输出流,保存到本地 .
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));
		// 4. 读写数据
        byte[] b = new byte[1024 * 8];
        int len;
        while ((len = bis.read(b)) != -1) {
            bos.write(b, 0, len);
        }
        //5. 关闭 资源
        bos.close();
        bis.close();
        accept.close();
        System.out.println("文件上传已保存");
    }
}

客户端实现:

public class FileUPload_Client {
	public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 创建输入流,读取本地文件  
        BufferedInputStream bis  = new BufferedInputStream(new FileInputStream("test.jpg"));
        // 1.2 创建输出流,写到服务端 
        Socket socket = new Socket("localhost", 6666);
        BufferedOutputStream   bos   = new BufferedOutputStream(socket.getOutputStream());

        //2.写出数据. 
        byte[] b  = new byte[1024 * 8 ];
        int len ; 
        while (( len  = bis.read(b))!=-1) {
            bos.write(b, 0, len);
            bos.flush();
        }
        System.out.println("文件发送完毕");
        // 3.释放资源

        bos.close(); 
        socket.close();
        bis.close(); 
        System.out.println("文件上传完毕 ");
	}
}

文件上传优化分析

  1. 文件名称写死的问题

    服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,建议使用系统时间优化,保证文件名称唯一,代码如下:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);
  1. 循环接收的问题

    服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件,代码如下:

// 每次接收新的连接,创建一个Socket
whiletrue{
    Socket accept = serverSocket.accept();
    ......
}
  1. 效率问题

    服务端,在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化,代码如下:

whiletrue{
    Socket accept = serverSocket.accept();
    // accept 交给子线程处理.
    new Thread(() -> {
      	......
        InputStream bis = accept.getInputStream();
      	......
    }).start();
}
优化实现
public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器 启动.....  ");
        // 1. 创建服务端ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
      	// 2. 循环接收,建立连接
        while (true) {
            Socket accept = serverSocket.accept();
          	/* 
          	3. socket对象交给子线程处理,进行读写操作
               Runnable接口中,只有一个run方法,使用lambda表达式简化格式
            */
            new Thread(() -> {
                try (
                    //3.1 获取输入流对象
                    BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                    //3.2 创建输出流对象, 保存到本地 .
                    FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
                    BufferedOutputStream bos = new BufferedOutputStream(fis);) {
                    // 3.3 读写数据
                    byte[] b = new byte[1024 * 8];
                    int len;
                    while ((len = bis.read(b)) != -1) {
                      bos.write(b, 0, len);
                    }
                    //4. 关闭 资源
                    bos.close();
                    bis.close();
                    accept.close();
                    System.out.println("文件上传已保存");
                } catch (IOException e) {
                  	e.printStackTrace();
                }
            }).start();
        }
    }
}

信息回写分析图解

前四步与基本文件上传一致.

  1. 【服务端】获取输出流,回写数据。
  2. 【客户端】获取输入流,解析回写数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R7lwhytZ-1614606989391)(C:/Users/22236/Desktop/java学习资料/深入/img/6_upload2.jpg)]

回写实现
public class FileUpload_Server {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器 启动.....  ");
        // 1. 创建服务端ServerSocket
        ServerSocket serverSocket = new ServerSocket(6666);
        // 2. 循环接收,建立连接
        while (true) {
            Socket accept = serverSocket.accept();
          	/*
          	3. socket对象交给子线程处理,进行读写操作
               Runnable接口中,只有一个run方法,使用lambda表达式简化格式
            */
            new Thread(() -> {
                try (
                    //3.1 获取输入流对象
                    BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
                    //3.2 创建输出流对象, 保存到本地 .
                    FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
                    BufferedOutputStream bos = new BufferedOutputStream(fis);
                ) {
                    // 3.3 读写数据
                    byte[] b = new byte[1024 * 8];
                    int len;
                    while ((len = bis.read(b)) != -1) {
                        bos.write(b, 0, len);
                    }

                    // 4.=======信息回写===========================
                    System.out.println("back ........");
                    OutputStream out = accept.getOutputStream();
                    out.write("上传成功".getBytes());
                    out.close();
                    //================================

                    //5. 关闭 资源
                    bos.close();
                    bis.close();
                    accept.close();
                    System.out.println("文件上传已保存");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

客户端实现:

public class FileUpload_Client {
    public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 创建输入流,读取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
        // 1.2 创建输出流,写到服务端
        Socket socket = new Socket("localhost", 6666);
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());

        //2.写出数据.
        byte[] b  = new byte[1024 * 8 ];
        int len ;
        while (( len  = bis.read(b))!=-1) {
            bos.write(b, 0, len);
        }
      	// 关闭输出流,通知服务端,写出数据完毕
        socket.shutdownOutput();
        System.out.println("文件发送完毕");
        // 3. =====解析回写============
        InputStream in = socket.getInputStream();
        byte[] back = new byte[20];
        in.read(back);
        System.out.println(new String(back));
        in.close();
        // ============================

        // 4.释放资源
        socket.close();
        bis.close();
    }
}

模拟B\S服务器(扩展知识点)

模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。

案例分析

  1. 准备页面数据,web文件夹。

    复制到我们Module中,比如复制到day08中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wyIdNF6-1614606989393)(C:/Users/22236/Desktop/java学习资料/深入/img/复制.png)]

  2. 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问

    public static void main(String[] args) throws IOException {
        	ServerSocket server = new ServerSocket(8000);
        	Socket socket = server.accept();
        	InputStream in = socket.getInputStream();
       	    byte[] bytes = new byte[1024];
        	int len = in.read(bytes);
        	System.out.println(new String(bytes,0,len));
        	socket.close();
        	server.close();
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i5pHpgFo-1614606989394)(C:/Users/22236/Desktop/java学习资料/深入/img/无法访问.jpg)]

  3. 服务器程序中字节输入流可以读取到浏览器发来的请求信息

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGhUYIt7-1614606989396)(C:/Users/22236/Desktop/java学习资料/深入/img/读取访问信息.jpg)]

GET/web/index.html HTTP/1.1是浏览器的请求消息。/web/index.html为浏览器想要请求的服务器端的资源,使用字符串切割方式获取到请求的资源。

//转换流,读取浏览器请求第一行
BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出请求资源的路径
String[] strArr = requst.split(" ");
//去掉web前面的/
String path = strArr[1].substring(1);
System.out.println(path);

案例实现

服务端实现:

public class SerDemo {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端  启动 , 等待连接 .... ");
        // 创建ServerSocket 对象
        ServerSocket server = new ServerSocket(8888);
        Socket socket = server.accept();
        // 转换流读取浏览器的请求消息
        BufferedReader readWb = new
        BufferedReader(new InputStreamReader(socket.getInputStream()));
        String requst = readWb.readLine();
        // 取出请求资源的路径
        String[] strArr = requst.split(" ");
        // 去掉web前面的/
        String path = strArr[1].substring(1);
        // 读取客户端请求的资源文件
        FileInputStream fis = new FileInputStream(path);
        byte[] bytes= new byte[1024];
        int len = 0 ;
        // 字节输出流,将文件写会客户端
        OutputStream out = socket.getOutputStream();
        // 写入HTTP协议响应头,固定写法
        out.write("HTTP/1.1 200 OK\r\n".getBytes());
        out.write("Content-Type:text/html\r\n".getBytes());
        // 必须要写入空行,否则浏览器不解析
        out.write("\r\n".getBytes());
        while((len = fis.read(bytes))!=-1){
            out.write(bytes,0,len);
        }
        fis.close();
        out.close();
        readWb.close();	
        socket.close();
        server.close();
    }
}

访问效果

  • 火狐

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jhIfKhx-1614606989397)(C:/Users/22236/Desktop/java学习资料/深入/img/效果图1.png)]

小贴士:不同的浏览器,内核不一样,解析效果有可能不一样。

发现浏览器中出现很多的叉子,说明浏览器没有读取到图片信息导致。

浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);
        while(true){
            Socket socket = server.accept();
            new Thread(new Web(socket)).start();
        }
    }
    static class Web implements Runnable{
        private Socket socket;

        public Web(Socket socket){
            this.socket=socket;
        }

        public void run() {
            try{
                //转换流,读取浏览器请求第一行
                BufferedReader readWb = new
                        BufferedReader(new InputStreamReader(socket.getInputStream()));
                String requst = readWb.readLine();
                //取出请求资源的路径
                String[] strArr = requst.split(" ");
                System.out.println(Arrays.toString(strArr));
                String path = strArr[1].substring(1);
                System.out.println(path);

                FileInputStream fis = new FileInputStream(path);
                System.out.println(fis);
                byte[] bytes= new byte[1024];
                int len = 0 ;
                //向浏览器 回写数据
                OutputStream out = socket.getOutputStream();
                out.write("HTTP/1.1 200 OK\r\n".getBytes());
                out.write("Content-Type:text/html\r\n".getBytes());
                out.write("\r\n".getBytes());
                while((len = fis.read(bytes))!=-1){
                    out.write(bytes,0,len);
                }
                fis.close();
                out.close();
                readWb.close();
                socket.close();
            }catch(Exception ex){

            }
        }
    }

}

存中…(img-i5pHpgFo-1614606989394)]

  1. 服务器程序中字节输入流可以读取到浏览器发来的请求信息

    [外链图片转存中…(img-EGhUYIt7-1614606989396)]

GET/web/index.html HTTP/1.1是浏览器的请求消息。/web/index.html为浏览器想要请求的服务器端的资源,使用字符串切割方式获取到请求的资源。

//转换流,读取浏览器请求第一行
BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出请求资源的路径
String[] strArr = requst.split(" ");
//去掉web前面的/
String path = strArr[1].substring(1);
System.out.println(path);

案例实现

服务端实现:

public class SerDemo {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端  启动 , 等待连接 .... ");
        // 创建ServerSocket 对象
        ServerSocket server = new ServerSocket(8888);
        Socket socket = server.accept();
        // 转换流读取浏览器的请求消息
        BufferedReader readWb = new
        BufferedReader(new InputStreamReader(socket.getInputStream()));
        String requst = readWb.readLine();
        // 取出请求资源的路径
        String[] strArr = requst.split(" ");
        // 去掉web前面的/
        String path = strArr[1].substring(1);
        // 读取客户端请求的资源文件
        FileInputStream fis = new FileInputStream(path);
        byte[] bytes= new byte[1024];
        int len = 0 ;
        // 字节输出流,将文件写会客户端
        OutputStream out = socket.getOutputStream();
        // 写入HTTP协议响应头,固定写法
        out.write("HTTP/1.1 200 OK\r\n".getBytes());
        out.write("Content-Type:text/html\r\n".getBytes());
        // 必须要写入空行,否则浏览器不解析
        out.write("\r\n".getBytes());
        while((len = fis.read(bytes))!=-1){
            out.write(bytes,0,len);
        }
        fis.close();
        out.close();
        readWb.close();	
        socket.close();
        server.close();
    }
}

访问效果

  • 火狐

[外链图片转存中…(img-0jhIfKhx-1614606989397)]

小贴士:不同的浏览器,内核不一样,解析效果有可能不一样。

发现浏览器中出现很多的叉子,说明浏览器没有读取到图片信息导致。

浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);
        while(true){
            Socket socket = server.accept();
            new Thread(new Web(socket)).start();
        }
    }
    static class Web implements Runnable{
        private Socket socket;

        public Web(Socket socket){
            this.socket=socket;
        }

        public void run() {
            try{
                //转换流,读取浏览器请求第一行
                BufferedReader readWb = new
                        BufferedReader(new InputStreamReader(socket.getInputStream()));
                String requst = readWb.readLine();
                //取出请求资源的路径
                String[] strArr = requst.split(" ");
                System.out.println(Arrays.toString(strArr));
                String path = strArr[1].substring(1);
                System.out.println(path);

                FileInputStream fis = new FileInputStream(path);
                System.out.println(fis);
                byte[] bytes= new byte[1024];
                int len = 0 ;
                //向浏览器 回写数据
                OutputStream out = socket.getOutputStream();
                out.write("HTTP/1.1 200 OK\r\n".getBytes());
                out.write("Content-Type:text/html\r\n".getBytes());
                out.write("\r\n".getBytes());
                while((len = fis.read(bytes))!=-1){
                    out.write(bytes,0,len);
                }
                fis.close();
                out.close();
                readWb.close();
                socket.close();
            }catch(Exception ex){

            }
        }
    }

}

访问效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值