深入分析Java Web技术内幕-第二章

深入分析Java I/O机制

Java I/O类库的基础架构

类库大概分为四组:

基于字节操作的I/O接口:InputStream 和 OutputStream
基于字符操作的I/O接口: Writer和Reader
基于磁盘操作的I/O接口: File
基于网络操作的I/O接口: Socket
(这尼玛也能划到一起,不过这样好像也行)

Continue reading “深入分析Java Web技术内幕-第二章”

Java STL & JDK8 聚合

JDK8的聚合和C#的Fluent API基本一样,每次操作后返回的类型和之前的一样,股可以继续操作.

至于STL,和C++比一下就是一些单词不一样罢了.
排序时需要实现Comparable接口.

这节水过去= =.

其中泛型如果需要支持一个类的子类.需要这样写.
使用? extends 类型名.

ArrayList<? extends Hero> heroList = apHeroList;

泛型通配符: ?

Java I/O

I/O

文件对象

创建一个文件对象

package file;

import java.io.File;

public class TestFile {
    public static void main(String[] args){
        //绝对路径
        File f1=new File("d:/LOLFolder");
        System.out.println("f1的绝对路径: " + f1.getAbsolutePath());
        //相对路径,相对于工作目录,如果在eclipse中,就是项目目录
        File f2=new File("LOL.exe");
        System.out.println("f2的绝对路径: "+f2.getAbsolutePath());
        //把f1作为父目录创建文件对象
        File f3=new File(f1,"LOL.exe");
        System.out.println("f3的绝对路径: "+f3.getAbsolutePath());
    }
}

输出:

f1的绝对路径: d:\LOLFolder
f2的绝对路径: D:\Java\Learning\LOL.exe
f3的绝对路径: d:\LOLFolder\LOL.exe

文件常用方法

exists() -是否存在
isDirectory() -是否是文件夹
isFile() -是否是文件
length() -长度
lastModified() -文件最后修改时间
setlastModified() -修改文件最后修改时间
renameTo(f2 type is File) -把名字改成f2的名字,两个文件必须存在

package file;

import java.io.File;
import java.util.Date;

public class Method {
    public static void main(String args[]){
        File f = new File("d:/LOLFolder/LOL.exe");
        System.out.println("当前文件是:" +f);
        //文件是否存在
        System.out.println("判断是否存在:"+f.exists());

        //是否是文件夹
        System.out.println("判断是否是文件夹:"+f.isDirectory());

        //是否是文件(非文件夹)
        System.out.println("判断是否是文件:"+f.isFile());

        //文件长度
        System.out.println("获取文件的长度:"+f.length());

        //文件最后修改时间
        long time = f.lastModified();
        Date d = new Date(time);
        System.out.println("获取文件的最后修改时间:"+d);
        //设置文件修改时间为1970.1.1 08:00:00
        f.setLastModified(0);

        //文件重命名
        File f2 =new File("d:/LOLFolder/DOTA.exe");
        f.renameTo(f2);
        System.out.println("把LOL.exe改名成了DOTA.exe");

        System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");
    }
}

输出:

当前文件是:d:\LOLFolder\LOL.exe
判断是否存在:false
判断是否是文件夹:false
判断是否是文件:false
获取文件的长度:0
获取文件的最后修改时间:Thu Jan 01 08:00:00 GMT+08:00 1970
把LOL.exe改名成了DOTA.exe
注意: 需要在D:\LOLFolder确实存在一个LOL.exe,
才可以看到对应的文件长度、修改时间等信息

文件的常用方法2

list() -以字符串数组的形式,返回当前文件夹下的所有文件
File[] fs=f.listFiles() -以文件数组的形式,返回当前文件夹下的所有文件
getParent() -以字符串形式返回所在文件夹
getParentFile() -以文件形式返回获取所在文件夹
mkdir() -创建文件夹,如果父目录不存在,创建失败
mkdirs() -如果父目录不存在,一样创建
createNewFile() -创建新文件,父目录不存在,抛异常
listRoots() -盘符c: d:
delete() -删除
deleteOnExit() -JVM结束时删除

package file;

import java.io.File;
import java.io.IOException;

public class Method2 {
    public static void main(String[] args) throws IOException {

        File f = new File("d:/LOLFolder/skin/garen.ski");

        // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        f.list();

        // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        File[]fs= f.listFiles();

        // 以字符串形式返回获取所在文件夹
        f.getParent();

        // 以文件形式返回获取所在文件夹
        f.getParentFile();
        // 创建文件夹,如果父文件夹skin不存在,创建就无效
        f.mkdir();

        // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹
        f.mkdirs();

        // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常
        f.createNewFile();
        // 所以创建一个空文件之前,通常都会创建父目录
        f.getParentFile().mkdirs();

        // 列出所有的盘符c: d: e: 等等
        f.listRoots();

        // 刪除文件
        f.delete();

        // JVM结束的时候,刪除文件,常用于临时文件的删除
        f.deleteOnExit();

    }
}

什么是流

流即一系列数据.
当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
数据源可以是文件,还可以是数据库,网络甚至是其他的程序

比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
输入流: InputStream
输出流:OutputStream

文件输入流

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Stream {
    public static void main(String[] args) {
        try {
            File f = new File("d:/lol.txt");
            // 创建基于文件的输入流
            FileInputStream fis = new FileInputStream(f);
            // 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟中来,也就是读取到内存中

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

文件读入与输出

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class bytes {
    //FileInputStream是InputStream的子类
    public static void main(String[] args){
        try{
            //从文件读入
            File f=new File("log.txt");
            //创建基于文件的输入流
            FileInputStream fis=new FileInputStream(f);
            //创建字节数组,其长度就是文件的长度
            byte[] all=new byte[(int)f.length()];
            //以字节流的形式读取文件所有内容
            fis.read(all);
            for(byte b:all){
                System.out.println(b);
            }

            //每次使用完毕关闭
            fis.close();


            //向文件输出
            File f1=new File("log1.txt");
            byte data[]={88,89};
            FileOutputStream fos=new FileOutputStream(f1);
            fos.write(data);
            fos.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

关闭文件时需要注意

在try的作用域里关闭文件输入流,在前面的示例中都是使用这种方式,这样做有一个弊端;
如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。 不推荐使用

最标准的写法是在 finally 中关闭文件.
1. 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
2. 在finally关闭之前,要先判断该引用是否为空
3. 关闭的时候,需要再一次进行try catch处理

使用try()方式.其使用方法和C#的using(),Python的with一样.

字符流

这节之前都是字节流
Reader字符输入流
Writer字符输出流
专门用于字符的形式读取和写入数据

package file;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharacterStream {
    public static void main(String[] args){
        File f=new File("log.txt");
        try(FileReader fr=new FileReader(f)){
            //以字符流读入到char数组中
            char[] all=new char[(int)f.length()];
            fr.read(all);
            for(char ch : all){
                System.out.println(ch);
            }
        }catch(IOException e){
            e.printStackTrace();
        }

        File f1=new File("log1.txt");
        try(FileWriter fr=new FileWriter(f1)){
            String data="abcdefghijklmn123456789";
            //字符串转字符数组
            char[] cs=data.toCharArray();
            fr.write(cs);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

中文编码问题

1.中文编码

package file;

import java.io.UnsupportedEncodingException;
//以字符 中 为例,查看其在不同编码方式下的值是多少

public class encode {
    public static void main(String[] args){
        String str = "中";
        showCode(str);
    }

    private static void showCode(String str){
        String[] encodes={"BIG5","GBK","GB2312","UTF-8","UTF-16","UTF32"};
        for(String encode : encodes){
            showCode(str,encode);
        }
    }

    private static void showCode(String str,String encode){
        try{
            //格式化输出用printf
            System.out.printf("字符: \"%s\" 的在编码方式%s下的十六进制是%n",str,encode);
            //str获取encode编码的byte数组 
            byte[] bs=str.getBytes(encode);
            for(byte b: bs){
                //只显示每字节的低两位
                int i=b&0xff;
                //toHex => 16进制
                System.out.print(Integer.toHexString(i) + "\t");
            }
            System.out.println();
            System.out.println();
        }catch(UnsupportedEncodingException e){
            System.out.printf("UnsupportedEncodingException: %s编码方式无法解析字符%s\n", encode, str);
        }
    }
}

2.文件流读取中文

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

public class fileEncode {
    public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
        File f = new File("test.txt");
        System.out.println("默认编码方式:"+Charset.defaultCharset());
        //FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
        //而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
        try (FileReader fr = new FileReader(f)) {
            char[] cs = new char[(int) f.length()];
            fr.read(cs);
            System.out.printf("FileReader会使用默认的编码方式%s,识别出来的字符是:%n",Charset.defaultCharset());
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
        //并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) {
            char[] cs = new char[(int) f.length()];
            isr.read(cs);
            System.out.printf("InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n");
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

缓存流

package file;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;

//缓存流
public class Buffer {
    public static void main(String[] args) {
        //缓存流读取
        {
            // 准备文件lol.txt其中的内容是
            // garen kill teemo
            // teemo revive after 1 minutes
            // teemo try to garen, but killed again
            File f = new File("log.txt");
            // 创建文件字符流
            // 缓存流必须建立在一个存在的流的基础上
            try (
                    FileReader fr = new FileReader(f);
                    //BufferedReader的带参构造函数是一个流
                    BufferedReader br = new BufferedReader(fr);
                )
            {
                while (true) {
                    // 一次读一行
                    String line = br.readLine();
                    if (null == line)
                        break;
                    System.out.println(line);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        //缓存流写出
        {
            File f=new File("log1.txt");
            try(
                //创建文件字符流
                FileWriter fw=new FileWriter(f);
                //缓存流必须建立在一个存在的流的基础上
                PrintWriter pw=new PrintWriter(fw);
            ){
                //直接pw.println
                pw.println("garen kill teemo");
                //如果想要在不是缓存满了才写入硬盘
                //就需要使用pw.flush()
                //pw.flush()
                //否则会在缓存满了或者结束才写入到硬盘中
                pw.println("teemo revive after 1 minutes");
                pw.println("teemo try to garen, but killed again");
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

对象流

即常见的 Serializable
将对象流化方便传递.
必须实现Serializable接口

Hero类

package charactor;

import java.io.Serializable;

public class Hero implements Serializable {
    //表示这个类当前的版本,如果有了变化,比如新设计了属性,就应该修改这个版本号
    private static final long serialVersionUID = 1L;
    public String name;
    public float hp;

}

应用:

package stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import charactor.Hero;

public class TestStream {

    public static void main(String[] args) {
        //创建一个Hero garen
        //要把Hero对象直接保存在文件上,务必让Hero类实现Serializable接口
        Hero h = new Hero();
        h.name = "garen";
        h.hp = 616;

        //准备一个文件用于保存该对象
        File f =new File("d:/garen.lol");

        try(
            //创建对象输出流
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream oos =new ObjectOutputStream(fos);
            //创建对象输入流              
            FileInputStream fis = new FileInputStream(f);
            ObjectInputStream ois =new ObjectInputStream(fis);
        ) {
            oos.writeObject(h);
            Hero h2 = (Hero) ois.readObject();
            System.out.println(h2.name);
            System.out.println(h2.hp);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

常用的控制台输入System.in(Scanner)

package stream;

import java.util.Scanner;

public class TestStream {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        int a = s.nextInt();
        System.out.println("第一个整数:"+a);
        int b = s.nextInt();
        System.out.println("第二个整数:"+b);
    }
}

流关系图

Java Throwable

Throwable是个接口.用于定义异常和error的类.
异常分为两类:
error和exception.
exception里又分 运行时异常,可查异常

throws 和 throw两个关键词,第一个经常出现在函数声明中.

自定义异常:

class 类名 extends Exception{

    public 类名(){

    }
    public 类名(String msg){
        super(msg);
    }
}

Java 8 默认方法

在之前的学习中,Java 的接口只能声明抽象方法(不写abstract的那种).

但在Java 8 中,接口也可以实现有方法体的方法.被称之为默认方法.

但需要注意,在默认方法中,需要使用 default 关键字.

Like this:

package charactor;

public interface Mortal {
    public void die();

    default public void revive() {
        System.out.println("本英雄复活了");
    }
}

Java 接口

接口(interface)的继承需要使用实现(implements)关键词,可以多继承

首先我们新建一个AD类型英雄的接口:

package charactor;

public interface AD {
    //声明一个接口方法
    //物理伤害
    public void physicAttack();
}

然后我们新建一个AD英雄:

package charactor;

public class ADHero extends Hero implements AD{
    @Override
    public void physicAttack(){
        System.out.print("进行物理攻击");
    }
}

然后我们新建一个AP类型英雄的接口:

package charactor;

public interface AP {
    public void magicAttack();
}

然后我们实现一个AP英雄:

package charactor;

public class APHero extends Hero implements AP{
    @Override
    public void magicAttack(){
        System.out.println("魔法攻击");
    }
}

然后我们实现一个既可以魔法攻击,又可以物理攻击的英雄:

package charactor;

public class ADAPHero extends Hero implements AD,AP{
    @Override
    public void physicAttack(){
        System.out.println("物理攻击");
    }
    public void magicAttack(){
        System.out.println("魔法攻击");
    }
}

Java 枚举类

Enum,也是个类

因为是常量,所以一般大写

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}

测试:

public class HelloWorld {
    public static void main(String[] args) {
        Season season = Season.SPRING;
        switch (season) {
        case SPRING:
            System.out.println("春天");
            break;
        case SUMMER:
            System.out.println("夏天");
            break;
        case AUTUMN:
            System.out.println("秋天");
            break;
        case WINTER:
            System.out.println("冬天");
            break;
        }
    }
}

可以使用foreach:

public class HelloWorld {
    public static void main(String[] args) {
        for (Season s : Season.values()) {
            System.out.println(s);
        }
    }
}

Java 单例模式

1.饿汉式单例模式

GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new 得到新的实例。

GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取12行定义的对象,而且每一次都是获取同一个对象。 从而达到单例的目的。

这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例

package charactor;

public class GiantDragon {

    //私有化构造方法使得该类无法在外部通过new 进行实例化
    private GiantDragon(){

    }

    //准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个

    private static GiantDragon instance = new GiantDragon();

    //public static 方法,提供给调用者获取12行定义的对象
    public static GiantDragon getInstance(){
        return instance;
    }

}

测试:

package charactor;

public class TestGiantDragon {

    public static void main(String[] args) {
        //通过new实例化会报错
//      GiantDragon g = new GiantDragon();

        //只能通过getInstance得到对象

        GiantDragon g1 = GiantDragon.getInstance();
        GiantDragon g2 = GiantDragon.getInstance();
        GiantDragon g3 = GiantDragon.getInstance();

        //都是同一个对象
        System.out.println(g1==g2);
        System.out.println(g1==g3);
    }
}

2.懒汉式单例模式

懒汉式单例模式与饿汉式单例模式不同,只有在调用getInstance的时候,才会创建实例

package charactor;

public class GiantDragon {

    //私有化构造方法使得该类无法在外部通过new 进行实例化
    private GiantDragon(){       
    }

    //准备一个类属性,用于指向一个实例化对象,但是暂时指向null
    private static GiantDragon instance;

    //public static 方法,返回实例对象
    public static GiantDragon getInstance(){
        //第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
        if(null==instance){
            instance = new GiantDragon();
        }
        //返回 instance指向的对象
        return instance;
    }

}

测试:

package charactor;

public class TestGiantDragon {

    public static void main(String[] args) {
        //通过new实例化会报错
//      GiantDragon g = new GiantDragon();

        //只能通过getInstance得到对象

        GiantDragon g1 = GiantDragon.getInstance();
        GiantDragon g2 = GiantDragon.getInstance();
        GiantDragon g3 = GiantDragon.getInstance();

        //都是同一个对象
        System.out.println(g1==g2);
        System.out.println(g1==g3);
    }
}

单例模式三要素:

这个是面试的时候经常会考的点,面试题通常的问法是:

什么是单例模式?

回答的时候,要答到三元素
1. 构造方法私有化
2. 静态属性指向实例
3. public static的 getInstance方法,返回第二步的静态属性