Java NIO其实就是JDK1.4中加入的新的基于Channel和Buffer的Iuput/Output方式。我个人认为NIO主要有以下两个优点:
NIO中主要有三个主要的结构:Channel(数据通道)、Buffer(缓存区)、Selector(选择器)。
Channel就是通道的意思,类似与IO中的流(InputStream/OutputStream),只不过Channel是有读写两种模式,而IO中的流都是单一模式的。Channel的是实现主要分为网络读写和文件读写两大块,主要实现有:
Buffer就是缓冲区的意思,在Buffer中主要有以下几个属性:
public abstract class Buffer {
// ......
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
// ......
}
一般情况下,这四个元素能组成缓冲区的读和写两种基本模式。
Buffer有多种类型,最常用的比如ByteBuffer、CharBuffer、MappedByteBuffer,其他的还有DoubleBuffer、FloatBuffer等等。
使用Buffer之前还需要对Buffer的大小通过allocate(int size)方法进行分配,其实就是创建数组,然后初始化mark、position、capacity等属性。对于文件读写来说,上面两个组件就能完成任务,因为文件读取是阻塞模式的,所以不能使用Selector。下面是一个文件读取的简单例子:
public static void main(String[] args) throws IOException {
RandomAccessFile file = new RandomAccessFile("C:\\Users\\AAA\\Desktop\\工作\\Nio.txt", "r");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder sb = new StringBuilder();
while ((channel.read(buffer)) != -1) { // 循环读取文件
buffer.flip(); // 写->读
while (buffer.hasRemaining()) {
char ch = (char)buffer.get();
if(ch == 'D') {
buffer.mark(); // 标记
}
sb.append(ch);
}
// mark的使用
buffer.reset();
while (buffer.hasRemaining()) {
sb.append((char)buffer.get());
}
buffer.clear(); // 读->写
}
// 元素数据:ABCDEFGHIJK ->现在输出数据:ABCDEFGHIJKEFGHIJK
System.out.println(sb.toString());
}
下面说明Buffer中主要使用到的一些方法:
Selector选择器具有同时管理多个Channel的能力(通过询问每一个Channel是否已经准备好I/O操作实现),这样做的好处就是可以通过一个线程来管理多个Tcp或者Udp通道,减少线程的上下文切换。
下面先上一个例子来说明Selector的使用:
/**
* 服务器端
*/
public class NioSocketServer {
//通道管理器
private Selector selector;
//获取一个ServerSocket通道,并初始化通道
public NioSocketServer init(int port) throws IOException{
//获取一个ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
//获取通道管理器
selector=Selector.open();
//将通道管理器与通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,
//只有当该事件到达时,Selector.select()会返回,否则一直阻塞。
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
return this;
}
public void listen() throws IOException{
System.out.println("服务器端启动成功");
//使用轮询访问selector
while(true){
//当有注册的事件到达时,方法返回,否则阻塞。
selector.select();
//获取selector中的迭代器,选中项为注册的事件
Iterator ite=selector.selectedKeys().iterator();
while(ite.hasNext()){
SelectionKey key = ite.next();
//删除已选key,防止重复处理
ite.remove();
//客户端请求连接事件
if(key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//获得客户端连接通道
SocketChannel channel = server.accept();
channel.configureBlocking(false);
//向客户端发消息
channel.write(ByteBuffer.wrap(new String("send message to client").getBytes()));
//在与客户端连接成功后,为客户端通道注册SelectionKey.OP_READ事件。
channel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端请求连接事件");
}else if(key.isReadable()){//有可读数据事件
//获取客户端传输数据可读取消息通道。
SocketChannel channel = (SocketChannel)key.channel();
//创建读取数据缓冲器
ByteBuffer buffer = ByteBuffer.allocate(10);
int read = channel.read(buffer);
byte[] data = buffer.array();
String message = new String(data);
System.out.println("receive message from client, size:" + buffer.position() + " msg: " + message);
// ByteBuffer outbuffer = ByteBuffer.wrap(("server.".concat(msg)).getBytes());
// channel.write(outbuffer);
}
}
}
}
public static void main(String[] args) throws IOException {
new NioSocketServer().init(9981).listen();
}
}
/**
* 客户端
*/
public class NioClient {
//管道管理器
private Selector selector;
public NioClient init(String serverIp, int port) throws IOException{
//获取socket通道
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
//获得通道管理器
selector=Selector.open();
//客户端连接服务器,需要调用channel.finishConnect();才能实际完成连接。
channel.connect(new InetSocketAddress(serverIp, port));
//为该通道注册SelectionKey.OP_CONNECT事件
channel.register(selector, SelectionKey.OP_CONNECT);
return this;
}
public void listen() throws IOException {
System.out.println("客户端启动");
//轮询访问selector
while(true){
//选择注册过的io操作的事件(第一次为SelectionKey.OP_CONNECT)
selector.select();
Iterator ite = selector.selectedKeys().iterator();
while(ite.hasNext()){
SelectionKey key = ite.next();
//删除已选的key,防止重复处理
ite.remove();
if(key.isConnectable()){
SocketChannel channel=(SocketChannel)key.channel();
//如果正在连接,则完成连接
if(channel.isConnectionPending()){
channel.finishConnect();
}
channel.configureBlocking(false);
//向服务器发送消息
channel.write(ByteBuffer.wrap(new String("send message to server.").getBytes()));
//连接成功后,注册接收服务器消息的事件
channel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接成功");
}else if(key.isReadable()){ //有可读数据事件。
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.read(buffer);
byte[] data = buffer.array();
String message = new String(data);
System.out.println("recevie message from server:, size:" + buffer.position() + " msg: " + message);
// ByteBuffer outbuffer = ByteBuffer.wrap(("client.".concat(msg)).getBytes());
// channel.write(outbuffer);
}
}
}
}
public static void main(String[] args) throws IOException {
new NioClient().init("127.0.0.1", 9981).listen();
}
}
在注册时我们会返回SelectionKey对像,这里说一下SelectionKey包含哪些内容,通过源码我们可以看到:
感兴趣的对象包括以下几个操作:
// 读操作
public static final int OP_READ = 1 << 0;
// 写操作
public static final int OP_WRITE = 1 << 2;
// 连接动作
public static final int OP_CONNECT = 1 << 3;
// 接受连接
public static final int OP_ACCEPT = 1 << 4;
附件的作用:可以将一个或者多个附加对象绑定到SelectionKey上,以便容易的识别给定的通道。通常有两种方式:
// 1.在注册的时候直接绑定
SelectionKey key=channel.register(selector,SelectionKey.OP_READ,theObject);
// 2.在绑定完成之后附加
selectionKey.attach(theObject);//绑定
这是在一个单线程中使用一个Selector处理3个Channel的图示:
这里分享一个利用Nio实现对大文件(大于10GB的文件)内容截取的功能(按时间段),文件的格式大致如下所示:
[2018-05-12 12:10:00.234] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[2018-05-12 12:10:00.530] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[2018-05-12 12:10:01.040] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[2018-05-12 12:10:01.040] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[2018-05-12 12:10:01.045] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
......
......
......
[2018-05-12 20:33:02.100] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[2018-05-12 20:33:02.105] XXXX XXXXXX XXXXXX XXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
实现的思路如下所示,首先我们要知道直接内存映射最多只支持2G内存(Integer.MAX_VALUE)。这里我选择了1G内存,通过遍历的方式(每一个G为一个块)确认开始位置在哪一个块中。然后再通过查询算法(比如折半查询等进行查询),这里要注意行的处理。代码如下所示:
public class Searcher {
/** 默认显示行数 */
static int defaultRows = SysConfig.getResultShowNum();
/** 时间format */
static SimpleDateFormat format1 = new SimpleDateFormat("yyyyMMddHHmmssSSS");
static SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
static DecimalFormat decimalFormat = new DecimalFormat("#.00");
static {
format2.setTimeZone(TimeZone.getTimeZone("GMT+16"));
}
/**
* 错误日志提取功能,并提供关键字查询功能
*
* @param indexPath 索引目录
* @param start 开始
* @param end 结束
* @param queryString 查询的字符串
* @param reverse false 升序 | true 降序
* @param save 是否保存到文件
* @param savePath 保存路径
* @throws Exception
*/
@SuppressWarnings("resource")
public static List getErrorLogSegment(String indexPath,
String start, String end, String queryString,
boolean reverse, boolean save, String savePath) throws Exception{
List resultList = new ArrayList();
IndexSearcher searcher = null;
IndexReader reader = null;
Analyzer analyzer = new StandardAnalyzer();
try {
reader = DirectoryReader
.open(FSDirectory.open(Paths.get(indexPath)));
searcher = new IndexSearcher(reader);// 检索工具
// 查询配置区
// 多条件查询
BooleanQuery.Builder builder = new BooleanQuery.Builder();
// 时间区间
Query query = new TermRangeQuery("date", new BytesRef(
format1.format(format2.parse(start))), new BytesRef(
format1.format(format2.parse(end))), true, true);
builder.add(query, Occur.MUST);
// 查询关键词
if (queryString != null && !queryString.equals("")) {
QueryParser parser = new QueryParser("content", analyzer);
Query wordsQuery = parser.parse(queryString);
builder.add(wordsQuery, Occur.MUST);
}
Sort sort = new Sort(
new SortField("date", SortField.Type.LONG, reverse));
int searchNum = save ? Integer.MAX_VALUE : defaultRows; // 查询的个数
TopDocs results = searcher.search(builder.build(), searchNum, sort);
ScoreDoc[] hits = results.scoreDocs;
//System.out.println("总命中行数:" + hits.length);
if (save) {// 保存结果到文件中
FileWriter fwrite = new FileWriter(savePath, false);
for (int i = 0; i < hits.length; i++) {
Document doc = searcher.doc(hits[i].doc);
fwrite.write(format2.format(format1.parse(doc.get("date")))
+ " " + doc.get("content") + "\r\n");
fwrite.flush();
}
return null;
} else {// 显示结果到界面
int size = hits.length >= defaultRows ? defaultRows : hits.length;
for (int i = 0; i < size; i++) {
Document doc = searcher.doc(hits[i].doc);
resultList.add(format2.format(format1.parse(doc.get("date")))
+ " " + doc.get("content"));
}
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return resultList;
}
/**
* 日志片段截取,并提供关键字查询功能
* 若选择保存结果到文件,则不返回查询的结果;若不选择保存结果到文件,则返回前指定个数的结果
*
* @param filePath 文件路径
* @param indexPath 索引目录
* @param start 开始
* @param end 结束
* @param queryRequest 查询的请求
* @param save 是否保存
* @param savePath 保存路径
* @throws Exception
*/
public static List getLogSegment(String filePath, String indexPath, String start,
String end, String queryRequest, boolean save, String savePath) throws Exception {
List resultList = new ArrayList();
File file = new File(filePath);
try{
if (!file.exists()) {
throw new LogAnalysisException("未找到文件!");
}
if (queryRequest == null || queryRequest.equals("")) {
long startPos = getLogPos(file, "[" + start + "]", true);
startPos = startPos==-1?0:startPos;
long endPos = getLogPos(file, "[" + end + "]", false);
endPos = endPos==-1?file.length():endPos;
//System.out.println("pos:" + startPos + "," + endPos);
if(save){
while(true){
// FileChanel.map的最大长度不能超过Integer.MAX_VALUE(大约一次性最多读取2G内容,大于2G需要分次读取)
if(endPos - startPos > Integer.MAX_VALUE){
readFileByMBBAndWriter(file, startPos, (long)(startPos + Integer.MAX_VALUE), savePath);
startPos = startPos + (long)Integer.MAX_VALUE;
} else {
readFileByMBBAndWriter(file, startPos, endPos, savePath);
break;
}
}
} else {
resultList = readFileByMappedByteBuffer(file, startPos, endPos);
}
} else {
if(save){
getRequestLogSegment(indexPath, start, end, queryRequest, null, null, SortType.Default, false, save, savePath);
}else{
resultList = getRequestLogSegment(indexPath, start, end, queryRequest, null, null, SortType.Default, false, save, savePath);
}
}
} catch (Exception e) {
throw e;
}
return resultList;
}
/**
* 传入位置列表,并找出所有的行
*
* @param file
* @param posList 位置列表
* @return
* @throws Exception
*/
@SuppressWarnings("unused")
private static List readFileByMappedByteBuffer(File file, List posList) throws Exception{
List resultList = new ArrayList();
MappedByteBuffer mbb = null;
FileInputStream fis = null;
FileChannel fChannel = null;
try {
fis = new FileInputStream(file);
fChannel = fis.getChannel();
for (long pos : posList) {
mbb = fChannel.map(FileChannel.MapMode.READ_ONLY, pos, 10*1024);
char ch = (char) mbb.get();
StringBuilder line = new StringBuilder();
while (mbb.hasRemaining()) {
ch = (char) mbb.get();
line.append(ch);
if (ch == '\n') {
resultList.add(line.toString());
line.delete(0, line.length());
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
if (fChannel != null)
fChannel.close();
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultList;
}
/**
* 截取文件中start和end中间数据(读取显示到界面)
*
* @param file
* @param start
* @param end
* @return
* @throws Exception
*/
private static List readFileByMappedByteBuffer(File file, long start,
long end) throws Exception {
List resultList = new ArrayList();
FileInputStream fis = null;
FileChannel fChannel = null;
ByteBuffer buffer = ByteBuffer.allocate(1024*1024);
Charset charset = Charset.forName("UTF-8");// 解决乱码问题
CharBuffer charBuffer = null;
int rows = 0;
try{
fis = new FileInputStream(file);
fChannel = fis.getChannel();
fChannel.position(start);
StringBuilder line = new StringBuilder();
outer:
while(fChannel.read(buffer) != -1){
buffer.flip();
charBuffer = charset.decode(buffer);
while (charBuffer.hasRemaining()) {
char ch = charBuffer.get();
if(ch != '\n'){
line.append(ch);
} else {
rows++;
line.append(ch);
resultList.add(line.toString());
line.delete(0, line.length());
if(rows >= defaultRows*2)
break outer;
}
}
buffer.clear();
charBuffer.clear();
}
}catch (IOException e) {
try {
if (fChannel != null)
fChannel.close();
if (fis != null)
fis.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
return resultList;
}
/**
* 截取文件中start和end中间数据(保存到文件中)
*
* @param file
* @param start
* @param end
* @param savePath
* @throws Exception
*/
private static void readFileByMBBAndWriter(File file, long start,
long end, String savePath) throws Exception {
MappedByteBuffer mbb = null;
FileInputStream fis = null;
FileChannel finChannel = null;
FileChannel foutChanel = null;
FileOutputStream fout = null;
try {
File saveFile = new File(savePath);
saveFile.getParentFile().mkdirs();
fis = new FileInputStream(file);
finChannel = fis.getChannel(); // 获取fis的channel
fout = new FileOutputStream(savePath, true);
foutChanel = fout.getChannel();
// 长度最大值有限制不能超过 Size exceeds Integer.MAX_VALUE
mbb = finChannel.map(FileChannel.MapMode.READ_ONLY, start, end - start);
//byte[] buffer = new byte[(int) (end-start)];
foutChanel.write(mbb);
//mbb.get(buffer);
//fout.write(buffer);
fout.flush();
// 清理缓存区
mbb.clear();
// 解决mbb内存释放问题
//clean(mbb);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
if (finChannel != null)
finChannel.close();
if (foutChanel != null)
foutChanel.close();
if (fis != null)
fis.close();
if (fout != null)
fout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*public static void clean(final Object buffer) {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Object run() {
try{
Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
getCleanerMethod.setAccessible(true);
Cleaner cleaner = (Cleaner)getCleanerMethod.invoke(buffer, new Object[0]);
cleaner.clean();
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
}*/
/**
* 获取日志中某个请求的详细日志
*
* @param datetime
* @param sessionAndThread
* @param url
* @param filePath
* @return
* @throws Exception
*/
public static List getRequestDetailLog(String datetime, String sessionAndThread, String url, String filePath) throws Exception{
List list = new ArrayList();
File file = new File(filePath);
// 获取时间所在的位置
long postion = getLogPos(file, datetime, true);
FileInputStream fis = null;
FileChannel finChannel = null;
ByteBuffer buffer = ByteBuffer.allocate(1024);// 1MB
try{
fis = new FileInputStream(file);
finChannel = fis.getChannel();
finChannel.position(postion);
StringBuilder line = new StringBuilder();
outer:
while(finChannel.read(buffer) != -1){
buffer.flip();
while (buffer.hasRemaining()) {
char ch = (char) buffer.get();
if(ch != '\n'){
line.append(ch);
} else {
String tmpline = line.toString();
line.delete(0, line.length());
if(tmpline.contains(sessionAndThread)){
list.add(tmpline);
if(tmpline.contains("After request")){
break outer;
}
}
}
}
buffer.clear();
}
buffer.clear();
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
if(finChannel != null){
finChannel.close();
}
if(fis != null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
/**
* 查询文件中所要查询日期的位置
*
* @param file 文件
* @param date 查询日期
* @param bigOrSmall 当bigOrSmall为true时,pos在查询日期之前 | 当bigOrSmall为false时,pos在查询日志过后
* @return
* @throws Exception
*/
private static long getLogPos(File file, String date, boolean bigOrSmall) throws Exception {
String regex = "^\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3}\\]$";
String formatStr = "[yyyy-MM-dd HH:mm:ss.SSS]";
FileInputStream fis = null;
FileChannel fChannel = null;
Pattern pattern = Pattern.compile(regex);
SimpleDateFormat format = new SimpleDateFormat(formatStr);
// 查询的位置 , 通道直接内存映射大小,默认1GB
long searchPos = 0L, size = 1073741824L;
MappedByteBuffer mbb = null;
try {
Date start = format.parse(date);
// 读取的缓存大小,默认1MB
byte[] buffer = null;
fis = new FileInputStream(file);
fChannel = fis.getChannel();
if(fChannel.size() >= 1048576)
buffer = new byte[1048576];
else
buffer = new byte[(int) fChannel.size()];
// 获取日志文件最多需要遍历的次数
int cyclicCount = (int) (fChannel.size() / size + 1);
long highPosOffset = size;
// 遍历文件,确定起始位置属于哪个直接内存映射块
outer: for (int i = 0; i < cyclicCount; i++) {
if (i == cyclicCount - 1) {
mbb = fChannel.map(FileChannel.MapMode.READ_ONLY, i * size,
fChannel.size() - i * size);
highPosOffset = fChannel.size() - i * size;
} else {
mbb = fChannel.map(FileChannel.MapMode.READ_ONLY, i * size,
size);
}
mbb.get(buffer);
StringBuilder line = new StringBuilder();
for (byte b : buffer) {
line.append((char) b);
if (b == '\n') {
if (line.length() > 25
&& pattern.matcher(line.substring(0, 25))
.matches()) {
if (format.parse(line.substring(0, 25)).getTime() > start
.getTime()) {
break outer;
}
searchPos = i * size;
break;
}
line.delete(0, line.length());
}
}
}
//System.out.println("区间:"+searchPos+","+(searchPos + highPosOffset));
return bisearchForFile(pattern, format, mbb, fChannel, start,
searchPos, searchPos + highPosOffset, bigOrSmall);
} catch (Exception e) {
//e.printStackTrace();
throw e;
} finally {
try {
if (fChannel != null)
fChannel.close();
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//return -1;
}
/**
* 折半查找时间起始点
*
* @param pattern
* @param format
* @param mbb
* @param fChannel
* @param startDate
* @param lowPos
* @param highPos
* @return
* @throws ParseException
* @throws IOException
*/
private static long bisearchForFile(Pattern pattern,
SimpleDateFormat format, MappedByteBuffer mbb,
FileChannel fChannel, Date start, long lowPos, long highPos, boolean bigOrSmall)
throws java.text.ParseException, IOException {
if (lowPos <= highPos) {
long midPos = (lowPos + highPos) / 2;
byte[] tmpbuffer = new byte[1024*512];// 512kb
Date midDate = null;
//mbb = fChannel.map(FileChannel.MapMode.READ_ONLY, midPos,
// (highPos - midPos));
//System.out.println("L:"+lowPos+", H:"+highPos);
if ((highPos - midPos)*2 > 1024*1024) {
mbb = fChannel.map(FileChannel.MapMode.READ_ONLY, midPos, highPos - midPos);
mbb.get(tmpbuffer);
} else { // 区间达到1M的精确度
mbb = fChannel.map(FileChannel.MapMode.READ_ONLY, lowPos, highPos - lowPos);
//System.out.println("lowPos:"+lowPos+", highPos:"+highPos+", midPos:"+midPos);
midPos = lowPos;
tmpbuffer = new byte[(int) (highPos - lowPos)];
mbb.get(tmpbuffer);
StringBuilder line = new StringBuilder();
for (byte b : tmpbuffer) {
line.append((char) b);
if (b == '\n') {
if (line.length() > 25
&& pattern.matcher(line.substring(0, 25))
.matches()) {
midDate = format.parse(line.substring(0, 25));
if (start.getTime() <= midDate.getTime()) {
if(bigOrSmall){// 之前
return midPos;
} else {// 之后
return lowPos;
}
}
midPos = lowPos;
}
line.delete(0, line.length());
}
lowPos++;
}
return -1;
}
StringBuilder line = new StringBuilder();
for (byte b : tmpbuffer) {
line.append((char) b);
if (b == '\n') {
if (line.length() > 25
&& pattern.matcher(line.substring(0, 25)).matches()) {
midDate = format.parse(line.substring(0, 25));
break;
}
line.delete(0, line.length());
}
}
if (start.getTime() == midDate.getTime()) {
return midPos;
} else if (start.getTime() > midDate.getTime()) {// 大值半区
return bisearchForFile(pattern, format, mbb, fChannel, start,
midPos + 1, highPos, bigOrSmall);
} else {// 小值半区
return bisearchForFile(pattern, format, mbb, fChannel, start,
lowPos, midPos - 1, bigOrSmall);
}
}
return -1;
}
/**
* 查询某个时间段内某个请求的所有记录,并返回请求的时间信息
*
* @param indexPath
* @param start
* @param end
* @param queryRequest
* @throws Exception
*/
public static List getAllRequest(String indexPath, String start, String end, String queryRequest) throws Exception {
IndexSearcher searcher = null;
IndexReader reader = null;
Analyzer analyzer = new StandardAnalyzer();
List requestTimes = new ArrayList();
try {
reader = DirectoryReader
.open(FSDirectory.open(Paths.get(indexPath)));
searcher = new IndexSearcher(reader);// 检索工具
// 查询配置区
// 多条件查询
BooleanQuery.Builder builder = new BooleanQuery.Builder();
// 时间区间
Query query = new TermRangeQuery("date", new BytesRef(
format1.format(format2.parse(start))), new BytesRef(
format1.format(format2.parse(end))), true, true);
builder.add(query, Occur.MUST);
// 查询关键词
if (queryRequest != null && !queryRequest.equals("")) {
QueryParser parser = new QueryParser("content", analyzer);
Query wordsQuery = parser.parse(queryRequest);
builder.add(wordsQuery, Occur.MUST);
}
TopDocs results = searcher.search(builder.build(), Integer.MAX_VALUE);
ScoreDoc[] hits = results.scoreDocs;
//System.out.println("总命中行数:" + hits.length);
for (int i = 0; i < hits.length; i++) {
Document doc = searcher.doc(hits[i].doc);
requestTimes.add(format2.format(format1.parse(doc.get("date"))));
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return requestTimes;
}
/**
* 日志请求提取功能,并提供关键字查询功能 (显示前{@link #defaultRows}条;若选择保存,则会将结果保存到指定目录)
*
* 选择排序类型包括以下几个类型:
*
* 1.默认查询排序:如果没有选择请求时间长短排序或者请求次数排序,则默认按时间排序
*
* 2.请求时间长短排序:如果选择请求时间长短排序,则按照时间的长短进行排序
*
* 3.请求次数排序:如果选择请求次数排序,则按照请求次数进行排序
*
* @param indexPath
* 索引目录(必选)
* @param start
* 开始时间(必选)
* @param end
* 结束时间(必选)
* @param queryString
* 查询字符串(可选择)
* @param session
* 会话(可选择)
* @param thread
* 线程(可选择)
* @param sortType
* 选择排序类型
* @param reverse
* false 升序 | true 降序
* @param save
* 是否保存
* @param savePath
* 保存路径
* @return
* @throws Exception
*/
public static List getRequestLogSegment(String indexPath,
String start, String end, String queryString, String session, String thread,
SortType sortType, boolean reverse, boolean save, String savePath) throws Exception {
List resultList = new ArrayList();
IndexSearcher searcher = null;
IndexReader reader = null;
Analyzer analyzer = new StandardAnalyzer();
try {
reader = DirectoryReader
.open(FSDirectory.open(Paths.get(indexPath)));
searcher = new IndexSearcher(reader);// 检索工具
// 查询配置区
// 多条件查询
BooleanQuery.Builder builder = new BooleanQuery.Builder();
// 时间区间
Query query = new TermRangeQuery("date", new BytesRef(
format1.format(format2.parse(start))), new BytesRef(
format1.format(format2.parse(end))), true, true);
builder.add(query, Occur.MUST);
// 查询关键词
if (queryString != null && !queryString.equals("")) {
QueryParser parser = new QueryParser("content", analyzer);
Query wordsQuery = parser.parse(queryString);
builder.add(wordsQuery, Occur.MUST);
}
if (thread != null && !thread.equals("")){
Term term = new Term("thread", thread);
Query query2 = new TermQuery(term);
builder.add(query2, Occur.MUST);
}
if (session != null && !session.equals("")){
Term term = new Term("session", session);
Query query2 = new TermQuery(term);
builder.add(query2, Occur.MUST);
}
// 排序, lucene5中对排序的字段,必须是使用NumericDocValuesField字段
if (sortType == SortType.Default) {
resultList = defaultRequestLogSegment(searcher, builder, reverse, save,
savePath);
} else if (sortType == SortType.RequestTime) {
resultList = requestTimeLogSegment(searcher, builder, !reverse, save,
savePath);
} else if (sortType == SortType.RequestFrequency) {
resultList = requestFrequencyLogSegment(searcher, builder, !reverse, save,
savePath);
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return resultList;
}
/**
* 默认的查询,默认只显示排名前{@link #defaultRows}的结果
*
* @param searcher
* @param builder
* @param save
* @param savePath
* @return
* @throws Exception
*/
private static List defaultRequestLogSegment(
IndexSearcher searcher, Builder builder, boolean reverse,
boolean save, String savePath) throws Exception {
Sort sort = new Sort(
new SortField("date", SortField.Type.LONG, reverse));
int searchNum = save ? Integer.MAX_VALUE : defaultRows; // 查询的个数
TopDocs results = searcher.search(builder.build(), searchNum, sort);
ScoreDoc[] hits = results.scoreDocs;
//System.out.println("总命中行数:" + hits.length);
if (save) {// 保存结果到文件中
//FileWriter fwrite = new FileWriter(savePath, false);
List data = new ArrayList();
for (int i = 0; i < hits.length; i++) {
Document doc = searcher.doc(hits[i].doc);
// {日志级别}%{seesion:线程}%{日志名称}%{url}
data.add(new String[]{
"[" + format2.format(format1.parse(doc.get("date"))) + "]",
doc.get("content").split(" ")[0],
doc.get("content").split(" ")[1],
doc.get("content").split(" ")[2],
doc.get("content").split(" ")[3]});
}
ExcelExport.exportInfo(data, new String[]{"时间", "日志级别", "session:线程", "日志名称", "URL"}, new File(savePath));
return null;
} else {// 显示结果到界面
List list = new ArrayList();
int size = hits.length >= defaultRows ? defaultRows : hits.length;
for (int i = 0; i < size; i++) {
Document doc = searcher.doc(hits[i].doc);
list.add("[" + format2.format(format1.parse(doc.get("date"))).replace(" ", "_") + "] "
+ doc.get("content") + "\n");
}
return list;
}
}
/**
* 按请求时间长短进行查询排序,默认只显示排名前{@link #defaultRows}的结果
*
* @param searcher
* @param builder
* @param reverse
* @param save
* false 升序 | true 降序
* @param savePath
* @return
* @throws Exception
*/
private static List requestTimeLogSegment(IndexSearcher searcher,
Builder builder, boolean reverse, boolean save, String savePath)
throws Exception {
Sort sort = new Sort(new SortField("interval", SortField.Type.FLOAT,
reverse));
int searchNum = save ? Integer.MAX_VALUE : defaultRows; // 查询的个数
TopDocs results = searcher.search(builder.build(), searchNum, sort);
ScoreDoc[] hits = results.scoreDocs;
//System.out.println("总命中行数:" + hits.length);
if (save) {// 保存到文件
List data = new ArrayList();
for (int i = 0; i < hits.length; i++) {
Document doc = searcher.doc(hits[i].doc);
data.add(new String[]{ "[" + format2.format(format1.parse(doc.get("date"))) + "]",
doc.get("content").split(" ")[1],
doc.get("content").split(" ")[2],
doc.get("content").split(" ")[3],
doc.get("interval")});
}
ExcelExport.exportInfo(data, new String[]{"时间", "session:线程", "日志名称", "URL", "执行时间(毫秒)"}, new File(savePath));
return null;
} else {// 显示结果到界面
List list = new ArrayList();
int size = hits.length >= defaultRows ? defaultRows : hits.length;
for (int i = 0; i < size; i++) {
Document doc = searcher.doc(hits[i].doc);
// {日志级别}%{seesion:线程}%{日志名称}%{url}%{执行时间}
list.add(format2.format(format1.parse(doc.get("date"))).replace(" ", "_") + " "
+ doc.get("content").split(" ")[1] + " "
+ doc.get("content").split(" ")[2] + " "
+ doc.get("content").split(" ")[3] + " "
+ doc.get("interval") + "\n");
}
return list;
}
}
/**
* 按请求次数进行查询排序,默认只显示排名前{@link #defaultRows}的结果
*
* @param searcher
* @param builder
* @param reverse
* @param save
* false 升序 | true 降序
* @return
* @throws Exception
*/
private static List requestFrequencyLogSegment(
IndexSearcher searcher, Builder builder, boolean reverse,
boolean save, String savePath) throws Exception {
Sort sort = new Sort(new SortField("date", SortField.Type.LONG));
TopDocs results = searcher.search(builder.build(), Integer.MAX_VALUE,
sort);
ScoreDoc[] hits = results.scoreDocs;
//System.out.println("总命中行数:" + hits.length);
HashMap requestMap = new HashMap();
// 获取
for (ScoreDoc scoreDoc : hits) {
Document doc = searcher.doc(scoreDoc.doc);
String url = doc.get("content").split(" ")[3];
if (requestMap.containsKey(url)) {
requestMap.put(url, new Searcher().new RequestModel(requestMap.get(url).times + 1,
requestMap.get(url).sumTime + Float.parseFloat(doc.get("interval"))));
} else {
requestMap.put(url, new Searcher().new RequestModel(1, Float.parseFloat(doc.get("interval"))));
}
}
// 排序
List> retList = new ArrayList>();
retList.addAll(requestMap.entrySet());
MapValueComparator mvComparator = new Searcher().new MapValueComparator(reverse);
// sort方法使用的是归并排序(合并排序),是稳定排序中最快的一种
Collections.sort(retList, mvComparator);
if (save) {// 保存到文件
List data = new ArrayList();
for (Entry entry : retList) {
data.add(new String[]{entry.getKey(), String.valueOf(entry.getValue().times),
String.valueOf((int)(entry.getValue().sumTime/entry.getValue().times))});
}
ExcelExport.exportInfo(data, new String[]{"URL", "访问次数", "平均执行时间(毫秒)"}, new File(savePath));
return null;
} else {// 显示结果到界面
List list = new ArrayList();
int showCount = 0;
for (Entry entry : retList) {
showCount++;
list.add(entry.getKey() + " " + entry.getValue().times + " " + (int)(entry.getValue().sumTime/entry.getValue().times));
if (showCount >= defaultRows) {
break;
}
}
return list;
}
}
/**
* 对Map类型元素的值进行比较
*
* @author wuxinhui
*
*/
class MapValueComparator implements Comparator> {
public MapValueComparator() {
}
public MapValueComparator(boolean reverse) {
this.reverse = reverse;
}
boolean reverse = false;// false 升序|true 降序
public void setReverse(boolean reverse) {
this.reverse = reverse;
}
@Override
public int compare(Map.Entry map1,
Map.Entry map2) {
if (reverse) {
return map2.getValue().times - map1.getValue().times;
} else {
return map1.getValue().times - map2.getValue().times;
}
}
}
/**
* 希尔排序(缩小增量排序):对于数据库不大的排序建议使用,希尔排序是对直接插入排序的优化。
*
* @param data
*/
@SuppressWarnings("unused")
private static int[] shellSort(int data[]) {
int j = 0, temp = 0;
for (int increment = data.length / 2; increment > 0; increment = increment / 2) {
//System.out.println("increment:" + increment);
for (int i = increment; i < data.length; i++) {
temp = data[i];
for (j = i - increment; j >= 0; j = j - increment) {
if (temp < data[j]) {
data[j + increment] = data[j];
} else {
break;
}
}
data[j + increment] = temp;
}
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
}
return data;
}
class RequestModel{
int times;// 访问的次数
float sumTime;// 访问的总时间,以便计算平均时间
public RequestModel(int times, float sumTime) {
super();
this.times = times;
this.sumTime = sumTime;
}
public int getTimes() {
return times;
}
public float getSumTime() {
return sumTime;
}
}
}
https://www.cnblogs.com/gaotianle/p/3325451.html
http://ifeve.com
https://blog.csdn.net/jeffleo/article/details/54695959