目录
需求
现在有5个文件,文件里面分别存储着1千万个用户年龄,并且每个文件中的年龄都是有序的(从小到大),现在需要将这5个文件整合到一个文件中,新文件的内容依然要保持有序(从小到大)。
初始化数据
1.数据生成1千万数据(无序)。
2.将无序的数据进行排序。
3.将排好序的数据写入到文件中。
全局变量类
package com.ymy.file;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 全局变量
*/
public class GlobalVariable {
/**
* 多线程数量
*/
public static int threadNum = 10;
/**
* 线程池
*/
public static Executor executor = Executors.newFixedThreadPool(threadNum);
/**
* 数组总长度
*/
public static int arrLenth = 1000;
/**
* 随机数范围
*/
public static int DATA_FIELD = 100;
/**
* 每个线程的数组长度
*/
public static int threadLength = arrLenth/threadNum;
/**
* 线程同步
*/
public static CountDownLatch count = new CountDownLatch(10);
/**
* 线程安全
*/
public static AtomicInteger atomic = new AtomicInteger(0);
public static int arr[] = new int[arrLenth];
}
生成随机数方法:
private static Random r = new Random();
/**
* 一次性生成1亿数据
* @return
*/
public static int[] getRandomNum1() {
int num = GlobalVariable.arrLenth;
int arr[] = new int[num];
int i = 0;
while ( i < num){
arr[i] = r.nextInt(GlobalVariable.DATA_FIELD) + 1;
i++;
}
return arr;
}
计数排序方法
/**
* 计数排序排序
*
* @param ages 需要排序的数组
*/
public static int[] sort(int[] ages) {
// long startTime = System.currentTimeMillis();
int length = ages.length;
if (ages == null || length <= 1) {
return ages;
}
int maxAge = ages[0];
for (int i = 1; i < length; ++i) {
if (maxAge < ages[i]) {
maxAge = ages[i];
}
}
System.out.println("");
System.out.println("最大年龄:"+maxAge);
int[] countArr = new int[maxAge + 1];
for (int i = 0; i <= maxAge; i++) {
countArr[i] = 0;
}
for (int i = 0; i < length; ++i){
countArr[ages[i]]++;
}
// for (int i = 0; i <= maxAge; i++) {
// System.out.print(countArr[i] + " ");
// }
// System.out.println("");
// 依次累加
for (int i = 1; i <= maxAge; ++i) {
countArr[i] = countArr[i - 1] + countArr[i];
}
int[] tmpArr = new int[length];
for (int i = length-1 ; i >= 0 ; --i) {
int index = countArr[ages[i]]-1;
tmpArr[index] = ages[i];
countArr[ages[i]]--;
}
for (int i = 0; i < length; ++i) {
ages[i] = tmpArr[i];
}
// long endTime = System.currentTimeMillis();
// System.out.println("排序已完成,耗时:"+(endTime-startTime)+" ms");
return ages;
}
文件管理
package com.ymy.file;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 文件管理
*/
public class FileManager {
private static FileReader reader1;
private static FileReader reader2;
private static FileReader reader3;
private static FileReader reader4;
private static FileReader reader5;
public static StringBuffer buff= new StringBuffer();
public static List<BufferedReader> readList = new ArrayList<BufferedReader>();
public static List<FileWriter> writeList = new ArrayList<FileWriter>();
public static File file1 = new File("C:\\Users\\Administrator\\Desktop\\data1.txt");
public static File file2 = new File("C:\\Users\\Administrator\\Desktop\\data2.txt");
public static File file3 = new File("C:\\Users\\Administrator\\Desktop\\data3.txt");
public static File file4 = new File("C:\\Users\\Administrator\\Desktop\\data4.txt");
public static File file5 = new File("C:\\Users\\Administrator\\Desktop\\data5.txt");
public static File file = new File("C:\\Users\\Administrator\\Desktop\\finaldata.txt");
private static FileWriter fos1;
private static FileWriter fos2;
private static FileWriter fos3;
private static FileWriter fos4;
private static FileWriter fos5;
//需要写入的文件
public static FileWriter fos;
static {
try {
fos1 = new FileWriter(file1, true);
fos2 = new FileWriter(file2, true);
fos3 = new FileWriter(file3, true);
fos4 = new FileWriter(file4, true);
fos5 = new FileWriter(file5, true);
fos = new FileWriter(file, true);
writeList.add(fos1);
writeList.add(fos2);
writeList.add(fos3);
writeList.add(fos4);
writeList.add(fos5);
} catch (IOException e) {
e.printStackTrace();
}
try {
reader1 = new FileReader(file1);
reader2 = new FileReader(file2);
reader3 = new FileReader(file3);
reader4 = new FileReader(file4);
reader5 = new FileReader(file5);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private static BufferedReader br1;
private static BufferedReader br2;
private static BufferedReader br3;
private static BufferedReader br4;
private static BufferedReader br5;
static {
try {
br1 = new BufferedReader(new FileReader(file1));
br2 = new BufferedReader(new FileReader(file2));
br3 = new BufferedReader(new FileReader(file3));
br4 = new BufferedReader(new FileReader(file4));
br5 = new BufferedReader(new FileReader(file5));
readList.add(br1);
readList.add(br2);
readList.add(br3);
readList.add(br4);
readList.add(br5);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读数据
* @param reader
* @return
* @throws IOException
*/
public static Integer readData(BufferedReader reader) throws IOException {
String s = reader.readLine();
return null == s ? null : Integer.valueOf(s);
}
/**
* 输入数据到文件
*
* @param arr
* @throws IOException
*/
public static void write(int[] arr, FileWriter fos) throws IOException {
System.out.println("写到文件的数据大小:"+arr.length);
int length = arr.length;
StringBuilder str = new StringBuilder();
for (int i = 0; i < length; i++) {
str.append(arr[i]);
str.append("\r\n");
}
writeToFile(str.toString(),fos);
}
/**
* 将数据写入到文件中
* @param data
*/
public static void writeToFile(String data,FileWriter fos){
try {
fos.write(data);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将数据写入到最终文件夹
*
* @param data
* @throws IOException
*/
public static void dataDispose(int data) throws IOException {
buff.append(data);
buff.append("\r\n");
// fos.write(String.valueOf(data));
// fos.write("\r\n");
}
}
准备内容已差不多,我们现在开始正式将数据分别写入到文件中,请看main函数
/**
* 初始化数据
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
System.out.println("初始化数据开始");
long time = System.currentTimeMillis();
for (int i = 0; i<FileManager.writeList.size() ;i++){
System.out.println("开始第 "+i+" 文件操作");
//初始化数据
int[] nums = DataUtil.getRandomNum1();
System.out.println("初始化数据完成,数据大小:"+nums.length);
//排序
int[] sort = AgeSortTest.sort(nums);
System.out.println("排序完成,数据大小:"+sort.length);
FileManager.write(sort,FileManager.writeList.get(i));
System.out.println("写入文件完成");
}
fos.close();
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - time)/1000 + " s");
}
由于我这里没有判断文件是否存在,所以测试的时候你需要先将txt文件建好,否者会报错,运行main函数,我们来看文件数据
看样子文件应该是已经生成成功了,那文件中到底有没有数据呢?请看:
数据刚好是1千万,这是data1文件的数据,其他4个文件中的数据也是类似的,这里就不展示了。
将文件合并写入新文件并保证数据仍然有序
思路:
1.分别从5个文件中获取第一条数据。
2.比较5条数据的大小,找出最小的一条。
3.将最小的数据写入到新文件中。
4.在最小一条所在的文件中继续取出一条。
5.继续比较5条数据的大小,后面重复上面步骤,直到数据被全部读取完成。
请看main函数
/**
* 将文件按顺序写入到新文件中
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
long time = System.currentTimeMillis();
List<BufferedReader> readList = FileManager.readList;
int readListSize = readList.size();
int[] arr = new int[readListSize];
//读取每个文件第一行,将数据赋值给数组
for (int i = 0; i < readListSize; i++) {
arr[i] = FileManager.readData(readList.get(i));
}
int index = DataUtil.comp(arr);
FileManager.dataDispose(arr[index]);
while(readList.size() > 1){
Integer data = FileManager.readData(readList.get(index));
if(null == data){
readList.remove(index);
//从新给数组赋值
arr = set(arr,index);
}else{
arr[index] = data;
}
index = DataUtil.comp(arr);
FileManager.dataDispose(arr[index]);
}
//最后将
for(;;){
Integer lastData = FileManager.readData(readList.get(0));
if(null == lastData){
break;
}
FileManager.dataDispose(lastData);
}
FileManager.writeToFile(FileManager.buff.toString(), fos);
fos.flush();
fos.close();
long endTime = System.currentTimeMillis();
System.out.println("操作完成!");
System.out.println("操作用时:"+(endTime-time));
}
数据对比函数
/**
* 数据对比
* @param arr
* @return
*/
public static int comp(int[] arr){
//判断数据大小
int index = 0;
for (int i = 1; i < arr.length; i++) {
if(arr[index] > arr[i] ){
index = i;
}
}
return index;
}
数组重排
/**
* 数组重排
* @param arr
* @param index
* @return
*/
public static int[] set(int arr[], int index){
if(arr.length == 1){
return arr;
}
int[] newArr = new int[arr.length-1];
for(int i = 0; i< arr.length-1;i++ ){
if(i != index ){
newArr[i] = arr[i+1];
}else{
newArr[i] = arr[i];
}
}
return newArr;
}
运行main函数
发现耗时7秒,这速度是块还是慢呢?一个文件一千万数据,5个文件就是5千万,5千万的读取以及5千万的写入耗时7秒,这里的读取是一行一行的读取,但是写入的时候是一次性往新文件中写入5千万数据,那还有没有优化的空间呢?让时间更短一些,其实是有的。
1.我们之前做的是一行行的读取,我们可以改为一次读取多行放在内存中,cpu直接往内存中获取数据,获取一条,删除一条,当内存中的数据被删完时,也就代表内存中的数据已经全被使用,这时候就再次往磁盘中读取数据,一次类推,这样也能提升不少性能。
2.如果你仔细查看代码你会发现,这里的所有操作都是串行化,
第一步:获取到数据才能进行对比;
第二步:对比数据;
第三步:将最小的数据写入到新文件中;
不知道你发现没有,第一步获取数据需要第二部的支持(找到最小的那条数据)才能继续获取数据,但是和第三步并没有任何关系,所以,如果我们能将第一、二步串行;第三步与第一、二步并行,是否能减少运行时间呢?可以考虑Disruptor队列实现。
总结
为什么要使用计数排序
初始化数据的时候我使用了计数排序,我为什么会选择他呢?为什么不选择快排这样的排序方式?
我先介绍一下什么是计数排序:计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)。
由于我们的年龄区间范围并不大,很合适计数排序的要求,所以这里选择计数排序会比快速排序、归并排序等等快很多,如果区间范围太大,计数排序就不适用了,所以使用的时候还是要慎重考虑。
数据初始化是使用多线程是否会比较快?
答案是否定的,你可能会疑惑,多线程不就是来提高性能的吗?使用多线程来初始化数据还是变慢呢?
我们的数据都是一次性生成在内存中,然后再一次性写入到磁盘中,内存中的运行速度是很快的,多线程会牵扯到上下文的切换,这就会让cpu寄存器花更多的时间去保存当前被中断的堆栈,而单线程则不同,因为在内存中,没有线程切换所带来的额外消耗,一个线程会跑的更快,如果我们操作的是磁盘,那么我们就可以考虑使用多线程,因为磁盘的效率很低,会让cpu长期处理空闲时间,所以这时候使用多线程会提高程序的效果,用过redis的都知道,redis就是单线程应用,但是他同样可以达到读:10w/s ;写:8w/s,就是因为redis都是再内存中操作没有上下文的切换,性能会被发挥的很好,这时候你发现内存还是跟不上cpu的速度啊,应该还是有空闲时间,随着技术的发展,我们引入了cpu缓存,也就是cpu需要操作数据的时候会将内存的数据加载到缓存中,后面直接操作缓存即可,cpu往内存拿数据的时候并不是只拿指定的数据,他会获取指定的数据以及它周围的一部分数据,cpu认为它被访问了,那么他周围的数据也有很大可能被访问,这就是有序数组的随机访问比链表的访问速度块的原因之一。
既然是多线程,那么必然会伴随着线程安全问题,我们还要对共享的数据加锁或者进行CAS处理,速度也会受限制,什么时候使用多线程,应该具体问题具体分析,并不是多线程就一定会比单线程速度快。