`

纯Java实现的多路归并快速排序算法

阅读更多

 

纯Java实现的多路归并快速排序算法,占用内存极少,速度很快,能处理上亿条的海量数据,无任何依赖.

[代码] 纯Java实现的多路归并快速排序算法

package wjw.PreTrans;

import java.io.*;
import java.util.*;
import org.apache.commons.io.*;

public class MergeSort {
  private static FastQSortAlgorithm<String> QQ = new FastQSortAlgorithm<String>(); 
  private MergeSort() {
  }

  /**
   * 得到指定文件的行数
   * @param fileC String
   * @return int
   * @throws IOException
   */
  private static int getFileLineSize(String fileC) throws IOException {
    Reader fC = null;
    try {
      fC = new FileReader(fileC);

      LineIterator it = IOUtils.lineIterator(fC);
      int lineSize = 0;
      while(it.hasNext()) {
        it.nextLine();
        lineSize++;
      }
      return lineSize;
    } finally {
      IOUtils.closeQuietly(fC);
    }
  }

  /**
   * 得到下一行的内容,如果已到文件末尾返回NULL
   * @param iterator LineIterator
   * @return String
   */
  private static String nextLine(LineIterator iterator) {
    if(iterator.hasNext()) {
      return iterator.nextLine();
    } else {
      return null;
    }
  }

  /**
   * 读指定行数的字符串到缓冲区列表里
   * @param iterator LineIterator
   * @param bufList List
   * @param lines int
   */
  private static void readLines(LineIterator iterator, List<String> bufList, int lines) {
    for(int i = 0; i < lines; i++) {
      if(!iterator.hasNext()) {
        break;
      }
      bufList.add(iterator.nextLine());
    }
  }

  /**
   * 扫描fileC中的归并段并把它们交替分别送到文件fileA和fileB中,
   * 本次归并段的大小为k*blockSize
   * @param fileA String
   * @param fileB String
   * @param fileC String
   * @param k int
   * @param blockSize int
   */
  private static void split(String fileA, String fileB, String fileC, int k,
                            int blockSize) throws FileNotFoundException, IOException {
    boolean useA = true;
    int i = 0;

    List<String> bufList = new ArrayList<String>(blockSize); //大小为blockSize的缓冲区
    Writer fA = null;
    Writer fB = null;
    Reader fC = null;
    try {
      fA = new BufferedWriter(new FileWriter(fileA));
      fB = new BufferedWriter(new FileWriter(fileB));
      fC = new FileReader(fileC);

      LineIterator itC = IOUtils.lineIterator(fC);
      while(itC.hasNext()) {
        //->读入数据块
        bufList.clear();
        readLines(itC, bufList, blockSize);
        //<-读入数据块

        if(useA) {
          IOUtils.writeLines(bufList, "\n", fA);
        } else {
          IOUtils.writeLines(bufList, "\n", fB);
        }

        if(++i == k) {
          i = 0;
          useA = !useA;
        }
      }
    } finally {
      bufList.clear();

      IOUtils.closeQuietly(fA);
      IOUtils.closeQuietly(fB);
      IOUtils.closeQuietly(fC);
    }
  }

  /**
   * n为当前归并段大小(k*blockSize);将文件fX的后续归并段拷入到fY,变量currRunPos为当前归并段的索引
   * @param fileX String
   * @param fileY String
   * @param currRunPos int
   * @param n int
   * @return int 当前归并段的索引
   */
  private static int copyTail(LineIterator fileX, Writer fileY, int currRunPos,
                              int n) throws IOException {
    //从当前位置到归并段结束,拷贝每个数据
    while(currRunPos <= n) {
      //若没有更多的数据项,则文件结束且归并段结束
      if(!fileX.hasNext()) {
        break;
      }

      //修改当前归并段位置并将数据项写入fY
      currRunPos++;
      IOUtils.write(fileX.nextLine() + "\n", fileY);
    }

    return currRunPos;
  }

  /**
   * 将文件fA和fB中长度为n(k*blockSize)的归并段合并回fC中
   * @param fileA String
   * @param fileB String
   * @param fileC String
   * @param n int
   * @throws IOException
   */
  private static void merge(String fileA, String fileB, String fileC, int n) throws
      IOException {
    //currA和currB表示在当前归并段中的位置
    int currA = 1;
    int currB = 1;

    //分别从fA和fB中读出的数据项
    String dataA, dataB;

    Reader fA = null;
    Reader fB = null;
    Writer fC = null;
    try {
      fA = new FileReader(fileA);
      fB = new FileReader(fileB);
      fC = new BufferedWriter(new FileWriter(fileC));

      LineIterator itA = IOUtils.lineIterator(fA);
      LineIterator itB = IOUtils.lineIterator(fB);

      dataA = nextLine(itA);
      dataB = nextLine(itB);
      for(; ; ) {
        //若(dataA<=dataB),则将dataA拷贝到fC并修改当前归并段的位置
        if(dataA.compareTo(dataB) <= 0) {
          IOUtils.write(dataA + "\n", fC);

          //从fA中取下一归并段,若不存在,则已到文件尾,应将fB的后续归并段拷入到fC;
          //若当前位置>n,则已将所有fA的归并段拷完,应拷贝fB的后续归并段
          dataA = nextLine(itA);
          currA++;
          if(dataA == null || currA > n) {
            IOUtils.write(dataB + "\n", fC);
            currB++;
            currB = copyTail(itB, fC, currB, n);

            //fA的大小>=fB的大小;若在fA的文件尾,则结束
            if(dataA == null) {
              break;
            } else { //否则,应在新的归并段中,重置当前位置
              currA = 1;
            }

            //取fB中的下一项.若不存在,则只有fA中剩余的部分要拷贝到fC,
            //退出循环前将当前归并段写入fC
            dataB = nextLine(itB);
            if(dataB == null) {
              IOUtils.write(dataA + "\n", fC);
              currA = 2;
              break;
            } else { //否则,重置fB中当前归并段
              currB = 1;
            }
          }
        } else { //否则(dataA>dataB)
          IOUtils.write(dataB + "\n", fC);

          //从fB中取下一归并段,若不存在,则已到文件尾,应将fA的后续归并段拷入到fC;
          //若当前位置>n,则已将所有fB的归并段拷完,应拷贝fA的后续归并段
          dataB = nextLine(itB);
          currB++;
          if(dataB == null || currB > n) {
            IOUtils.write(dataA + "\n", fC);
            currA++;
            currA = copyTail(itA, fC, currA, n);

            //若fB中没有更多项,则置fA的当前位置,准备拷贝fA中的最后归并段到fC中
            if(dataB == null) {
              currA = 1;
              break;
            } else { //否则,置fB的当前位置,并从fA中读入数据
              currB = 1;
              if((dataA = nextLine(itA)) == null) {
                break;
              } else {
                currA = 1;
              }
            }
          }
        }
      } //<- end for(; ;)

      //将fA中可能存在的剩余的归并段写入fC中(注:fA的长度时>=fB的)
      if(dataA != null && dataB == null) {
        currA = copyTail(itA, fC, currA, n);
      }
    } finally {
      IOUtils.closeQuietly(fA);
      IOUtils.closeQuietly(fB);
      IOUtils.closeQuietly(fC);
    }
  }

  /**
   * 用指定的blockSize块大小,排序指定的文件fileC
   * @param fileC String
   * @param blockSize int
   * @throws IOException
   */

  /**
   * 用指定的blockSize块大小,排序指定的文件fileSource,排序后的文件是fileOut
   * @param fileSource String
   * @param fileOut String
   * @param blockSize int
   * @param removeDuple
   * @throws IOException
   */
  public static void sort(String fileSource,String fileOut, int blockSize,boolean removeDuple) throws IOException {
    String fileA = File.createTempFile("wjw", null).getAbsolutePath();
    String fileB = File.createTempFile("wjw", null).getAbsolutePath();
    
    int mergeIndex = 1;
    int lineSize = getFileLineSize(fileSource);
    int k = 1;
    int n = k * blockSize;
    boolean useA = true;
    List<String> list = new ArrayList<String>(blockSize);

    Writer fA = null;
    Writer fB = null;
    Reader fC = null;
    try {
      fA = new BufferedWriter(new FileWriter(fileA));
      fB = new BufferedWriter(new FileWriter(fileB));
      fC = new FileReader(fileSource);

      LineIterator itC = IOUtils.lineIterator(fC);
      if(lineSize <= blockSize) { //对于小文件,从fC读入数据,直接排序后写回文件中
        readLines(itC, list, lineSize);
        Collections.sort(list);
        IOUtils.closeQuietly(fC);
        FileUtils.writeLines(new File(fileOut), "GBK", list, "\n");

        list.clear();
        
        if(removeDuple) {
          removeDuple(fileOut);
        }
        return;
      }

      //->第一次分割,合并
      System.out.println("第:"+mergeIndex+"分割,合并");
      while(itC.hasNext()) {
        list.clear();
        readLines(itC, list, blockSize);

        Collections.sort(list);
        if(useA) {
          IOUtils.writeLines(list, "\n", fA);
        } else {
          IOUtils.writeLines(list, "\n", fB);
        }

        useA = !useA;
      }
      list.clear();

      IOUtils.closeQuietly(fA);
      IOUtils.closeQuietly(fB);
      IOUtils.closeQuietly(fC);
      merge(fileA, fileB, fileOut, blockSize);
      mergeIndex++;
      //<-第一次分割,合并

      //->将当前归并段大小加倍,循环进行
      k = k * 2;
      n = k * blockSize;
      while(n < lineSize) { //当n>=文件大小时,fC仅剩一个已排好序的归并段
        System.out.println("第:"+mergeIndex+"分割,合并");
        split(fileA, fileB, fileOut, k, blockSize);
        merge(fileA, fileB, fileOut, n);
        mergeIndex++;
        k = k * 2;
        n = k * blockSize;
      }
      //->将当前归并段大小加倍,循环进行

    } finally {
      IOUtils.closeQuietly(fA);
      IOUtils.closeQuietly(fB);
      IOUtils.closeQuietly(fC);

      (new File(fileA)).delete();
      (new File(fileB)).delete();
    }
    
    if(removeDuple) {
      removeDuple(fileOut);
    }
  }

  /**
   * 删除已经排好序的文件中重复的数据
   * @param fileC String
   * @throws IOException
   */
  private static void removeDuple(String fileC) throws IOException {
    System.out.println("去重");
    Reader fC = null;
    Writer fTemp = null;
    File tempFile = File.createTempFile("wjw", null);
    try {
      fC = new FileReader(fileC);
      fTemp = new BufferedWriter(new FileWriter(tempFile));

      String tmpA = "";
      String tmpB = "";
      LineIterator itC = IOUtils.lineIterator(fC);
      while(itC.hasNext()) {
        tmpB = itC.nextLine();
        if(tmpB.compareTo(tmpA) != 0) {
          IOUtils.write(tmpB + "\n", fTemp);
          tmpA = tmpB;
        }
      }
    } finally {
      IOUtils.closeQuietly(fTemp);
      IOUtils.closeQuietly(fC);
    }

    File cFile = new File(fileC);
    if(cFile.delete()) {
      if(tempFile.renameTo(cFile)) {
        tempFile.delete();
      }
    }
  }

  public static String formatSecond(long seconds) {
    long h = seconds /(60*60);
    StringBuffer sb = new StringBuffer();
    
    sb.append(h+"小时");
    
    seconds = seconds%(60*60);

    long c = seconds /60;
    sb.append(c+"分");

    sb.append(seconds %60+"秒");
    
    return sb.toString();
  }

  public static void main(String args[]) {
    try {
      String fileName = "d:/ESort.txt";
      int blockSize = 100000;

      long c1 = System.currentTimeMillis();
      MergeSort.sort(fileName,fileName + "_SORT", blockSize,true);
      long c2 = (System.currentTimeMillis() - c1) / 1000;
      System.out.println("耗时:"+formatSecond(c2));
    } catch(IOException ex) {
      ex.printStackTrace();
    }
  }

} 
 
分享到:
评论

相关推荐

    各种排序代码的JAVA实现

    JAVA中实现基数排序可以使用多路归并,先按个位排序,再按十位,直至最高位,其时间复杂度为O(kn),其中k是数字的最大位数。 5. **选择排序**:选择排序每次找到当前未排序部分的最小(或最大)元素,放到已排序...

    一些简单排序的简单Java实现

    这里我们将深入探讨一些简单的排序算法的Java实现,包括它们的基本思想、工作原理以及如何在实际代码中应用。 1. **冒泡排序**(Bubble Sort) 冒泡排序是一种基础的排序算法,通过不断交换相邻的不正确顺序的元素...

    策略模式实现五种排序java代码

    然后,为每种排序算法创建一个实现类,如`BubbleSortStrategy`、`BinarySortStrategy`(可能是快速排序或二路归并排序)和`HeapSortStrategy`。这些实现类将包含具体的排序逻辑。接着,你可以有一个`Sorter`类,它...

    排序,一般的排序算法

    在Java中,我们可以使用内置的`Arrays.sort()`方法进行排序,它使用了一种称为Timsort的混合排序算法,结合了插入排序、归并排序和双路插入排序的优点,具有较好的性能保证和稳定性。 对于学习和实现排序算法,理解...

    java算法大全源码包.zip

    若将两个有序表合并成一个有序表,称为二路归并。时间复杂度O(n log n) 。 (6)堆排序 利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。

    Java方法排序五百万数据

    一种常见的外部排序算法是多路归并排序(Multi-Pass Merge Sort),它通过多次的划分、排序和合并步骤,逐步减少需要排序的数据量,直到所有数据都排好序。 在Java中实现外部排序,可以创建一个BufferedReader或...

    算法 第4版-谢路云 译(Java描述)-完整版

    #### 4.2 Java在算法实现中的优势 - **丰富的类库**:Java拥有强大的标准类库,支持各种复杂的数据结构和算法。 - **跨平台性**:Java编写的程序可以在任何安装了Java虚拟机(JVM)的平台上运行。 - **面向对象**:...

    【超全!】图解Java数据结构和算法(共195集)【资料+视频+课件+代码+笔记】

    稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫...快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树...

    算法 第4版-谢路云 译 Java描述 -完整版.pdf

    3. **排序与搜索**:排序算法包括插入排序、选择排序、交换排序(快速排序、归并排序)、堆排序、计数排序、桶排序、基数排序等,以及更高级的排序技巧如Timsort。搜索算法则有顺序搜索、二分搜索、哈希搜索等。这些...

    算法 第4版-谢路云 译(Java描述)中英文

    1. **排序算法**:包括插入排序、选择排序、快速排序、归并排序、堆排序等,深入讲解了每种算法的工作原理、时间复杂度和空间复杂度,以及它们之间的比较和优化。 2. **查找算法**:介绍了线性查找、二分查找、哈希...

    数据结构Java排序PPT学习教案.pptx

    外部排序通常涉及到多路归并等技术。 2. **冒泡排序**: - 冒泡排序是最简单的排序算法之一。它通过重复遍历数组,比较相邻元素并交换位置,使较大的元素逐渐“冒”到数组末尾。每一轮遍历(称为一趟)会确保最大...

    Java实现直接插入排序和折半插入排序算法示例

    直接插入排序和折半插入排序是两种常见的简单...在实际应用中,通常会选择更高效的排序算法,如快速排序、归并排序或堆排序,尤其对于大规模数据。然而,这些基本排序算法对理解排序原理和算法设计思想具有重要意义。

    算法(第四版 Java语言) 谢路云译 PDF扫描版 下载地址

    书中会详细讨论各种排序算法(如冒泡排序、插入排序、快速排序、归并排序、堆排序等)和搜索算法(如线性搜索、二分搜索、哈希表搜索等),并分析它们的性能特性。 3. **图算法篇**:这部分会涉及图论的基础知识,...

    最常用的 8 个排序算法:从原理到改进,再到代码兑现透彻解析1

    本讨论主要聚焦于最常用的8个排序算法,包括它们的原理、优化以及在Java中的具体实现。以下是这些算法的详细介绍: 1. **插入排序**: - 基本思想:将未排序的元素逐个插入到已排序的部分,每次插入时找到合适的...

    14排序优化:如何实现一个通用的、高性能的排序函数?.pdf

    例如,在实际应用中,可以采用多路归并排序来处理大文件排序,或者通过多线程并行排序来加速排序过程。同时,还需要关注数据的特性,如是否存在大量重复数据、数据是否已经部分排序等,根据这些特性来选择或者调整...

    排序分割文件

    在给定的`MergeSort.java`文件中,可能包含了实现双路归并排序算法的Java代码。通常,Java代码会定义一个类,包含分割文件、对子文件排序以及合并文件的方法。这些方法可能会使用到文件I/O操作,如`java.io`包中的`...

Global site tag (gtag.js) - Google Analytics