Java实现外部排序

外部排序使用场景及来源

主要针对大容量数据进行排序
在使用选择排序,插入排序,冒泡排序,和快速排序时的最大时间复杂度是O(n^2),因此对于几十万的数据量时排序要耗费很长的时间。对于外部的文件进行数据排序,首先要将数据送入内存中,然后对他们进行内部,但是如果文件过大,那么文件的所有数据不能送入内存,因此就有了外部排序。

外部排序的思路
  1. 首先将数据从文件中分段读入内存,并使用内部排序算法对于分段的数组进行排序,然后将数组输出到一个临时文件中。得到一个分段排序好的文件,类似下面这样
    在这里插入图片描述
    分为四段有序的数据段
  2. 将数据段 进行归并到一个大一些的有序分段中,并将新分段存储到新的临时文件中。然后循环该操作。
算法的思路流程图如下

a. 生成800000个随机数据, 分为8段 ,每段存储100000个数据。
b.每段的数据使用数组自带的排序算法进行排序即 Arrays.sort(list,0,i);
c.将s0,s1,s2,s3,s4,s5,s6,s7这八段数据进行两两归并排序。s0和s4归并,s1和s5归并,s2和s6归并,s3和s7归并 成为四个有序的段,依次重复归并直到总段数为1
在这里插入图片描述

主要算法代码实现
  1. 生成800000个随机数据 写到largedata.dat文件里面
  DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("largedata.dat")));
        for(int i=0;i<800000;i++){
            int number = (int) (Math.random()*100000);
            output.writeInt(number);
        }
  1. 将两个段进行合并 并写到新的段里面 可以理解为合并两个数组,只不过这两个数组存储在文件里面
/**
* 参数1:segmentSize 段的大小 即100000
* 参数2,3是比较的数据文件
* 参数4 存储新的有序数据文件
*/
private static void mergeTwoSegments(int segmentSize,DataInputStream f1,DataInputStream f2,DataOutputStream f3) throws Exception{
        int intFormF1 = f1.readInt();
        int intFormF2 = f2.readInt();
        int f1count =1;
        int f2count =1;
        //将f1.f2 有序写进f3中
        while(true){
            if(intFormF1<intFormF2){
                f3.writeInt(intFormF1);
                if(f1.available()==0||f1count++>segmentSize){
                //读到最后一个的时候发现f1为空此时直接将f2加进f3中
                    f3.writeInt(intFormF2);
                    break;
                }else{
                    intFormF1 = f1.readInt();
                }
            }else{
                f3.writeInt(intFormF2);
                if(f2.available()==0||f2count++>segmentSize){
                //读到最后一个的时候发现f2为空此时直接将f1加进f3中
                    f3.writeInt(intFormF1);
                    break;
                }else{
                    intFormF2 = f2.readInt();
                }
            }
        }
        while(f1.available()>0&&f1count++<segmentSize){
            f3.writeInt(f1.readInt());
        }
        while(f2.available()>0&&f2count++<segmentSize){
            f3.writeInt(f2.readInt());
        }
    }
  1. 合并所有的分段
    该函数的主要目的是
    如果numberOfSegments=8 表示有8个段执行循环归并成4个段 ,
    如果unmberOfSegments=4表示有4个段执行循环归并之后为2个段。
    函数执行的结果就是合并所有的分段并将新形成的分段存储在f3中。
/***
* 参数1 表示段的个数
* 参数2 段的大小
* 参数3 原始段,
* 参数4,原始段的一半。因为对于文件的读写来说符合先进后出的特性,因此从原始段中读取一半赋值个f2,此时原始段中的读的指针就剩未读的数据了。因此可以归并比较 示意图如下
*/
 private static void mergeSegments(int numberOfSegments,
                                      int segmentSize,DataInputStream f1,
                                      DataInputStream f2,DataOutputStream f3) throws Exception{
        for(int i=0;i<numberOfSegments;i++){
            mergeTwoSegments(segmentSize,f1,f2,f3);
        }
        while(f1.available()>0){
            f3.writeInt(f1.readInt());
        }
    }

在这里插入图片描述

  1. 将f1的一半复制给f2 ,并且进行合并操作
  private static void copyHalfToF2(int numberOfSegments,int segmentSize,
  								DataInputStream f1,DataOutputStream f2) throws Exception{
        for(int i=0;i<(numberOfSegments/2)*segmentSize;i++){
            f2.writeInt(f1.readInt());
        }//文件的输入输出符合栈的特点 先进后出
    }

 public static void mergeOneStep(int numberOfSegments,int segmentSize,String f1,String f2,String f3) throws Exception{
        DataInputStream f1input = new DataInputStream(new BufferedInputStream(new FileInputStream(f1),BUFFER_SIZE));
        DataOutputStream f2output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f2),BUFFER_SIZE));
        copyHalfToF2(numberOfSegments,segmentSize,f1input,f2output);
        f2output.close();

        //合并f1中剩下的和 f2段  写到f3中
        DataInputStream f2input = new DataInputStream(new BufferedInputStream(new FileInputStream(f2),BUFFER_SIZE));
        DataOutputStream f3output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f3),BUFFER_SIZE));
        //此时f1的读指针没有发生移动
        mergeSegments(numberOfSegments/2,segmentSize,f1input,f2input,f3output);
        f1input.close();
        f2input.close();
        f3output.close();
    }
  1. 递归归并
  public static void merge(int numberOfSegments,int segmentSize,String f1,String f2,String f3,String target) throws Exception{
        if(numberOfSegments>1){
            mergeOneStep(numberOfSegments,segmentSize,f1,f2,f3);
            merge((numberOfSegments+1)/2,segmentSize*2,f3,f1,f2,target);
        }else{
            File file = new File(target);
            if(file.exists()) file.delete();
            new File(f1).renameTo(file);
        }
    }

这个递归有点难以理解,
因为mergeOneStep 调用了copyHalfToF2 和mergeSegments,mergeSegments 又调用了mergeTwoSegment,嵌套的有点深。
总结主要函数的功能。
merge :负责合并 减少段的个数,增大段的大小。以容纳数据
mergeOneStep 负责将数据文件分成两大段,并且将两大段分别归并成新的段。
mergeSegments 将两大新段里面的子段分别合并。被mergeOneStep调用。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值