第18章 Java I/O系统

第18章 Java I/O系统

18.1 File类

File类不仅仅指文件,还能代表一个目录下的一组文件。

18.1.1 目录列表器
public class Test {
    public static void main(String[] args) {
        File file = new File("E:\\test");
        String[] strings = file.list(new DirFilter(".*\\.java"));
        // 忽略大小写排序
        Arrays.sort(strings,String.CASE_INSENSITIVE_ORDER);
        for (String string : strings) {
            System.out.println(string);
            /*
            test.java
            user.java
             */
        }
    }
}

// Filename过滤器
class DirFilter implements FilenameFilter{
    private Pattern pattern;
    public DirFilter(String regex){
       pattern = pattern.compile(regex);
    }
    // File.list()方法为每一个文件调用该方法
    @Override
    public boolean accept(File dir, String name) {
        return pattern.matcher(name).matches();
    }
}
// 使用匿名内部类代替实现类  
static FilenameFilter f(final String /*必须是final的*/regex){
        return new FilenameFilter() {
            private Pattern pattern = Pattern.compile(regex);
            @Override
            public boolean accept(File dir, String name) {
                return pattern.matcher(name).matches();
            }
        };
    }
 public static void main(String[] args) {
        File file = new File("E:\\test");
     	// 直接使用匿名内部类
        String[] strings = file.list(new FilenameFilter() {
            Pattern pattern = Pattern.compile(".*\\.java");
            @Override
            public boolean accept(File dir, String name) {
                return pattern.matcher(name).matches();
            }
        });

        Arrays.sort(strings,String.CASE_INSENSITIVE_ORDER);
        for (String string : strings) {
            System.out.println(string);
            /*
            test.java
            user.java
             */
        }
    }
18.1.2 目录实用工具
public final class Directory {
    // 产生由指定文件构成的数组
    public static File[] local(File dir,final String regex){
        return dir.listFiles(new FilenameFilter() {
            Pattern pattern = Pattern.compile(regex);
            @Override
            public boolean accept(File dir, String name) {
                return pattern.matcher(name).matches();
            }
        });
    }

    // 重载方法,接收一个文件地址
    public static File[] local(String path,String regex){
        return local(new File(path),regex);
    }

    public static class TreeInfo implements Iterable<File>{
        // 为什么要设置为public的?
        // walk()方法访问的是对象元组,它包含了两个List,可以让客户端选择使用哪一个
        // 目录集合
        public List<File> dirs = new ArrayList<>();
        // 文件集合
        public List<File> files = new ArrayList<>();

        @Override
        public Iterator<File> iterator() {
            return files.iterator();
        }

        // 增加其他对象的集合
        public void addAll(TreeInfo other){
            files.addAll(other.files);
            dirs.addAll(other.dirs);
        }

        public static TreeInfo walk(File start, String regex){
            return recurseDirs(start,regex);
        }

        public static TreeInfo walk(String path,String regex){
            return recurseDirs(new File(path),regex);
        }

        public static TreeInfo walk(File start){
            return recurseDirs(start,".*");
        }

        @Override
        public String toString() {

            return "目录:"+print(dirs)+"\n文件:"+print(files);
        }

        // 格式化输出集合
        // 针对长度为0和1的集合做了特俗处理
        private static <T>String print(Collection<T> c){
            if (c.size() == 0){
                return "[]";
            }else {
                StringBuilder sb = new StringBuilder("[");
                for (T t : c) {
                    if (c.size() != 1){
                        sb.append("\r\n");
                    }
                    sb.append(t);
                }
                if (c.size() != 1){
                    sb.append("\r\n");
                }
                sb.append("]");
                return sb.toString();
            }
        }

        // 核心的处理方法
        private static TreeInfo recurseDirs(File starDir, String regex){
            TreeInfo result = new TreeInfo();
            for (File file : starDir.listFiles()) {
                // 判断是否是文件夹
                if (file.isDirectory()) {
                    result.dirs.add(file);
                    // 递归循环
                    result.addAll(recurseDirs(file,regex));
                }else {
                    if (file.getName().matches(regex)){
                        result.files.add(file);
                    }
                }
            }
            return result;
        }
    }
}

  public static void main(String[] args) {
        File[] files = Directory.local("E:\\test",".*java");
        System.out.println(Arrays.toString(files));
      // [E:\test\test.java, E:\test\user.java]
        Directory.TreeInfo treeInfo = Directory.TreeInfo.walk(new File("E:\\test"));
        System.out.println(treeInfo);
      /*
      	目录:[E:\test\com]
        文件:[
        E:\test\a.txt
        E:\test\test.java
        E:\test\user.java
        ]
        */
    }

在目录中穿行,并处理指定的文件。

// 遍历文件中的文件,并可根据Strategy对其进行处理
public class ProcessFiles {
    public  interface Strategy{
        void process(File file);
    }
    // 策略
    private Strategy strategy;
    // 后缀
    private String ext;

    public ProcessFiles(Strategy strategy, String ext) {
        this.strategy = strategy;
        this.ext = ext;
    }

    public void start(String[] paths) throws IOException {
        // 如果不含参数,默认为当前文件目录
        if (paths.length == 0){
            processDirectoryTree(new File("."));
        }else {
            // 遍历传入地址
            for (String path : paths) {
                File file = new File(path);
                // 判断是否为文件目录
                if (file.isDirectory()){
                    processDirectoryTree(file);
                }
                // 文件直接处理
                else {
                    // 为什么要这样处理?岂不是每个文件都会被处理了
                    if (!path.endsWith("."+ext)){
                        path += "."+ ext;
                    }
                    File canonicalFile = new File(path).getCanonicalFile();
                    strategy.process(canonicalFile);
                }
            }
        }
    }
    // 文件路径解决方案
    private void processDirectoryTree(File rootFile) throws IOException {
        Directory.TreeInfo treeInfo = Directory.TreeInfo.walk(rootFile./*获取绝对路径*/getAbsolutePath(),".*\\."+ext);
        for (File file : treeInfo.files) {
            // 为什么不直接传入File?
            //区别是该方法内的获得规范路径:getCanonicalPath(),它会返回文件的绝对路径,并替换掉各操作系统相应的符号
            strategy.process(file.getCanonicalFile());
        }
    }
}

  public static void main(String[] args) throws IOException {
        ProcessFiles processFiles = new ProcessFiles(new ProcessFiles.Strategy() {
            @Override
            public void process(File file) {
                System.out.println(file);
            }
        },"java");
        String[] paths = {"E:\\test"};
        processFiles.start(paths);
    }
/*
E:\test\com\home.java
E:\test\test.java
E:\test\user.java
*/
18.1.3 目录的检查及创建
public class Test {
    public static void main(String[] args) throws IOException {
        File dir = new File("e:/test/b");
        // 创建文件夹
        dir.mkdirs();
        File file = new File("e:/test/b/a.txt");
        // 创建新文件
        file.createNewFile();
        File oldFile = new File("e:\\test\\a.txt");
        File newFile = new File("e:\\test\\b.txt");
        // 将旧文件移动到新文件,并改名
        oldFile.renameTo(newFile);
        fileData(newFile);
    }

    private static void fileData(File file) throws IOException {
        System.out.println("绝对地址:"+ file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());
        System.out.println("是否可读:"+file.canRead());
        System.out.println("是否可写:"+file.canWrite());
        System.out.println("文件名:"+file.getName());
        System.out.println("上级目录:"+file.getParent());
        System.out.println("地址:"+file.getPath());
        System.out.println("大小:"+file.length());
        System.out.println("最后修改时间:"+file.lastModified());
        System.out.println("是否为文件:"+file.isFile());
        System.out.println("是否为目录:"+file.isDirectory());
        System.out.println("是否存在:"+file.exists());

    }
/*
绝对地址:e:\test\b.txt
E:\test\b.txt
是否可读:true
是否可写:true
文件名:b.txt
上级目录:e:\test
地址:e:\test\b.txt
大小:2
最后修改时间:1605599850616
是否为文件:true
是否为目录:false
是否存在:true
*/
}

18.2 输入和输出

任何有能力产出数据的数据源对象或有能力接收数据的接收端对象。

输入流: InputStream Reader,表示从不同数据源产生输入的类。

输出流: OutputStream Writer,决定了输出要去往的目标。

数据源:

  • 字节数组;
  • String对象;
  • 文件;
  • 管道 从一段输入,从另一端输出;
  • 多个流组成的序列;
  • 其他数据源。

InputStream

2021-06-19-InputStream类型d.png

OutputStream

2021-06-19-FilterOutputStream类型.png

18.3 添加属性和有用的接口

FilterInputStream和FilterOutputStream用来提供装饰器类接口以控制特定输入流和输出流。

2021-06-19-FilterInputStream类型.png

2021-06-19-FilterOutputStream类型.png

18.4 Reader和Writer

Reader和Writer在JDK1.1中出现,提供兼容Unicode与面向字符的I/O功能,其比旧的I/O更快,其设计的主要目的是国际化。

适配器类InputStreamReader可以把InputStream转为Reader。

字节流和字符流对应关系

与FilterInputStream和FilterOutputStream对应的类

未发生变化的类

18.5 自我独立的类:RandomAccessFile

其与字节流没有任何关系,直接继承自Object类,方法大多是本地的,适用于大小已知的记录文件。

18.6 I/O流的典型使用方式

字节数组接收数据

  public static void main(String[] args) throws IOException {
        File file = new File("e:/test/a.txt");
        InputStream in = new FileInputStream(file);
        byte[] bytes = new byte[512];
        int len; // 读取的字节数
        while ( (len = in.read(bytes)) != -1){
            // 将字节数组转为字符串,字节数组的起始位置为0,长度为len
            System.out.print(new String(bytes,0,len));
        }
        in.close();
    }

缓冲输入文件

  public static void f(String path) throws IOException {
        // 为了提升速度,使用缓冲读取文件
        // 编码只有在InputStreamReader/OutputStreamWriter才有
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path),"gbk"/*指定编码*/));
        StringBuilder sb = new StringBuilder();
        String s ;
        // 读取一行,并判断是否为null
        while ((s = br.readLine()) != null){
            // 注意:这里需要换行,因为readLine()将换行符去掉了
            sb.append(s).append("\n");
        }
        // 关闭读取流
        br.close();
        System.out.println(sb.toString());
    }

从内存输入

  public static void main(String[] args) throws IOException {
        // 读取字符串
        StringReader sr = new StringReader("呵呵\nhello");
        int c;
      	// 每次读取一个字节
        while ((c = sr.read()) != -1){
            System.out.print((char)c);
        }
    }

格式化的内存输入

 public static void main(String[] args) throws IOException {
        InputStream in = new ByteArrayInputStream("12.3ae".getBytes());
        DataInputStream di = new DataInputStream(in);
        try {
            while (true) {
                System.out.println((char) di.readByte());
            }
        } // 通过异常结束输出 
        catch (EOFException e) {
            System.out.println("输出完毕!");
        }
    }
   public static void main(String[] args) throws IOException {
        InputStream in = new ByteArrayInputStream("12.3ab".getBytes());
        DataInputStream di = new DataInputStream(in);
        while (/*查看还有多少可供读取的数*/di.available() != 0) {
            System.out.println((char)di.readByte());
        }
    }

基本的文件输出

  public static void main(String[] args) throws IOException {
        // 通过使用缓冲提高速度
        PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("e:/test/c.txt")));
        for (int i = 0; i < 5; i++) {
            // write()会转为ascii输出
            // print()会原样输出
            pw.println("这是第"+i+"行");
        }
        pw.close();
    }
public static void main(String[] args) throws IOException {
      File file = new File("e:/test/e.txt");
    // 快捷方式,减少装饰工作,仍会进行缓冲
      PrintWriter pw = new PrintWriter(file);
        for (int i = 0; i < 5; i++) {
            pw.println(i);
        }
        pw.close();
    }

储存和恢复数据

 public static void main(String[] args) throws IOException {
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("e:/test/a.txt")));
        out.writeInt(12);
        out.writeDouble(12.5);
        // java针对UTF-8的一种变体
        out.writeUTF("丁");
        out.close();
        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("e:/test/a.txt")));
        System.out.println(in.readInt());
        System.out.println(in.readDouble());
        System.out.println(in.readUTF());
        in.close();
    }
// 问题:存储和读取数据顺序必须一致

读写随机访问文件

public class Test {
    static String path = "e:/test/a.txt";
    
    public static void main(String[] args) throws IOException {
        RandomAccessFile ra = new RandomAccessFile(path, "rw");
        for (int i = 0; i < 5; i++) {
            // 写
            ra.writeDouble(i * 1.44);
        }
        ra.writeUTF("末尾");
        ra.close();
        f(path);
        /*
        0.0
        1.44
        2.88
        4.32
        5.76
        末尾
        */
        ra = new RandomAccessFile(path, "rw");
        // 跳到某个位置
        // double是8字节,2*8表示跳到第2个数的末尾,第3个数的起点
        ra.seek(2 * 8);
        // 这里会覆盖第3个double数
        ra.writeDouble(47.47);
        ra.close();
        f(path);
        /*
        0.0
        1.44
        47.47
        4.32
        5.76
        末尾
        */
    }

    public static void f(String path) throws IOException {
        // r:只读 rw:读写
        RandomAccessFile ra = new RandomAccessFile(path, "r");
        for (int i = 0; i < 5; i++) {
            // 读
            System.out.println(ra.readDouble());
        }
        System.out.println(ra.readUTF());
    }
}

其他

public static void main(String[] args) throws IOException, InterruptedException {
      File file = new File("e:/test/a.txt");
      BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file,/*追加而不是重写*/true));
      out.write("呵呵".getBytes());
      // 刷新缓冲区,将数据写入到基础流中(写入文本中)
      out.flush();
    }

18.7 文件读写的实用工具

读写文本文件

/ 实现读写文本文件,并将读取的文件以固定形式分割放入Listpublic class TextFile extends ArrayList<String> {

    // 读取
    public static String read(String fileName) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String s;
            while ((s = br.readLine()) != null) {
                sb.append(s).append("\n");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    // 写
    public static void write(String fileName, String txt, boolean append) {
        try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fileName, append)))) {
            pw.print(txt);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public TextFile(String fileName,String regex){
        // 以固定形式分割文件并读到List中
        super(Arrays.asList(read(fileName).split(regex)));
        if ("".equals(get(0))) {
            remove(0);
        }
    }

    // 默认以换行分割
    public TextFile(String fileName){
        this(fileName,"\n");
    }

    // 将List中的元素写入到文件中
    public void write(String fileName,boolean append){
        try (PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fileName,append)))){
            this.forEach(s ->pw.println(s));
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }
}

   public static void main(String[] args) throws IOException {
        TextFile textFile = new TextFile("e:/test/a.txt","\\d");
        System.out.println(textFile);
        textFile.addAll(Arrays.asList("4","e"));
        textFile.write("e:/test/a.txt",false);
    }

读取二进制文件

// 读取二进制文件
public class BinaryFile {
    public static byte[] read(String fileName) {
        try (
                BufferedInputStream in = new BufferedInputStream(new FileInputStream(fileName))
        ) {
            // 创建指定大小的字节数组
            // 问题:数组可能会巨大
            byte[] bytes = new byte[in.available()];
            in.read(bytes);
            return bytes;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

18.8 标准I/O

从标准输入中读取

按照标准模型,Java提供了System.in、System.out、Systeim.err。

out和err被包装成了printStream,但是in没有,需要我们包装。

// 打印我们输入的信息  
public static void main(String[] args) throws IOException {
       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
       String s;
       while ((s = br.readLine()) != null && s.length() > 0){
           System.out.println(s);
       }
    }
public static void main(String[] args) throws IOException {
        BufferedInputStream in = new BufferedInputStream(System.in);
        byte[] bytes = new byte[128];
        int len;
        while ((len = in.read(bytes)) != -1){
            String s = new String(bytes,0,len);
            // 至少有一个回车符
            if (s.length() == 1)
                System.exit(0);
            System.out.println(s);
        }
    }

将System.out转换成PrintWriter

  public static void main(String[] args) throws IOException {
        // 开启自动清空缓存
        PrintWriter pw = new PrintWriter(System.out,true);
        pw.println("呵呵");
    }

标准I/O重定向

  // 重定向输出
public static void out() throws FileNotFoundException {
        OutputStream out = new FileOutputStream("e:/test/a.txt");
        System.setOut(new PrintStream(out));
        System.out.println("你好");
    }
// 重定向输入
    public static void in() throws IOException {
        InputStream in = new FileInputStream("e:/test/a.txt");
        System.setIn(in);
        byte[] bytes = new byte[128];
        System.in.read(bytes);
        System.out.println(new String(bytes));
    }

18.9 进程控制

在Java内部执行操作系统的其他程序,并控制这些程序的输入和输出。

 public static void main(String[] args) throws IOException {
        // 进程构建
        ProcessBuilder processBuilder = new ProcessBuilder(/*运行命令*/"java Test".split(" "));
        // 获得运行时环境
        Map<String, String> environment = processBuilder.environment();
        environment.put("CLASSPATH","E:\\test");
        // 运行进程
        Process process = processBuilder.start();
        // 获得进程的输出(对于该程序是输入)
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String s;
        while ((s = br.readLine()) != null){
            System.out.println(s);
        }
    }

18.10 新I/O

JDK1.4中引入新I/O,其目的是提高速度。

相关概念:

通道 包含数据的地方

缓冲器 与通道交互

旧I/O中的FileInputStream/FileOutputStream/RandomAccessFile可以直接获取通道;

字符流不能产生通道,Channels类提供了在通道中产生Reader和Writer的方法。

public class Test {
    public static int SIZE = 1024;
    public static void main(String[] args) throws IOException {
        // 获得通道
        FileChannel fc = new FileOutputStream("e:\\test\\a.txt").getChannel();
        // wrap(byte[])将字节数组包装为ByteBuffer
        fc.write(ByteBuffer.wrap("hello nio,".getBytes()));
        fc.close();
        File file;
        fc = new RandomAccessFile("e:\\test\\a.txt", "rw").getChannel();
        // 移动到文件最后
        fc.position(fc.size());
        fc.write(ByteBuffer.wrap("append".getBytes()));
        fc.close();
        fc = new FileInputStream("e:\\test\\a.txt").getChannel();
        // allocateDirect()返回与操作系统更高耦合的直接缓冲器,速度可能更快
        ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
        // 读取到缓冲器中
        fc.read(byteBuffer);
        // 做好让别人读取的准备
        byteBuffer.flip();
        while (/*判断是否还有字节*/byteBuffer.hasRemaining()){
            System.out.print((char) byteBuffer.get());
        }
    }
}

复制文件

public class Test {
    public static int SIZE = 8;
    public static void main(String[] args) throws IOException {
        // 读
        FileChannel in = new FileInputStream("e:\\test\\a.txt").getChannel();
        // 写
        FileChannel out = new FileOutputStream("e:\\test\\b.txt").getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(SIZE);
        while (in.read(buffer) != -1){
            // 准备被读取
            buffer.flip();
            // 写
            // 下次循环会追加
            out.write(buffer);
            // 清空缓冲区,为下次读作准备
            buffer.clear();
        }
    }
}
public class Test {
    public static int SIZE = 8;
    public static void main(String[] args) throws IOException {
        // 读
        FileChannel in = new FileInputStream("e:\\test\\a.txt").getChannel();
        // 写
        FileChannel out = new FileOutputStream("e:\\test\\b.txt").getChannel();
        // 专门的复制文件
        // in从0开始,size()长度,复制到out文件
        in.transferTo(0,in.size(),out);
        // out文件来自于in,从0开始,size()长度
        out.transferFrom(in,0,in.size());
    }
}
18.10.1 转换数据

前边读取文件时存在的一个问题:每次只读一个字节的数据。

ByteBuffer可以通过asCharBuffer()转为CharBuffer,CharBuffertoString()为字符串。

public class Test {
    public static int SIZE = 1024;
	// 读取字符
    public static void main(String[] args) throws IOException {
        FileChannel channel = new FileInputStream("e:\\test\\a.txt").getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
        channel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(byteBuffer.asCharBuffer()); // 乱码
        byteBuffer.rewind(); // 重置位置
        String encode = System.getProperty("file.encoding"); // 获得程序的编码
        System.out.println(encode); // UTF-8
        // 使用该文件的编码对缓冲器解码
        System.out.println(Charset.forName("GB2312").decode(byteBuffer));
        channel.close();

        // 存入时编码
        channel = new FileOutputStream("e:/test/a.txt").getChannel();
        // 这里使用是UTF-16,UTF-16BE,utf-8不可以;另一种utf-16LE也不可以
        channel.write(ByteBuffer.wrap("福".getBytes("UTF-16BE")));
        channel.close();
        channel = new FileInputStream("e:\\test\\a.txt").getChannel();
        byteBuffer.clear();
        channel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(byteBuffer.asCharBuffer());
        byteBuffer.clear();
        channel.close();

        // 存入CharBuffer
        channel = new FileOutputStream("e:/test/a.txt").getChannel();
        byteBuffer.asCharBuffer().put("景");
        channel.write(byteBuffer);
        byteBuffer.clear();
        channel.close();
        channel = new FileInputStream("e:\\test\\a.txt").getChannel();
        channel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(byteBuffer.asCharBuffer());
    }
}

编码

 public static void main(String[] args) throws IOException {
        Charset defaultCharset = Charset.defaultCharset(); // 默认编码
        SortedMap<String, Charset> charsets = Charset.availableCharsets(); // 支持的所有编码
        Charset charset = Charset.forName("UTF-8"); // 获得指定编码
        ByteBuffer byteBuffer = charset.encode("嗨"); // 编码
        CharBuffer charBuffer = charset.decode(byteBuffer); // 解码
        System.out.println(charBuffer); // 嗨
    }
18.10.2 获取基本类型
public class Test {
    public static int SIZE = 1024;

    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
        int i = 0;
        // 检查缓冲器内容是否都置为0了
        while (i++ < byteBuffer.limit()) {
            if (byteBuffer.get() != 0) {
                System.out.println("缓冲器内容没有置为0");
            }
        }
        System.out.println("i:" + i);
        byteBuffer.rewind();
        byteBuffer.asCharBuffer().put("abc");
        char c;
        while ((c = byteBuffer.getChar()) != 0) {
            System.out.print(c + " ");
        }
        byteBuffer.rewind();
        byteBuffer.asIntBuffer().put(123);
        System.out.println("整型:" + byteBuffer.getInt());
        byteBuffer.rewind();
        byteBuffer.asDoubleBuffer().put(12.3);
        System.out.println("浮点:" + byteBuffer.getDouble());
        byteBuffer.rewind();
        byteBuffer.asShortBuffer().put((short) 12);
        System.out.println("shot:" + byteBuffer.getShort());
        byteBuffer.rewind();
        byteBuffer.asLongBuffer().put(145L);
        System.out.println("long:" + byteBuffer.getLong());
        byteBuffer.clear();
    }
}
/*
i:1025
a b c 整型:123
浮点:12.3
shot:12
long:145
*/
18.10.3 视图缓冲器

视图缓冲器 让我们通过某个特定的基本类型的视窗查看底层的ByteBuffer,但是底层仍然是Bytebuffer,对视窗的修改都会映射到底层中。

视图基本示例

public class Test {
    public static int SIZE = 1024;
    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(SIZE);
        // 获得整型视图
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        // put数组
        intBuffer.put(new int[]{1,2,3,4,5});
        // 获得指定位置的值
        System.out.println(intBuffer.get(2)); // 3
        // 修改
        intBuffer.put(1,10);
        intBuffer.flip();
        while (intBuffer.hasRemaining()){
            System.out.print(intBuffer.get()+" "); // 1 10 3 4 5 
        }
    }
}

各种视图中转换

 public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[]{0,0,0,0,0,0,0,'a'});
        while (byteBuffer.hasRemaining())
            // 0->0 1->0 2->0 3->0 4->0 5->0 6->0 7->97 
            System.out.print(byteBuffer.position()/*地址*/+"->"+byteBuffer.get()+" ");
        System.out.println();
        byteBuffer.rewind();
        // int
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        while (intBuffer.hasRemaining()){
            // 0->0 1->97 
            System.out.print(intBuffer.position()/*地址*/+"->"+intBuffer.get()+" ");
        }
        System.out.println();
        intBuffer.rewind();
        // double
        DoubleBuffer doubleBuffer = byteBuffer.asDoubleBuffer();
        while (doubleBuffer.hasRemaining()){
            // 0->4.8E-322 
            System.out.print(doubleBuffer.position()/*地址*/+"->"+doubleBuffer.get()+" ");
        }
        System.out.println();
        doubleBuffer.rewind();
        // char
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        while (charBuffer.hasRemaining()){
            // 0->  1->  2->  3->a 
            System.out.print(charBuffer.position()/*地址*/+"->"+charBuffer.get()+" ");
        }
        System.out.println();
        charBuffer.clear();
    }

各基本类型对应结果

字节存放次序

高位字节 其值变化后使整个字的值变化最大的那个字节。

高位优先 将高位字节放在地址最低的储存单元上。

低位优先 将高位字节放在地址最高的储存单元上。

  public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(12);
        byteBuffer.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(byteBuffer.array()));
        byteBuffer.rewind();
        // 设置高位优先
        byteBuffer.order(ByteOrder.BIG_ENDIAN);
        byteBuffer.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(byteBuffer.array()));
        byteBuffer.rewind();
        // 设置低位优先
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        byteBuffer.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(byteBuffer.array()));
    }
/*
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]
*/
// char是两个字节,可以看到,高位优先时a是0,97;低位优先时是97,0
18.10.4 用缓冲器操纵数据

ByteBuffer是数据进出通道的唯一方式,基本类型的缓冲器不能转为ByteBuffer,但是基本类型的缓冲器可以操作底层的ByteBuffer,所以可以认为没有任何限制。

nio之间的关系

18.10.5 缓冲器的细节

Buffer由数据和四个索引组成。

索引

  • mark:标记;
  • position:位置;
  • limit:界限;可以读取到的最大位置
  • capacity:容量;缓冲器最大可以装的数据

相关方法

public class Test {

	 public static void main(String[] args) throws IOException {
        char[] chars = "UsingBuffers".toCharArray();
        ByteBuffer byteBuffer = ByteBuffer.allocate(chars.length * 2);
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        /*可以通过CharBuffer的wrap()方法产生一个CharBuffer,但是这里的
        底层仍然使用了ByteBuffer,因为是ByteBuffer唯一可以与通道交互的
         */
        // charBuffer = CharBuffer.wrap(chars);
        // 此时位置移动到了末尾
        charBuffer.put(chars);
        // 要将位置移动到开始,才能输出所有值
        System.out.println(charBuffer.rewind()); // UsingBuffers
        f(charBuffer); // 交换
        System.out.println(charBuffer.rewind()); // sUniBgfuefsr
        f(charBuffer);
        System.out.println(charBuffer.rewind()); // UsingBuffers
    }

    // 交换相邻的两个字符
    public static void f(CharBuffer charBuffer) {
        while (charBuffer.hasRemaining()) {
            charBuffer.mark();
            char c1 = charBuffer.get();
            char c2 = charBuffer.get();
            // 重置到标记的位置
            charBuffer.reset();
            charBuffer.put(c2).put(c1);
        }
    }
}

细节解析:

  • 开始时position和mark指向0,limit和capacity指向最大值;
  • 输出的元素是[position,limit];
  • 调用get()和put()会将position向后移动,但是带索引的重载不会移动;
  • mark()方法将mark移到position位置;
  • reset()方法将position移到mark位置;
  • rewind()将position移动到初始位置。
18.10.6 内存映射文件

内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,这可以让我们假定整个文件都在内存中。

public class Test {
    public static int SIZE = 0x8FFFFFF; // 128M
    public static void main(String[] args) throws IOException {
        MappedByteBuffer mappedByteBuffer = new RandomAccessFile("e:/test/a.txt", "rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, SIZE);
        for (int i = 0; i < SIZE; i++) {
            mappedByteBuffer.put((byte)'a');
        }
        System.out.println("输出完毕");
        for (int i = SIZE/2; i < SIZE / 2 + 6; i++) {
            System.out.println(mappedByteBuffer.get(i));
        }
    }
}

MappedByteBuffer 特殊类型的直接缓冲器,必须指定初始位置和长度,这使我们可以映射某个大文件的较小部分,它继承在ByteBuffer,我们可以使用很多方法,如asCharBuffer.

性能

public class Test {
    private static int numInts = 200000;
    private static int readAndWrite = 10000;

    // 测试类
    private static abstract class Tester {
        private String name;

        public Tester(String name) {
            this.name = name;
        }

        // 执行的方法
        public void runTest() throws IOException {
            System.out.print(name + ": ");
            long start = System.nanoTime();
            test();
            System.out.println(System.nanoTime() - start);
        }

        public abstract void test() throws IOException;
    }

    // 6种比较对象
    private static Tester[] testers = {
            new Tester("io 写") {
                @Override
                public void test() throws IOException {
                    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("e:/test/a.txt")));
                    for (int i = 0; i < numInts; i++) {
                        out.writeInt(i);
                    }
                    out.close();
                }
            },
            new Tester("nio 写") {
                @Override
                public void test() throws IOException {
                    FileChannel channel = new RandomAccessFile("e:/test/a.txt", "rw").getChannel();
                    IntBuffer intBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size()).asIntBuffer();
                    for (int i = 0; i < numInts; i++) {
                        intBuffer.put(i);
                    }
                    channel.close();
                }
            },
            new Tester("io 读") {
                @Override
                public void test() throws IOException {
                    DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("e:/test/a.txt")));
                    for (int i = 0; i < numInts; i++) {
                        in.readInt();
                    }
                    in.close();
                }
            },
            new Tester("nio 读") {
                @Override
                public void test() throws IOException {
                    FileChannel channel = new RandomAccessFile("e:/test/a.txt", "r").getChannel();
                    IntBuffer intBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()).asIntBuffer();
                    while (intBuffer.hasRemaining()) {
                        intBuffer.get();
                    }
                    channel.close();
                }
            },
            new Tester("io 读写") {
                @Override
                public void test() throws IOException {
                    RandomAccessFile accessFile = new RandomAccessFile("e:/test/a.txt", "rw");
                    accessFile.writeInt(1);
                    for (int i = 0; i < readAndWrite; i++) {
                        // 一个整数是4字节,索引这里要-4
                        accessFile.seek(accessFile.length() - 4);
                        accessFile.writeInt(accessFile.readInt());
                    }
                    accessFile.close();
                }
            },
            new Tester("nio 读写") {
                @Override
                public void test() throws IOException {
                    FileChannel channel = new RandomAccessFile("e:/test/a.txt", "rw").getChannel();
                    IntBuffer intBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size()).asIntBuffer();
                    intBuffer.put(0);
                    for (int i = 1; i < readAndWrite; i++) {
                        intBuffer.put(intBuffer.get(i - 1));
                    }
                    channel.close();
                }
            }
    };

    public static void main(String[] args) throws IOException {
        for (Tester tester : testers) {
            tester.runTest();
        }
    }
}
/*
io 写: 20793800
nio 写: 7365400
io 读: 16946400
nio 读: 4548600
io 读写: 238458600
nio 读写: 1009200
*/
// 可以看到,尽管io已用nio重写,在同样的情况下nio明显比io快
18.10.7 文件加锁

加锁机制:允许我们同步的访问某个作为共享资源的文件。该锁对操作系统的其他进程也起作用。

 public static void main(String[] args) throws IOException, InterruptedException {
        FileOutputStream out = new FileOutputStream("e:/test/a.txt",true);
        // tryLock()非阻塞式,不能获得就返回
        // lock()阻塞式,会等到获得锁
        FileLock fileLock = out.getChannel().tryLock();
     	// 判断该锁是共享还是独占
     	fileLock.isShared();
        if (fileLock != null){
            System.out.println("已上锁");
            TimeUnit.MILLISECONDS.sleep(1000);
            System.out.println("锁已释放");
        }
    }
// 对文件部分上锁
// 起始位置,长度,是否是共享锁
// tryLock(long position, long size, boolean shared)

由操作系统提供对独占锁和共享锁的支持,如果不支持共享锁,则都是独占锁。

对映射文件部分加锁

public class Test {
    private static FileChannel channel;
    private static int SIZE = 0x8FFFFFF; // 128M

    public static void main(String[] args) throws IOException, InterruptedException {
        channel = new RandomAccessFile("e:/test/a.txt","rw").getChannel();
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);
        for (int i = 0; i < SIZE; i++) {
            mappedByteBuffer.put((byte) 'a');
        }
        new LockAndModify(mappedByteBuffer,0,SIZE/3);
        new LockAndModify(mappedByteBuffer,SIZE/2,SIZE/2+SIZE/4);
        System.in.read();
    }

    private static class LockAndModify extends Thread{
        private ByteBuffer buffer;
        private int start,end;
        public LockAndModify(ByteBuffer buffer,int start,int end){
            this.start = start;
            this.end = end;
            buffer.limit(end);
            buffer.position(start);
            // 返回该缓冲器的一个分片,从position到limit
            this.buffer = buffer.slice();
            // 开启线程
            start();
        }

        @SneakyThrows
        @Override
        public void run() {
            // 锁只能通过通道获得
            FileLock lock = channel.lock(start, end, false);
            System.out.println("获得锁 "+start+" "+end);
            while (buffer.position() < buffer.limit()-1){
                buffer.put((byte) (buffer.get()+1));
            }
            lock.release();
            System.out.println("释放锁 "+start+" "+end);
        }
    }

}

18.11 压缩

压缩类是InputStream和OutputStream继承层次的一部分,压缩类库是按照字节而不是字符处理的。

用GZIP进行简单压缩

注意:GZIP即是GZ

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader("e:/test/a.txt"));
        BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("e:/test/a.gz")));
        String s;
        System.out.println("输出压缩文件==");
        while ((s = reader.readLine()) != null) {
            out.write(s.getBytes());
        }
        reader.close();
        out.close();
        reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream("e:/test/a.gz"))));
        System.out.println("读取压缩文件==");
        while ((s = reader.readLine()) != null){
            System.out.println(s);
        }
        reader.close();
    }

用Zip进行多文件保存

public class Test {
    private static String[] paths={"e:/test/a.txt","e:/test/b.txt"};
    public static void main(String[] args) throws IOException {
        // 输出zip文件
        // 字节输出流
        FileOutputStream out = new FileOutputStream("e:/test/test.zip");
        // 检验输出流
        CheckedOutputStream cops = new CheckedOutputStream(out, new Adler32());
        // 压缩输出流
        ZipOutputStream zops = new ZipOutputStream(cops);
        // 缓冲输出流
        BufferedOutputStream bout = new BufferedOutputStream(zops);
        zops.setComment("设置注释");
        // 遍历多个文件
        for (String path : paths) {
            // 读取文件
            BufferedReader reader = new BufferedReader(new FileReader(path));
            // 加入要压缩的文件
            // 解决多级目录问题
            path = path.substring(path.length()-5,path.length());
            // ZipEntry包含很多功能,代表zip中的文件
            zops.putNextEntry(new ZipEntry(path));
            // 这里最好使用读取每一行,否则中文可能乱码
            String c;
            while ((c= reader.readLine()) != null){
                bout.write(c.getBytes());
            }
            reader.close();
            // 清空缓冲区
            bout.flush();
        }
        // 关闭输出流
        bout.close();

        // 读取zip文件
        // 类似的输入
        FileInputStream in = new FileInputStream("e:/test/test.zip");
        CheckedInputStream cips = new CheckedInputStream(in,new Adler32());
        ZipInputStream zips = new ZipInputStream(cips);
        BufferedInputStream bips = new BufferedInputStream(zips);
        // 接收zip中的文件
        ZipEntry ze;
        // 接收zip中文件的内容
        byte[] bytes = new byte[128];
        // 判断zip中是否由文件
        while ((ze = zips.getNextEntry()) != null){
            int len;
            // 读取文件
            while ((len = bips.read(bytes) )!= -1){
                System.out.println(new String(bytes,0,len));
            }
        }
        bips.close();
    }
}
// Checksum类型:Adler32快,CRC32慢但准确
// 压缩并不局限于文件,可以压缩任何格式的文件

Java档案文件

# 将当前目录下的类文件都加到my.jar中
jar cf my.jar *.class
# 展示my.jar中的列表
jar tf my.jar
# 查看my.jar更详细信息
jar tvf my.jar
# 将com cn文件夹及其子内容添加到my.jar,并展示详细信息
jar cvf my.jar com cn

jar不支持添加和更新,也不支持移动文件到jar中,但是跨平台。

18.12 对象序列化

对象序列化 将那些实现了Serializable接口的对象转换成一个字节序列,并能够以后将这个字节序列完全恢复为原来的对象。

其是轻量级的,因为其必须在程序中显式的序列化和反序列化,而不是通过关键字定义。

// 示例
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Worm worm = new Worm(2, 'a');
        // 使用文件序列化
        System.out.println("输出对象:"+worm);
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("e:/test/a.out"));
        oout.writeObject("测试String");
        oout.writeObject(worm);
        oout.close();
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream("e:/test/a.out"));
        String s = (String) oin.readObject();
        System.out.println("文件读入:"+s);
        worm = (Worm) oin.readObject();
        System.out.println("文件读入:"+worm);
        oin.close();

        // 使用缓存序列化
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout2 = new ObjectOutputStream(bout);
        oout2.writeObject("测试String");
        oout2.writeObject(worm);
        // 关了数据也会被写进ByteArrayOutputStream,不知道为什么不关
        oout2.flush();
        ObjectInputStream oin2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
        s = (String) oin2.readObject();
        System.out.println("文件读入:"+s);
        worm = (Worm) oin2.readObject();
        System.out.println("文件读入:"+worm);
        oout2.close();
        oin2.close();
    }
}

// 数据类
class Data implements Serializable {
    private int i;

    public Data(int i) {
        this.i = i;
    }

    @Override
    public String toString() {
        return Integer.toString(i);
    }
}

//蠕虫类
class Worm implements Serializable {
    private Random random = new Random();
    private Data[] datas = {
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10))};
    private int i;
    private char c;
    private Worm next;
    public Worm(int i,char c){
        this.i = i;
        this.c = c;
        // 如果i-1>0则有下一个链接
        if (--i > 0){
            next = new Worm(i, (char) (c+1));
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(c).append("(");
        for (Data data : datas) {
            sb.append(data);
        }
        sb.append(")");
        if (next != null){
            sb.append("\n").append(next);
        }
        return sb.toString();
    }
}
18.12.1 寻找类

将一个类从序列化状态恢复时,虚拟机必须可以找到序列化的Class对应的类。

18.12.2 序列化的控制

Externalizable接口

作为一个代替Serializable的接口,可以对序列化/反序列化做出控制;在反序列化时,会调用默认构造器。

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
       Blip blip = new Blip("s",1);
        // 使用文件序列化
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("e:/test/a.out"));
        oout.writeObject(blip);
        oout.close();
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream("e:/test/a.out"));
        blip = (Blip) oin.readObject();
        System.out.println(blip); // Blip{s='s', i=1}
        oin.close();
    }
}

// Externalizable的两个方法如果没有实现,则s为nulli为0
// 如果被序列化的类有继承关系,也必须调用基类的实现方法
class Blip implements Externalizable{
    private String s;
    private int i;
    // 默认构造器必须是public的,Externalizable会调用默认构造器,调不到时会报错
    public Blip(){

    }
    public Blip(String s, int i) {
        this.s = s;
        this.i = i;
    }


    // 实现对输出的控制
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(s);
        out.writeObject(i);
    }

    // 实现对输入的控制
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        s = (String) in.readObject();
        i = (int) in.readObject();
    }

    @Override
    public String toString() {
        return "Blip{" +
                "s='" + s + '\'' +
                ", i=" + i +
                '}';
    }
}

transient(关键字)

标识字段,表示不需要序列化


public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        Login login = new Login("ding","d123");
        System.out.println(login);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
        out.writeObject(login);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
        login = (Login) in.readObject();
        System.out.println(login);
    }
}

class Login implements Serializable{
    private Date date = new Date();
    private String name;
    private transient String password;

    public Login(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Blip{" +
                "date=" + date +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Externalizable替代方法

只实现Serializable并且添加如下两个方法,则可以达到和Externalizable一样的效果

 private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        SerialCtl sCtl = new SerialCtl("test1","test2");
        System.out.println(sCtl);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
        // 在序列化时会检查被序列化的对象是否实现了自己的writeObject(),如果有,则调用。
        out.writeObject(sCtl);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
        // 可以看到,这里成功了,但是没有调用默认的构造器
        sCtl = (SerialCtl) in.readObject();
        System.out.println(sCtl);
    }
}

class SerialCtl implements Serializable{
    private String s1;
    private transient String s2;

    public SerialCtl(String s1, String s2) {
        this.s1 = s1;
        this.s2 = s2;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        // 如果先执行默认的序列化,可以调用该方法
        out.defaultWriteObject();
        out.writeObject(s2);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        // 必须先调用默认方法
        in.defaultReadObject();
        s2 = (String) in.readObject();
    }

    @Override
    public String toString() {
        return "SerialCtl{" +
                "s1='" + s1 + '\'' +
                ", s2='" + s2 + '\'' +
                '}';
    }
}
18.12.3 使用持久性
class House implements Serializable{}

class Animal implements Serializable{
    private String name;
    private House house;

    public Animal(String name, House house) {
        this.name = name;
        this.house = house;
    }

    @Override
    public String toString() {
        return name+":"+super.toString()+" house:"+house;
    }
}

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        House house = new House();
        Animal a1 = new Animal("狗",house);
        Animal a2 = new Animal("猫",house);
        List<Animal> list = Arrays.asList(a1,a2);
        System.out.println(list);
        ObjectOutputStream out1 = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
        out1.writeObject(list);
        out1.writeObject(list);
        out1.close();
        ObjectOutputStream out2 = new ObjectOutputStream(new FileOutputStream("e:/test/b.txt"));
        out2.writeObject(list);
        out2.close();
        ObjectInputStream in1 = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
        list = (List<Animal>) in1.readObject();
        System.out.println(list);
        System.out.println(list);
        list = (List<Animal>) in1.readObject();
        ObjectInputStream in2 = new ObjectInputStream(new FileInputStream("e:/test/b.txt"));
        list = (List<Animal>) in2.readObject();
        System.out.println(list);
    }
}

/*
[狗:com.io.Animal@27c170f0 house:com.io.House@5451c3a8, 猫:com.io.Animal@2626b418 house:com.io.House@5451c3a8]
[狗:com.io.Animal@49097b5d house:com.io.House@6e2c634b, 猫:com.io.Animal@37a71e93 house:com.io.House@6e2c634b]
[狗:com.io.Animal@49097b5d house:com.io.House@6e2c634b, 猫:com.io.Animal@37a71e93 house:com.io.House@6e2c634b]
[狗:com.io.Animal@7e6cbb7a house:com.io.House@7c3df479, 猫:com.io.Animal@7106e68e house:com.io.House@7c3df479]
*/
// 可以看到在同一个流中,同一指向的对象反序列化后也会指向同一个地方
// 放入不同流后的恢复,则不会指向同一地址
// 注意:准备序列化的对象最好不要在序列化过程中乱改,会导致不确定性

静态问题

注意:静态域不能被序列化。

搭建一个画图的系统


abstract class Shape implements Serializable {
    // 定义颜色
    public static final int RED = 1, BULE = 2, GREEN = 3;
    private int x;
    private int y;
    // 维度
    private int dimension;
    private static Random random = new Random();
    private static int couter;
    public Shape(int x, int y, int dimension) {
        this.x = x;
        this.y = y;
        this.dimension = dimension;
    }

    public abstract void setColor(int color);
    public abstract int getColor();

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + getColor() + " " + x + " " + y + " " + dimension;
    }
	// 工厂类
    public static Shape shapeFactory() {
        int x = random.nextInt(100);
        int y = random.nextInt(100);
        int dimension = random.nextInt(100);
        switch (couter++ % 3) {
            case 0:
                return new Circle(x, y, dimension);
            case 1:
                return new Square(x, y, dimension);
            default:
                return new Line(x, y, dimension);
        }
    }
}

class Circle extends Shape {
    // 静态域不能继承
    private static int color = RED;

    public Circle(int x, int y, int dimension) {
        super(x, y, dimension);
    }

    @Override
    public void setColor(int color) {
        Circle.color = color;
    }

    @Override
    public int getColor() {
        return color;
    }
}

class Square extends Shape {
    private static int color = RED;

    public Square(int x, int y, int dimension) {
        super(x, y, dimension);
    }

    @Override
    public void setColor(int color) {
        Square.color = color;
    }

    @Override
    public int getColor() {
        return color;
    }
}

class Line extends Shape {
    private static int color = RED;

    public Line(int x, int y, int dimension) {
        super(x, y, dimension);
    }

    @Override
    public void setColor(int color) {
        Line.color = color;
    }

    @Override
    public int getColor() {
        return color;
    }
	// 如果希望序列化静态域,我们必须手动去作
    public static void serializeStaticState(ObjectOutputStream out) throws IOException {
        out.writeInt(color);
    }

    public static void derializeStaticState(ObjectInputStream in) throws IOException {
        color = in.readInt();
    }
}
 public static void main(String[] args) throws IOException, ClassNotFoundException {
     // Class   
     List<Class<? extends Shape>> shapeTypes = Arrays.asList(Circle.class,Square.class,Line.class);
        List<Shape> shapes = new ArrayList<>();
     	// 对象
        for (int i = 0; i < 5; i++) {
            shapes.add(Shape.shapeFactory());
        }
        System.out.println(shapes);
     // [Circle 1 9 61 4, Square 1 56 80 70, Line 1 63 62 7, Circle 1 37 90 66, Square 1 96 97 68]
     // 设置颜色
        for (int i = 0; i < 5; i++) {
            shapes.get(i).setColor(Shape.GREEN);
        }
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("e:/test/a.txt"));
        out.writeObject(shapeTypes);
        Line.serializeStaticState(out);
        out.writeObject(shapes);
        System.out.println(shapes);
     // [Circle 3 9 61 4, Square 3 56 80 70, Line 3 63 62 7, Circle 3 37 90 66, Square 3 96 97 68]
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
        shapeTypes = (List<Class<? extends Shape>>) in.readObject();
        Line.derializeStaticState(in);
        shapes = (List<Shape>) in.readObject();
        System.out.println(shapes);
     // [Circle 3 9 61 4, Square 3 56 80 70, Line 3 63 62 7, Circle 3 37 90 66, Square 3 96 97 68]
    }
/* 理论上,静态域不应该被序列化,为什么反序列化后,颜色是我们需要的?因为序列化和反序列化在一个JVM中,在反序列化时静态域被加载了,所以,此时,被序列化的对象可以获得我们想要的值。
*/
   public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<Shape> shapes = new ArrayList<>();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("e:/test/a.txt"));
        List<Class<? extends Shape>> shapeTypes = (List<Class<? extends Shape>>) in.readObject();
        Line.derializeStaticState(in);
        shapes = (List<Shape>) in.readObject();
        System.out.println(shapes);
       // [Circle 1 9 61 4, Square 1 56 80 70, Line 3 63 62 7, Circle 1 37 90 66, Square 1 96 97 68]
    }
/*此时可以看到,只有Line类的静态域是我们需要的值,而其他类的静态域都是他们的初始值。

18.13 XML

主要介绍了XOM类库的使用,但是目前几乎没有在用的了。

18.14 Preferences

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 用于个人偏好 systemNodeForPackage()用于通用的安装配置
        Preferences pref = Preferences.userNodeForPackage(Test.class);
        pref.put("name","ding");
        int age = pref.getInt("age",0);
        // 每次执行都不同,程序会利用系统资源帮助我们自动存储
        System.out.println(age);
        age++;
        pref.putInt("age",age);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/429660.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安装Proxmox VE虚拟机平台

PVE是专业的虚拟机平台&#xff0c;可以利用它安装操作系统&#xff0c;如&#xff1a;Win、Linux、Mac、群晖等。 1. 下载镜像 访问PVE官网&#xff0c;下载最新的PVE镜像。 https://www.proxmox.com/en/downloads 2. 下载balenaEtcher balenaEtcher用于将镜像文件&#…

「滚雪球学Java」:多线程(章节汇总)

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

数据可视化原理-腾讯-3D网格热力图

在做数据分析类的产品功能设计时&#xff0c;经常用到可视化方式&#xff0c;挖掘数据价值&#xff0c;表达数据的内在规律与特征展示给客户。 可是作为一个产品经理&#xff0c;&#xff08;1&#xff09;如果不能够掌握各类可视化图形的含义&#xff0c;就不知道哪类数据该用…

带你全方位体验 Amazon Connect

1.前言 授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在亚马逊云科技开发者社区、 知乎、自媒体平台、第三方开发者媒体等亚马逊云科技官方渠道。 近日亚马逊云科技在拉斯维加斯拉开了年度客户大会-亚马逊云科技 re:Invent 的序…

python网络爬虫教程笔记(1)

系列文章目录 文章目录 系列文章目录前言一、爬虫入门1.爬虫是什么&#xff1f;2.爬虫工作原理3.爬虫基本原理4.工作流程5.HTTP请求6.HTTP响应7.HTTP原理&#xff1a;证书传递、验证和数据加密、解密过程解析8.Urllib.request库的使用9.TCP3次握手&#xff0c;4次挥手过程 总结…

PHP+MySQL实现后台管理系统增删改查之够用就好

说明 最近要给博客弄个后台&#xff0c;不想搞得很复杂&#xff0c;有基本的增删改查就够了&#xff0c;到网上找了一圈发现这个不错&#xff0c;很实用&#xff0c;希望可以帮到大家&#xff0c;需要的朋友评论区留下邮箱&#xff0c;我安排发送。 演示效果 项目介绍 本项目…

【RISC-V 指令集】RISC-V 向量V扩展指令集介绍(一)-向量扩展编程模型

1. 引言 以下是《riscv-v-spec-1.0.pdf》文档的关键内容&#xff1a; 这是一份关于向量扩展的详细技术文档&#xff0c;内容覆盖了向量指令集的多个关键方面&#xff0c;如向量寄存器状态映射、向量指令格式、向量加载和存储操作、向量内存对齐约束、向量内存一致性模型、向量…

【2024.03.05】定时执行专家 V7.1 发布 - TimingExecutor V7.1 Release

目录 ▉ 软件介绍 ▉ 新版本 V7.1 下载地址 ▉ V7.1 新功能 ▼2024-03-03 V7.1 - 更新日志 ▉ V7.0 新UI设计 ▉ 软件介绍 《定时执行专家》是一款制作精良、功能强大、毫秒精度、专业级的定时任务执行软件。软件具有 25 种【任务类型】、12 种【触发器】触发方式&#x…

C++ 根据公式计算椭圆任意点到中心的距离

#include <iostream> using namespace std;double fact(int x) //定义阶乘函数。注意是double类型 {double y x; //注意是double类型for (int i x-1; i > 0; i--)y * i;return y; };double My_sin(int x) //定义sin函数。注意是double类型 {double y 0; //注意是do…

Linux性能即时评估60秒秘籍

下面这个清单适用于任何性能问题的分析工作&#xff0c;也反映了笔者在实际工作中&#xff0c;当登录到一台表现不佳的 Linux 系统中后&#xff0c;在最初60秒内通常会进行的操作。 uptime dmesg | tail && cat /var/log/messages vmstat 1 mpstat -P ALL 1 pidsta…

使用 gulp-cleanwxss 清除小程序无用样式代码

小程序在迭代中&#xff0c;因没有及时清理无用样式&#xff0c;造成包体积越来越大。而通过人工判断清除工作量大&#xff0c;因而使用 gulp-cleanwxss 实现脚本清除。 一、Demo 演示 二、实现步骤 1、全局安装 gulp 命令行工具 yarn global add gulp-cli2、局部安装依赖 gu…

一条SQL引起的系统不可用

一.前言 最近在运维系统&#xff0c;系统对客端突然报了403错误&#xff0c;从后台看发现了大量的慢SQL&#xff0c;导致查询超时&#xff0c;仔细分析我从来没见过那么厚颜无耻的SQL&#xff0c;一条SQL语句关联了一个大表&#xff08;6000数据&#xff09;查询了10次。我也很…

Android 14后台服务永久保活的技术方法

Android 14后台服务永久保活的技术方法 在本篇博客中&#xff0c;我们将探讨如何创建一个在Android系统中不会被杀死的后台服务。 第一步&#xff1a;创建一个后台服务。 在这一步中&#xff0c;我们需要创建一个后台服务的代码。 第二步&#xff1a;在AndroidManifest.xml中…

StarRocks实战——vivo基于 StarRocks 构建实时大数据平台

目录 前言 一、数据挑战 1.1 时效性挑战&#xff0c;业务分析决策需加速 1.2 访问量挑战&#xff0c;性能与稳定性亟待提高&#xff0c;支撑业务稳定运行 1.3 计算场景挑战&#xff0c;难以满足业务复杂查询需求 1.4. 运维挑战&#xff0c;用户查询体验需优化 二、OLA…

docker的网络配置

文章目录 1、网络模式1.1、bridge模式(默认模式)1.2、host模式 2、bridge模式3、自定义网络 1、网络模式 Docker在创建容器时有四种网络模式&#xff1a;bridge/host/container/none&#xff0c;bridge为默认不需要用–net去指定&#xff0c;其他三种模式需要在创建容器时使用…

Spring(22) Spring中的9种设计模式

目录 一、简单工厂模式&#xff08;Simple Factory&#xff09;二、工厂方法模式&#xff08;Factory Method&#xff09;三、单例模式&#xff08;Singleton&#xff09;四、适配器模式&#xff08;Adapter&#xff09;五、代理模式&#xff08;Proxy&#xff09;七、观察者模…

洛谷p1225 c++(使用高精度)

题解: 一开始我这个代码想到的是使用递归来求解 int digui(int n){int sum=0;if(n==1)sum=1;if(n==2)sum=2;if(n==1||n==2)return sum;if(n>2){return sum+=digui(n-1)+digui(n-2);} } 但是后面发现明显超时,我试图用记忆化搜索来抢救一下,所以就有了下面代码 int di…

前端食堂技术周刊第 114 期:Interop 2024、TS 5.4 RC、2 月登陆浏览器的新功能、JSR、AI SDK 3.0

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;凉拌鸡架 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…

Github 2024-03-05 Python开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-03-05统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目10TypeScript项目1 稳定扩散Web UI 创建周期&#xff1a;512 天开发语言&#xff1a;Python协议类…

数仓实战——京东数据指标体系的构建与实践

目录 一、如何理解指标体系 1.1 指标和指标体系的基本含义 1.2 指标和和标签的区别 1.3 指标体系在数据链路中的位置和作用 1.4 流量指标体系 1.5 指标体系如何向上支撑业务应用 1.6 指标体系背后的数据加工逻辑 二、如何搭建和应用指标体系 2.1 指标体系建设方法—OS…