字符串分段对比的多线程实现

闲来无事,写了一个字符串分段对比的方法:两个字符串,将字符串1分为多个段,逐段与字符串2整体进行比较,统计比较的结果,并计算字符串1所有匹配上字符串2的段占字符串1所有分段总数的比例,即此方法中的相似百分比。

该方法仅为一个初步想法,其结果可以作为一个参考,但也可能没有实际意义,只当是对多线程的一次学习与实践。另外感受一下对代码的性能优化:

  • 其中方法【utils.StringUtils.compareStringThread(String, String, int, int, boolean)】相较方法【utils.StringUtils.compareStringThread_old(String, String, int, int, boolean)】做了一次优化(前者的方法注释上标明了优化方案及方案间针对时间及空间的比较),降低了最大内存占用量和方法执行时间);
  • 读取txt文件内容的方法【utils.FileUtils.getContentFromTextFile(File, String, boolean)】,最开始用的是【RandomAccessFile】,但发现读大文件时实在是太慢了(读一个50MB的文件花了300多秒),网上一查,发现原因在于【RandomAccessFile】是读一个字节做一次转码,而【BufferedReader】这种是读一个缓冲区,再对缓冲区中的内容整个进行转码。

接下来贴出比较方法的代码(utils.StringUtils),附带一个文件操作的共通类(utils.FileUtils),包含文件的读取与文件路径的获取等。

package utils;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class StringUtils {
	public static Integer compareStr = 0;// 字符串1匹配上字符串2的段数
	
	/**
     * 字符串比较(多线程处理,按多个分段进行比较)
     * @deprecated 大数据量下效率较低,内存空间占用太大,以方法{@link utils.StringUtils#compareStringThread(String, String, int, int, boolean)}取代之
     * @param str1 字符串1
     * @param str2 字符串2
     * @param paragSize 比较的分段长度(应大于等于1,若小于0则默认字符串1的长度(后期会变),其他特殊选项:0,字符串1的长度;)
     * @param threadCount 线程数(若参数小于1,则默认为1个线程)
     * @param isShowLog 是否展示日志
     * @return 比较结果:
     * 	str1Length:字符串1总长度;
	 * 	str2Length:字符串2总长度;
	 * 	str1SumParag:字符串1总段数;
	 * 	str2SumParag:字符串2总段数;
	 * 	strComParag:字符串1匹配上字符串2的段数;
	 * 	str1ComParagPropor:字符串1匹配上字符串2的段数占字符串1总段数的比例;
	 * 	str2ComParagPropor:字符串1匹配上字符串2的段数占字符串2总段数的比例(该项仅供参考,不具有说明性:若该比例大于100%,则说明出现字符串1中多段重复匹配字符串2的情况,此时可考虑增加段长度);
     */
    public static Map<String,Double> compareStringThread_old(String str1, String str2, int paragSize, int threadCount, boolean isShowLog){
    	// 参数处理
    	// 比较的分段长度
    	if (paragSize <= 0) {
    		paragSize = str1.length();
    		threadCount = 1;
    	}
    	// 线程数
    	if (threadCount < 1) {
    		threadCount = 1; 
    	}
    	
    	// 执行开始时间
    	long startTime = 0l;
    	if (isShowLog) {
    		startTime = (new Date()).getTime();
    		System.out.println("------------ 开始执行比对……");
    		System.out.println("分段长度 = "+paragSize);
    		System.out.println("字符串1总长度 = "+str1.length());
	    	System.out.println("字符串2总长度 = "+str2.length());
    	}
    	
    	// 返回结果
    	Map<String,Double> result = new HashMap<String,Double>();
    	
    	// 取得所有段
    	List<String> str1Parags = new ArrayList<String>();// 字符串1的所有段
		for (int i=0; i<str1.length()-paragSize+1; i++) {
			str1Parags.add(str1.substring(i, i+paragSize));
		}
    	
    	// 比对两个字符串,取其中某一段编码进行对比,得出总段数与匹配上的段数以及匹配上的比例
    	int pageSize = str1Parags.size()/threadCount;// 单个线程需要比对的集合数
    	int fromIndex = 0;
    	int toIndex = pageSize;
    	List<Thread> works = new ArrayList<Thread>();
    	if (isShowLog) {
	    	System.out.println("单个线程比对的段数 = "+pageSize);
	    	System.out.println("比对字符串的长度 = "+str2.length());
	    	System.out.println("------------ 线程共【"+threadCount+"】开始比对……");
    	}
    	// 多线程处理
    	for (int i=1; i<=threadCount; i++) {
    		List<String> stringList = str1Parags.subList(fromIndex, toIndex);
    		Thread thread = new Thread(new Runnable(){
				@Override
				public void run() {
					int compareTimes = 0;
			    	for (String str1Parag : stringList) {
			    		if (str2.indexOf(str1Parag) >= 0) {
			    			compareTimes++;
			    		}
			    	}
			    	if (isShowLog) {
		    			System.out.println("------ 单个比对结束:本次比对总数count="+stringList.size()+";本次比对成功count="+compareTimes);
		    		}
			    	
			    	// 为单个线程锁定compareStr对象,以对compareStr同步访问
			    	synchronized (compareStr) {
			    		compareStr += compareTimes;
			        }
				}
    		},"Thread"+i);
    		thread.start();
    		works.add(thread);
    		fromIndex += pageSize;
        	toIndex += pageSize;
    		// 最后一个线程应被分配剩余所有分段
    		if (i == (threadCount - 1)) {
    			toIndex = str1Parags.size();
    		} 
    	}
    	// 等待线程结束
    	for (Thread thread : works) {
    		try {
    			thread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    	
    	// 设置结果
    	result.put("str1Length", Double.parseDouble(String.valueOf(str1.length())));// 字符串1总长度
    	result.put("str2Length", Double.parseDouble(String.valueOf(str2.length())));// 字符串2总长度
    	result.put("str1SumParag", Double.parseDouble(String.valueOf(str1Parags.size())));// 字符串1总段数
    	result.put("str2SumParag", Double.parseDouble(String.valueOf(paragSize > str2.length() ? 1 : str2.length()-paragSize+1)));// 字符串2总段数
    	result.put("strComParag", Double.parseDouble(String.valueOf(compareStr)));// 字符串1匹配上字符串2的段数
    	result.put("str1ComParagPropor", result.get("strComParag")/result.get("str1SumParag"));// 字符串1匹配上字符串2的段数占字符串1总段数的比例
    	result.put("str2ComParagPropor", result.get("strComParag")/result.get("str2SumParag"));// 字符串1匹配上字符串2的段数占字符串2总段数的比例
    	
    	if (isShowLog) {
    		System.out.println("字符串1匹配上字符串2的段数 = "+result.get("strComParag"));
	    	System.out.println("字符串1总段数 = "+result.get("str1SumParag"));
	    	System.out.println("字符串2总段数 = "+result.get("str2SumParag"));
	    	System.out.println("字符串1匹配上字符串2的段数占字符串1总段数的比例 = "+String.format("%.6f", result.get("str1ComParagPropor")*100) + "%");
	    	System.out.println("字符串1匹配上字符串2的段数占字符串2总段数的比例(仅供参考) = "+String.format("%.6f", result.get("str2ComParagPropor")*100) + "%");
	    	// 特殊关系
	    	String relationship = "无";
	    	if (result.get("str1ComParagPropor") == 1 && result.get("str2ComParagPropor") == 1) {
	    		relationship = "完全相同";
	    	} else if (result.get("str2ComParagPropor") > 1) {
	    		relationship = "前者中多段重复匹配后者";
	    	} else if (result.get("str1ComParagPropor") == 1 && str1.length() == paragSize) {
	    		relationship = "前者是后者中的一段";
	    	} else if (result.get("str1ComParagPropor") == 1 && str1.length() > paragSize) {
	    		relationship = "前者(可能)是后者中的一段";
	    	}
	    	System.out.println("字符串1与字符串2的特殊关系:【"+relationship+"】");
	    	
	    	
	    	// 执行结束时间
	    	long endTime = (new Date()).getTime();
	    	System.out.println("------------ 方法执行消耗的时间:"+ ((endTime-startTime) > 1000 ? ((endTime-startTime)/1000 + "s") : ((endTime-startTime) + "ms ------------")));
    	}
    	return result;
    }
    
    /**
     * 字符串比较(多线程处理,按多个分段进行比较)
     * <p>大数据量下方案(此方法为方案2实现):</p>
     * 	1、先分隔字符串,在每一个线程处理对应字符串;
     * 	<li>空间:①原字符串;②分割后的字符串(大小大于原字符串,原字符串长时相当);</br>
     * 		①分割后的字符串(大小大于原字符串,原字符串长时相当);②每个线程分配的段;</li>
     * 	<li>时间相比方案2多出的:①几十次字符串截取;②针对原字符串的析构时间;</li>
     * 	<li>空间相比方案2多出的:①分隔后的字符串的重叠部分;</li>
     * 	2、不分隔字符串,直接在线程中取对应的段(多线程同时读取某一资源时不存在死锁问题);
     * 	<li>空间:①原字符串;②每个线程分配的段;</li></br>
     * @param str1 字符串1
     * @param str2 字符串2
     * @param paragSize 比较的分段长度(应大于等于1,若小于0则默认字符串1的长度(后期会变),其他特殊选项:0,字符串1的长度;)
     * @param threadCount 线程数(若参数小于1,则默认为1个线程)
     * @param isShowLog 是否展示日志
     * @return 比较结果:
     * 	str1Length:字符串1总长度;
	 * 	str2Length:字符串2总长度;
	 * 	str1SumParag:字符串1总段数;
	 * 	str2SumParag:字符串2总段数;
	 * 	strComParag:字符串1匹配上字符串2的段数;
	 * 	str1ComParagPropor:字符串1匹配上字符串2的段数占字符串1总段数的比例;
	 * 	str2ComParagPropor:字符串1匹配上字符串2的段数占字符串2总段数的比例(该项仅供参考,不具有说明性:若该比例大于100%,则说明出现字符串1中多段重复匹配字符串2的情况,此时可考虑增加段长度);
     */
    public static Map<String,Double> compareStringThread(String str1, String str2, int paragSize, int threadCount, boolean isShowLog){
    	// 参数处理
    	// 比较的分段长度
    	if (paragSize <= 0) {
    		paragSize = str1.length();
    		threadCount = 1;
    	}
    	// 线程数
    	if (threadCount < 1) {
    		threadCount = 1; 
    	}
    	
    	// 执行开始时间
    	long startTime = 0l;
    	if (isShowLog) {
    		startTime = (new Date()).getTime();
    		System.out.println("------------ 开始执行比对……");
    		System.out.println("分段长度 = "+paragSize);
    		System.out.println("字符串1总长度 = "+str1.length());
	    	System.out.println("字符串2总长度 = "+str2.length());
    	}
    	
    	// 返回结果
    	Map<String,Double> result = new HashMap<String,Double>();
    	
    	// 比对两个字符串,取其中某一段编码进行对比,得出总段数与匹配上的段数以及匹配上的比例
    	int paragsCount;// 字符串1的总分段数
    	if (paragSize > str1.length()) {
    		paragsCount = 1;
    		threadCount = 1;
    	} else {
    		paragsCount = str1.length() - paragSize + 1;
    	}
    	int pageSize = (int)Math.ceil((double)paragsCount/(double)threadCount);// 每个线程应比对段数(上取整,最后一个线程所分配段数<=pageSize,开的总线程数<=threadCount)
    	List<Thread> works = new ArrayList<Thread>();
    	if (isShowLog) {
	    	System.out.println("单个线程比对的段数 = "+pageSize);
	    	System.out.println("比对字符串的长度 = "+str2.length());
	    	System.out.println("------------ 线程共【"+threadCount+"】开始比对……");
    	}
    	
    	// 多线程处理
    	for (int i=1; i<=threadCount; i++) {
    		// 解决java内部类访问局部变量时局部变量必须声明为final的问题
    		final int fromIndex = (i - 1) * pageSize;
    		// 开的总线程数<=threadCount
    		if (fromIndex > paragsCount) {
    			break;
    		}
    		final int paragSizeParam = paragSize;
    		Thread thread = new Thread(new Runnable(){
    			// 分配给该线程的字符串1的段
    			private List<String> stringList = new ArrayList<String>();
				@Override
				public void run() {
					// 分配给该线程对应段
			    	for (int j=fromIndex; j<fromIndex+pageSize; j++) {
			    		// 最后一个线程可能不足该pageSize个段
			    		if (j+paragSizeParam > str1.length()) {
			    			break;
			    		}
			    		this.stringList.add(str1.substring(j, j+paragSizeParam));
			    	}
					
					int compareTimes = 0;
			    	for (String str1Parag : this.stringList) {
			    		if (str2.indexOf(str1Parag) >= 0) {
			    			compareTimes++;
			    		}
			    	}
			    	if (isShowLog) {
		    			System.out.println("------ 单个比对结束:本次比对总数count="+stringList.size()+";本次比对成功count="+compareTimes);
		    		}
			    	// 为单个线程锁定compareStr对象,以对compareStr同步访问
			    	synchronized (compareStr) {
			    		compareStr += compareTimes;
			        }
				}
    		},"Thread"+i);
    		thread.start();
    		works.add(thread);
    	}
    	// 等待线程结束
    	for (Thread thread : works) {
    		try {
    			thread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    	
    	// 设置结果
    	result.put("str1Length", Double.parseDouble(String.valueOf(str1.length())));// 字符串1总长度
    	result.put("str2Length", Double.parseDouble(String.valueOf(str2.length())));// 字符串2总长度
    	result.put("str1SumParag", Double.parseDouble(String.valueOf(paragsCount)));// 字符串1总段数
    	result.put("str2SumParag", Double.parseDouble(String.valueOf(paragSize > str2.length() ? 1 : str2.length()-paragSize+1)));// 字符串2总段数
    	result.put("strComParag", Double.parseDouble(String.valueOf(compareStr)));// 字符串1匹配上字符串2的段数
    	result.put("str1ComParagPropor", result.get("strComParag")/result.get("str1SumParag"));// 字符串1匹配上字符串2的段数占字符串1总段数的比例
    	result.put("str2ComParagPropor", result.get("strComParag")/result.get("str2SumParag"));// 字符串1匹配上字符串2的段数占字符串2总段数的比例
    	
    	if (isShowLog) {
    		System.out.println("字符串1匹配上字符串2的段数 = "+result.get("strComParag"));
	    	System.out.println("字符串1总段数 = "+result.get("str1SumParag"));
	    	System.out.println("字符串2总段数 = "+result.get("str2SumParag"));
	    	System.out.println("字符串1匹配上字符串2的段数占字符串1总段数的比例 = "+String.format("%.6f", result.get("str1ComParagPropor")*100) + "%");
	    	System.out.println("字符串1匹配上字符串2的段数占字符串2总段数的比例(仅供参考) = "+String.format("%.6f", result.get("str2ComParagPropor")*100) + "%");
	    	// 特殊关系
	    	String relationship = "无";
	    	if (result.get("str1ComParagPropor") == 1 && result.get("str2ComParagPropor") == 1) {
	    		relationship = "完全相同";
	    	} else if (result.get("str2ComParagPropor") > 1) {
	    		relationship = "前者中多段重复匹配后者";
	    	} else if (result.get("str1ComParagPropor") == 1 && str1.length() == paragSize) {
	    		relationship = "前者是后者中的一段";
	    	} else if (result.get("str1ComParagPropor") == 1 && str1.length() > paragSize) {
	    		relationship = "前者(可能)是后者中的一段";
	    	}
	    	System.out.println("字符串1与字符串2的特殊关系:【"+relationship+"】");
	    	
	    	
	    	// 执行结束时间
	    	long endTime = (new Date()).getTime();
	    	System.out.println("------------ 方法执行消耗的时间:"+ ((endTime-startTime) > 1000 ? ((endTime-startTime)/1000 + "s") : ((endTime-startTime) + "ms ------------")));
    	}
    	return result;
    }
    
    /**
     * 字符串比较(多线程处理,按多个分段进行比较,不输出日志)
     * @param str1 字符串1
     * @param str2 字符串2
     * @param paragSize 比较的分段长度
     * @param threadCount 线程数
     * @return 比较结果,参见方法{@link utils.StringUtils#compareStringThread(String, String, int, int, boolean)}
     */
    public static Map<String,Double> compareStringThread(String str1, String str2, int paragSize, int threadCount){
    	return compareStringThread(str1, str2, paragSize, threadCount, false);
    }
    
    /**
     * 字符串比较(多线程处理,按多个分段进行比较,不输出日志, 默认20个线程)
     * @param str1 字符串1
     * @param str2 字符串2
     * @param paragSize 比较的分段长度
     * @return 比较结果,参见方法{@link utils.StringUtils#compareStringThread(String, String, int, int, boolean)}
     */
    public static Map<String,Double> compareStringThread(String str1, String str2, int paragSize){
    	return compareStringThread(str1, str2, paragSize, 20, false);
    }
    
    /**
     * 对两个文本文件中的内容(字符串)比较(多线程处理,按多个分段进行比较)
     * @param textFilePath1 文本文件1路径
     * @param textFilePath2 文本文件2路径
     * @param paragSize 比较的分段长度(应大于等于1,其他特殊选项:0,文本文件1的长度;)
     * @param threadCount 线程数(若参数为0,则默认为1个线程)
     * @param isShowLog 是否展示日志
     * @return 比较结果:
     * 	file1Length:文本文件1总长度;
	 * 	file2Length:文本文件2总长度;
	 * 	file1SumParag:文本文件1总段数;
	 * 	file2SumParag:文本文件2总段数;
	 * 	fileComParag:文本文件1匹配上文本文件2的段数;
	 * 	file1ComParagPropor:文本文件1匹配上文本文件2的段数占文本文件1总段数的比例;
	 * 	file2ComParagPropor:文本文件1匹配上文本文件2的段数占文本文件2总段数的比例(该项仅供参考,不具有说明性:若该比例大于100%,则说明出现字符串1中多段重复匹配字符串2的情况,此时可考虑增加段长度);
     */
    public static Map<String,Double> compareTextFileThread(String textFilePath1, String textFilePath2, int paragSize, int threadCount, boolean isShowLog){
    	String charsetName = "GBK";
    	if (textFilePath1 == null || textFilePath2 == null) {
    		if (isShowLog) System.out.println("请确保文件存在!"); 
    		return null;
    	}
		String str1 = FileUtils.getContentFromTextFile(new File(textFilePath1), charsetName, isShowLog);
		String str2 = FileUtils.getContentFromTextFile(new File(textFilePath2), charsetName, isShowLog);
    	
    	return compareStringThread(str1, str2, paragSize, threadCount, isShowLog);
    }
    
    public static void main(String[] args) {
    	String str1 = FileUtils.getFilePath("test/textFiles/file4.txt");
    	String str2 = FileUtils.getFilePath("test/textFiles/file4.txt");
    	
    	compareTextFileThread(str1,str2,5,20,true);
    }
}

文件操作的共通类:

package utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.util.Date;

public class FileUtils {
	public static final String PROJECT_NAME = System.getProperty("user.dir").substring(System.getProperty("user.dir").lastIndexOf(File.separator)+1, System.getProperty("user.dir").length());
	
	/**
	 * 根据uri取得文件路径
	 * @param uri
	 * @return 文件绝对路径(异常则返回null)
	 */
	public static String getPathFromURI(URI uri) {
		try {
			File file = new File(new URI(uri.toString()));
			return file.getAbsolutePath();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}  
	}
	
	/**
	 * 根据uri取得文件路径
	 * @param uriStr
	 * @return 文件绝对路径(异常则返回null)
	 */
	public static String getPathFromURI(String uriStr) {
		try {
			File file = new File(new URI(uriStr));
			return file.getAbsolutePath();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}  
	}
	
	/**
	 * 根据url取得文件路径
	 * @param url
	 * @return 文件绝对路径(异常则返回null)
	 */
	public static String getPathFromURL(URL url) {
		try {
			File file = new File(url.toURI());
			return file.getAbsolutePath();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}  
	}
	
	/**
	 * 根据url取得文件路径
	 * @param urlStr
	 * @return 文件绝对路径(异常则返回null)
	 */
	public static String getPathFromURL(String urlStr) {
		try {
			File file = new File((new URL(urlStr)).toURI());
			return file.getAbsolutePath();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}  
	}
    
	/**
	 * 根据相对路径取得文件
	 * @param relativePath 相对项目目录的路径
	 * @return 对应文件(文件不存在则返回null)
	 */
    public static File getFile(String relativePath){
    	if (relativePath == null) {
    		return null;
    	}
    	if (".".equals(relativePath)) {
    		relativePath = "";
    	}
    	// 相对路径开头没有路径分隔符
    	if (relativePath.indexOf("/") != 0 && relativePath.indexOf("\\") != 0) {
    		relativePath = File.separator + relativePath;
    	}
    	
    	File file = new File(System.getProperty("user.dir") + relativePath);
        return file.exists() ? file : null;
    }
    
    /**
	 * 根据相对路径取得文件绝对路径
	 * @param relativePath 相对项目目录的路径
	 * @return 对应文件绝对路径
	 */
    public static String getFilePath(String relativePath){
    	File file = getFile(relativePath);
    	return file != null ? file.getAbsolutePath() : null;
    }
    
    /**
     * 读取文本文件内容(默认不展示日志,默认UTF-8)
     * @param file 文本文件
     * @return 文本文件内容
     */
    public static String getContentFromTextFile(File file){
    	return getContentFromTextFile(file,"UTF-8",false);
    }
    
    /**
     * 读取文本文件内容(默认不展示日志)
     * @param file 文本文件
     * @param charsetName 字符编码
     * @return 文本文件内容
     */
    public static String getContentFromTextFile(File file, String charsetName){
    	return getContentFromTextFile(file,charsetName,false);
    }
    
    /**
     * 读取文本文件内容
     * @param file 文本文件
     * @param isShowLog 是否展示日志
     * @param charsetName 字符编码
     * @return 文本文件内容
     */
    public static String getContentFromTextFile(File file, String charsetName, boolean isShowLog){
    	long startTime = 0l;
		if (isShowLog) {
			startTime = (new Date()).getTime();
			System.out.println("------------ 读取文件开始:文件【"+file.getPath()+"】  ------------");
		}
		
		StringBuffer result = new StringBuffer();
		BufferedReader reader = null;
		InputStreamReader isr = null;
		try {
			isr = new InputStreamReader(new FileInputStream(file), charsetName);
			reader = new BufferedReader(isr);
			String tempString = null;
			// 一次读一行,读入null时文件结束
			tempString = reader.readLine();
			while (tempString != null) {
				result.append(tempString);
				tempString = reader.readLine();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (isr != null) {
				try {
					isr.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
		
		if (isShowLog) {
			long endTime = (new Date()).getTime();
			System.out.println("------------ 读取文件结束,共耗时:"+((endTime-startTime) > 1000 ? ((endTime-startTime)/1000 + "s") : ((endTime-startTime) + "ms")) + " ------------");
		}
    	return result.toString();
    }
    
    public static void main(String[] args) {
    	String str = getContentFromTextFile(getFile("test\\textFiles\\file5.txt"),"GBK",true);
    	System.out.println(str.length());
    	System.out.println(str);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值