Java 8 IO/NIO 增强

更便捷的文本行处理

BufferedReader 提供了一个lines()方法用于返回文本行的流Stream<String>Files类也提供了一个同名的方法lines(Path, Charset)返回文本行的流,在返回的流上结合Lambda可以进行一序列便捷的处理。

System.out.println("\nBufferedReader.lines");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("text.txt")));
bufferedReader
          .lines()
          .filter(line -> line.length() > 2)
          .filter(line -> line.matches("\\d+"))
          .forEach(line -> System.out.println(line));

System.out.println("\nFiles.lines");
Stream<String> stream = Files.lines(Paths.get("text.txt"), Charset.forName("utf8"));
stream.filter(line -> line.length() > 5)
          .forEach(line -> System.out.println(line));

在Java SE 8 b116的实现里,Stream是继承自AutoCloseable接口,所以可以结合try-with-resourse机制来确保资源的关闭。

目录遍历

Files新增了3个方法用于遍历目录:Files.list(Path), Files.walk(Path, int, FileVisitOption...), Files.walk(Path, FileVisitOption...)

Files.list方法只遍历当前目录下的文件和目录,Files.walk方法遍历指定目录下的所有文件和目录,除非指定了最大深度。
第一个walk方法的int类型参数用于指定遍历的最大深度。

System.out.println("\nFiles.list");
Files.list(Paths.get("."))
          .filter(path -> !path.toString().endsWith(".iml"))
          .forEach(path -> System.out.println(path));

System.out.println("\nFiles.walk");
Files.walk(Paths.get("."), 3, FileVisitOption.values())
          .filter(path -> path.toString().startsWith(".\\src"))
          .forEach(path -> System.out.println(path));

System.out.println("\nFiles.walk 2");
Files.walk(Paths.get("."), FileVisitOption.values())
     .filter(path -> path.toString().endsWith(".java"))
     .forEach(path -> System.out.println(path));

文件查找

查找的功能其实跟文件的遍历差不多,多了一个匹配器。

System.out.println("\nFiles.find:");
BiPredicate<Path, BasicFileAttributes> matcher = (path, attr) -> path.endsWith(Paths.get("Collec"));
Files
          .find(Paths.get("."), 3,  matcher, FileVisitOption.values())
          .forEach(path -> System.out.println(path.toString()));

UncheckedIOException

由于Iterator/Stream的方法签名不允许抛出IOException,当进行IO操作出现异常时,需要通过不受检查的异常类来传递这个异常信息。UncheckedIOException就是做这个的。

总的来说,io包变化不大,主要是结合Lambda进行一些改进,很多IO方面的改进已经在Java 7里提供了。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

java.util.HashMap 源码解读及其进化

概述

java.util.HashMap 是JDK里散列的一个实现,JDK6里采用位桶+链表的形式实现,Java8里采用的是位桶+链表/红黑树的方式,非线程安全。关于散列可以看这篇文章

这篇文章主要是对JDK6和Java8里java.util.HashMap的一些源码的解读。Java8里的改进主要是为了解决哈希碰撞攻击。

这个源码解读主要关注基础数据结构、put(key,value)逻辑 和遍历所有键值对的逻辑。
继续阅读

Java 8 之 java.time 包

包概述

java.time 包是在JDK8新引入的,提供了用于日期、时间、实例和周期的主要API。

java.time包定义的类表示了日期-时间概念的规则,包括instants, durations, dates, times, time-zones and periods。这些都是基于ISO日历系统,它又是遵循 Gregorian规则的。

所有类都是不可变的、线程安全的。

继续阅读

J.U.C 包

概述

J.U.C 包是java.util.concurrent包的简写。这个包在JDK5引入,大大增强了Java的并发特性。JDK7还引入ForkJoin框架。

该包提供的能力主要包括:可重入锁,具有原子性操作属性的类,线程池执行服务,调度执行服务,增强的线程安全容器,线程关卡,信号器,ForkJoin任务执行框架等等。
继续阅读

正则表达式反向引用

参考: http://java.dzone.com/articles/backreferences-java-regular

以前没用过这种用法,mark。

介绍

反向引用是基于的,组就是把多个字符当作单一的单元看待。组是通过在一对小括号(())内放置正则字符来创建的,每对小括号对应一个组。

反向引用是便捷的,允许重复正则而不需要再写一次。可以通过 \# 来引用前面定义的组,# 是组的序号,从 1 开始。

正则引擎在处理匹配时,要求 反向引用与所引用的组 匹配的内容必须是一样的:即,(\d\d\d)\1 匹配 123123,而不匹配123456

继续阅读

struts2 漏洞 和 缓存攻击

本文主要讲我对漏洞的探索和探索过程中发现的一个可以进行缓存攻击的坑。

问题

struts2 的漏洞在网上已经够热闹了,各个技术站点都是头条显示,微博上也有大佬转发。

这个漏洞的危害行在于允许执行远程命令,直接攻击服务器,危害无穷;根源在于struts2框架把用户输入的数据当作命令执行了,这也是一切注入估计的根源。

今天旁边的同事soul在调试官方给出的可攻击的demo,想看看到底是怎么攻击;我好奇的是既然攻击可以远程启动一个子进程的话,那么那些输入的java代码应该会被编译,然后再执行,如果是这样的话,这框架不会是对每个输入都进行动态编译吧,这会很影响性能的,所以我想看看struts2到底是怎么处理的,就看我同事调试。
继续阅读

Java Socket HTTP

本文最早发表在ITeye上的个人博客http://wen866595.iteye.com/blog/1168658,现在移植到这里,也是一种记忆。

用Java Socket 实现发送HTTP GET请求和读取响应。


public class LearnHttp {
    private static final byte CR = '\r';
    private static final byte LF = '\n';
    private static final byte[] CRLF = {CR, LF};

    public static void main(String[] args) throws UnknownHostException, IOException {
        new LearnHttp().testHttp();
    }
    
    public void testHttp() throws UnknownHostException, IOException {
        String host = "www.baidu.com";
        Socket socket = new Socket(host, 80);
        
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream();
        
        // 在同一个TCP连接里发送多个HTTP请求
        for(int i = 0; i < 2; i++) {
            writeRequest(out, host);
            readResponse(in);
            System.out.println("\n\n\n");
        }
    }
    
    private void writeRequest(OutputStream out, String host) throws IOException {
        // 请求行
        out.write("GET /index.html HTTP/1.1".getBytes());
        out.write(CRLF);        // 请求头的每一行都是以CRLF结尾的
        
        // 请求头
        out.write(("Host: " + host).getBytes()); // 此请求头必须
        out.write(CRLF);

        out.write(CRLF);        // 单独的一行CRLF表示请求头的结束

        // 可选的请求体。GET方法没有请求体
        
        out.flush();
    }

    private void readResponse(InputStream in) throws IOException {
        // 读取状态行
        String statusLine = readStatusLine(in);
        System.out.println("statusLine :" + statusLine);
        
        // 消息报头
        Map headers = readHeaders(in);
        
        int contentLength = Integer.valueOf(headers.get("Content-Length"));
        
        // 可选的响应正文
        byte[] body = readResponseBody(in, contentLength);
        
        String charset = headers.get("Content-Type");
        if(charset.matches(".+;charset=.+")) {
            charset = charset.split(";")[1].split("=")[1];
        } else {
            charset = "ISO-8859-1";     // 默认编码
        }
        
        System.out.println("content:\n" + new String(body, charset));
    }
    
    private byte[] readResponseBody(InputStream in, int contentLength) throws IOException {
        
        ByteArrayOutputStream buff = new ByteArrayOutputStream(contentLength);
        
        int b;
        int count = 0;
        while(count++ < contentLength) {
            b = in.read();
            buff.write(b);
        }
        
        return buff.toByteArray();
    }
    
    private Map readHeaders(InputStream in) throws IOException {
        Map headers = new HashMap();
        
        String line;
        
        while(!("".equals(line = readLine(in)))) {
            String[] nv = line.split(": ");     // 头部字段的名值都是以(冒号+空格)分隔的
            headers.put(nv[0], nv[1]);
        }
        
        return headers;
    }
    
    private String readStatusLine(InputStream in) throws IOException {
        return readLine(in);
    }
    
    /**
     * 读取以CRLF分隔的一行,返回结果不包含CRLF
     */
    private String readLine(InputStream in) throws IOException {
        int b;
        
        ByteArrayOutputStream buff = new ByteArrayOutputStream();
        while((b = in.read()) != CR) {
            buff.write(b);
        }
        
        in.read();      // 读取 LF
        
        String line = buff.toString();
        
        return line;
    }
    
}

值得记下的教训:

InputStream.read()返回-1表示流的结束,即流被关闭了。在这次实现同一个Socket发送多个HTTP请求的过程中,一开始总是以为一个响应报文的结束是在InputStream.read返回-1时,导致第一次读取响应时总是很久才结束,第二次的请求虽然发出去了,但响应总是空的。

读取第一次请求的响应要很久是因为,这是在等待服务器关闭连接,百度的基本上是60秒。第二次是空的是因为,请求虽然发出去了,但服务器写响应的流已经被关闭,所以也读不到响应。

导致这个问题的根本原因在于没有记住:TCP是面向流的,并不知道传输的消息的开始和结束,这是由上层的协议去控制的,这里是HTTP协议。

2013-09-01:这是当时尝试HTTP pipeline,由于百度服务器不支持而失败。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

JNA实例

有时候Java需要通过调用dll来操作底层的硬件或者现有的组件,直接使用JNI是容易出错的,且需要编写本地的C代码。这里介绍另一个调用dll的更简单的方法:JNA。这里介绍JNA调用dll的本地方法、JNA回调Java方法以及JNA相关的一些知识点。如果去留意的话,会发现现在很多jar包都用JNA来调用dll了。

继续阅读

同步方法与同步代码块的区别

当JVM执行一个方法时,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

同步化一个方法块会超过JVM对获取对象锁和异常处理的内置支持,要求以字节代码显式写入功能。如果使用同步方法读取一个方法的字节代码,就会看到有十几个额外的操作用于管理这个功能。

public class Sync {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }

}

反编译出的字节码:

D:\Java\jdk1.6.0_02\bin>javap -c Sync
Compiled from "Sync.java"

public class Sync extends java.lang.Object{
public Sync();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public synchronized int synchronizedMethodGet();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field i:I
   4:   ireturn

public int synchronizedBlockGet();
  Code:
   0:   aload_0
   1:   dup
   2:   astore_1
   3:   monitorenter
   4:   aload_0
   5:   getfield        #2; //Field i:I
   8:   aload_1
   9:   monitorexit
   10:  ireturn
   11:  astore_2
   12:  aload_1
   13:  monitorexit
   14:  aload_2
   15:  athrow
  Exception table:
   from   to  target type
     4    10    11   any
    11    14    11   any

}

欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。