将5个文件中的一千万年龄合并到一个新文件中

目录

 

需求

初始化数据

将文件合并写入新文件并保证数据仍然有序

总结

为什么要使用计数排序

数据初始化是使用多线程是否会比较快?


需求

现在有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处理,速度也会受限制,什么时候使用多线程,应该具体问题具体分析,并不是多线程就一定会比单线程速度快。

 

发布了41 篇原创文章 · 获赞 79 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_33220089/article/details/103676900