现在有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:UsersAdministratorDesktopdata1.txt");public static File file2 = new File("C:UsersAdministratorDesktopdata2.txt");public static File file3 = new File("C:UsersAdministratorDesktopdata3.txt");public static File file4 = new File("C:UsersAdministratorDesktopdata4.txt");public static File file5 = new File("C:UsersAdministratorDesktopdata5.txt");public static File file = new File("C:UsersAdministratorDesktopfinaldata.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("rn");}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("rn");
// fos.write(String.valueOf(data));
// fos.write("rn");}}
准备内容已差不多,我们现在开始正式将数据分别写入到文件中,请看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处理,速度也会受限制,什么时候使用多线程,应该具体问题具体分析,并不是多线程就一定会比单线程速度快。