2017-03-04 4 views
7

Excel 문서에서 C#으로 모든 텍스트 데이터를 추출하려고하는데 성능 문제가 있습니다. 다음 코드에서는 통합 문서를 열고 모든 워크 시트를 반복하며 사용 된 범위의 모든 셀을 반복하면서 각 셀의 텍스트를 추출합니다. 문제는 실행하는데 14 초가 걸린다는 것입니다.C# Excel Interop 셀을 반복 할 때 느려짐

public class ExcelFile 
{ 
    public string Path = @"C:\test.xlsx"; 
    private Excel.Application xl = new Excel.Application(); 
    private Excel.Workbook WB; 
    public string FullText; 
    private Excel.Range rng; 
    private Dictionary<string, string> Variables; 
    public ExcelFile() 
    { 
     WB = xl.Workbooks.Open(Path); 
     xl.Visible = true; 
     foreach (Excel.Worksheet CurrentWS in WB.Worksheets) 
     { 
      rng = CurrentWS.UsedRange; 
      for (int i = 1; i < rng.Count; i++) 
      { FullText += rng.Cells[i].Value; } 
     } 
     WB.Close(false); 
     xl.Quit(); 
    } 
} 

VBA에서 나는 ~ 일초합니다 같은 것을, 할 것입니다 반면 :

Sub run() 
    Dim strText As String 
    For Each ws In ActiveWorkbook.Sheets 
     For Each c In ws.UsedRange 
      strText = strText & c.Text 
     Next c 
    Next ws 
End Sub 

또는 더 빠르게 (1 초 미만) :

Sub RunFast() 
    Dim strText As String 
    Dim varCells As Variant 
    For Each ws In ActiveWorkbook.Sheets 
     varCells = ws.UsedRange 
     For i = 1 To UBound(varCells, 1) 
      For j = 1 To UBound(varCells, 2) 
       strText = strText & CStr(varCells(i, j)) 
      Next j 
     Next i 
    Next ws 
End Sub 

아마도 뭔가를 C#의 for 루프에서 일어나는 일입니다. 셀 객체가 아닌 값만 반복 할 수 있도록 배열 유형 객체에 범위를로드 할 수 있습니까 (마지막 예제에서와 같이)?

+0

VBA는 프로세스 내에서 실행되지만 첫 번째 스 니펫은 프로세스 외부에서 실행됩니다. 프로세스 경계를 ​​넘는 것이 느립니다. 배열을 사용하여 EPPlus 또는 ClosedXML과 같은 OpenXML을 기반으로하는 왕복 횟수 또는 진행중인 솔루션을 줄입니다. –

+0

감사합니다 한스 - 궁금한 점이 있다면 왕복을 피하기 위해 배열에 범위를로드하는 방법은 무엇입니까? – pwwolff

답변

3

Excel과 C#은 완전히 다른 환경에서 실행됩니다. C#은 관리되는 메모리를 사용하는 .NET 프레임 워크에서 실행되는 반면 Excel은 기본 C++ 응용 프로그램이며 관리되지 않는 메모리에서 실행됩니다. 이 둘 사이의 데이터 변환 ("마샬링"이라고하는 프로세스)은 성능면에서 극도로 비쌉니다.

코드를 조정하면 도움이되지 않습니다. 루프의 경우, 문자열 생성 등은 모두 마샬링 프로세스와 비교해 놀랍도록 빠릅니다. 현저하게 향상된 성능을 얻으려면 프로세스 간 경계를 넘어야하는 트립 수를 줄이는 것이 유일한 방법입니다.. 셀을 기준으로 데이터 셀을 추출하는 경우 을 입력하지 마십시오. 원하는 성능을 얻을 수 있습니다.

  1. , 당신이 원하는 모든 작업을 수행 VBA에서 하위 또는 함수를 작성 후 상호 운용성을 통해 그 하위 또는 함수를 호출 :

    여기에 몇 가지 옵션이 있습니다. Walkthrough.

  2. interop를 사용하여 워크 시트를 CSV 형식의 임시 파일에 저장 한 다음 C#을 사용하여 파일을 엽니 다. 루프를 통해 파일을 유용한 데이터 구조로 가져와야하지만이 루프는 훨씬 빠르게 진행됩니다.

  3. interop을 사용하여 셀 범위를 클립 보드에 저장 한 다음 C#을 사용하여 클립 보드를 직접 읽습니다.

+0

4. (다른 답변에서 암시 된 것처럼), 모든 값을 읽으십시오. 서식을 유지하는 변경을해야합니다. 5. 매개 변수와 함께 get_Value()를 사용하여 서식을 포함하여 편집 할 수있는 XML을 가져옵니다. – Jbjstam

1

속도를 높이는 한 가지 방법은 이전 문자열에서 += 대신 StringBuilder를 사용하는 것입니다. C#에서는 문자열이 변경되지 않으므로 최종 문자열을 만드는 과정에서 여분의 문자열이 많이 생성됩니다.

또한 색인을 반복하지 않고 행, 열 위치에서 성능 반복을 향상시킬 수 있습니다.

public class ExcelFile 
{ 
    public string Path = @"C:\test.xlsx"; 
    private Excel.Application xl = new Excel.Application(); 
    private Excel.Workbook WB; 
    public string FullText; 
    private Excel.Range rng; 
    private Dictionary<string, string> Variables; 
    public ExcelFile() 
    { 
     StringBuilder sb = new StringBuilder(); 
     WB = xl.Workbooks.Open(Path); 
     xl.Visible = true; 

     foreach (Excel.Worksheet CurrentWS in WB.Worksheets) 
     { 
      rng = CurrentWS.UsedRange; 
      for (int i = 1; i <= rng.Rows.Count; i++) 
      { 
       for (int j = 1; j <= rng.Columns.Count; j++) 
       { 
        sb.append(rng.Cells[i, j].Value); 
       } 
      } 
     } 
     FullText = sb.ToString(); 
     WB.Close(false); 
     xl.Quit(); 
    } 
} 
+0

팁 주셔서 감사합니다 2 초 (12까지), 그래서 그것은 요소 였을 것 같습니다 - 그리고 일반적으로 명심하는 것이 좋습니다. – pwwolff

3

I이 기능을 사용 여기

은 모두 StringBuilder 및 행으로 변경 코드 열 위치 루핑. 루프는 인덱스 0에서 시작하는 배열로 변환하기위한 것이며, 주요 작업은 object[,] tmp = range.Value입니다.

public object[,] GetTable(int row, int col, int width, int height) 
{ 
    object[,] arr = new object[height, width]; 

    Range c1 = (Range)Worksheet.Cells[row + 1, col + 1]; 
    Range c2 = (Range)Worksheet.Cells[row + height, col + width]; 
    Range range = Worksheet.get_Range(c1, c2); 

    object[,] tmp = range.Value; 

    for (int i = 0; i < height; ++i) 
    { 
     for (int j = 0; j < width; ++j) 
     { 
      arr[i, j] = tmp[i + tmp.GetLowerBound(0), j + tmp.GetLowerBound(1)]; 
     } 
    }     

    return arr; 
} 
2

나는 당신과 공감합니다. Excel 셀을 통한 루핑은 비용이 많이 듭니다. 안토니오와 맥스는 모두 정확하지만 존 우의 대답은 그것을 훌륭하게 요약합니다. 문자열 빌더를 사용하면 작업 속도가 빨라지고 사용 된 범위에서 객체 배열을 만들 수 있습니다. IMHO는 interop을 사용하여 얻을 수있는 속도만큼 빠릅니다. 더 잘 수행 할 수있는 타사 라이브러리가 있다는 것을 알고 있습니다. interop을 사용하여 파일이 큰 경우 각 셀을 루핑하는 데 시간이 오래 걸립니다.

아래 테스트에서 시트에 사용 된 범위 데이터가 11 열과 100 행으로 구성된 단일 시트가있는 통합 문서를 사용했습니다. 객체 배열 구현을 사용하면 1 초 조금 넘게 걸렸습니다. 735 개의 행으로 약 40 초가 걸렸습니다.

3 개의 단추를 여러 줄의 텍스트 상자가있는 양식에 놓았습니다. 첫 번째 단추는 게시 된 코드를 사용합니다. 두 번째 버튼은 루프 범위를 벗어납니다. 세 번째 단추는 객체 배열 방식을 사용합니다. 각각은 다른 것보다 현저한 성능 향상을 보입니다. 양식에 텍스트 상자를 사용하여 데이터를 출력 했으므로 문자열을 그대로 사용할 수 있지만 하나의 큰 문자열이 있어야하는 경우 문자열 작성기를 사용하는 것이 좋습니다.

파일이 큰 경우 다른 구현을 고려할 수도 있습니다. 희망이 도움이됩니다.

private void button1_Click(object sender, EventArgs e) { 
    Stopwatch sw = new Stopwatch(); 
    MessageBox.Show("Start DoExcel..."); 
    sw.Start(); 
    DoExcel(); 
    sw.Stop(); 
    MessageBox.Show("End DoExcel...Took: " + sw.Elapsed.Seconds + " seconds and " + sw.Elapsed.Milliseconds + " Milliseconds"); 
} 

private void button2_Click(object sender, EventArgs e) { 
    MessageBox.Show("Start DoExcel2..."); 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    DoExcel2(); 
    sw.Stop(); 
    MessageBox.Show("End DoExcel2...Took: " + sw.Elapsed.Seconds + " seconds and " + sw.Elapsed.Milliseconds + " Milliseconds"); 
} 

private void button3_Click(object sender, EventArgs e) { 
    MessageBox.Show("Start DoExcel3..."); 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    DoExcel3(); 
    sw.Stop(); 
    MessageBox.Show("End DoExcel3...Took: " + sw.Elapsed.Seconds + " seconds and " + sw.Elapsed.Milliseconds + " Milliseconds"); 
} 

// object[,] array implementation 
private void DoExcel3() { 
    textBox1.Text = ""; 
    string Path = @"D:\Test\Book1 - Copy.xls"; 
    Excel.Application xl = new Excel.Application(); 
    Excel.Workbook WB; 
    Excel.Range rng; 

    WB = xl.Workbooks.Open(Path); 
    xl.Visible = true; 
    int totalRows = 0; 
    int totalCols = 0; 
    foreach (Excel.Worksheet CurrentWS in WB.Worksheets) { 
    rng = CurrentWS.UsedRange; 
    totalCols = rng.Columns.Count; 
    totalRows = rng.Rows.Count; 
    object[,] objectArray = (object[,])rng.Cells.Value; 
    for (int row = 1; row < totalRows; row++) { 
     for (int col = 1; col < totalCols; col++) { 
     if (objectArray[row, col] != null) 
      textBox1.Text += objectArray[row,col].ToString(); 
     } 
     textBox1.Text += Environment.NewLine; 
    } 
    } 
    WB.Close(false); 
    xl.Quit(); 
    Marshal.ReleaseComObject(WB); 
    Marshal.ReleaseComObject(xl); 
} 

// Range taken out of loops 
private void DoExcel2() { 
    textBox1.Text = ""; 
    string Path = @"D:\Test\Book1 - Copy.xls"; 
    Excel.Application xl = new Excel.Application(); 
    Excel.Workbook WB; 
    Excel.Range rng; 

    WB = xl.Workbooks.Open(Path); 
    xl.Visible = true; 
    int totalRows = 0; 
    int totalCols = 0; 
    foreach (Excel.Worksheet CurrentWS in WB.Worksheets) { 
    rng = CurrentWS.UsedRange; 
    totalCols = rng.Columns.Count; 
    totalRows = rng.Rows.Count; 
    for (int row = 1; row < totalRows; row++) { 
     for (int col = 1; col < totalCols; col++) { 
     textBox1.Text += rng.Rows[row].Cells[col].Value; 
     } 
     textBox1.Text += Environment.NewLine; 
    } 
    } 
    WB.Close(false); 
    xl.Quit(); 
    Marshal.ReleaseComObject(WB); 
    Marshal.ReleaseComObject(xl); 
} 

// original posted code 
private void DoExcel() { 
    textBox1.Text = ""; 
    string Path = @"D:\Test\Book1 - Copy.xls"; 
    Excel.Application xl = new Excel.Application(); 
    Excel.Workbook WB; 
    Excel.Range rng; 

    WB = xl.Workbooks.Open(Path); 
    xl.Visible = true; 
    foreach (Excel.Worksheet CurrentWS in WB.Worksheets) { 
    rng = CurrentWS.UsedRange; 
    for (int i = 1; i < rng.Count; i++) { 
     textBox1.Text += rng.Cells[i].Value; 
    } 
    } 
    WB.Close(false); 
    xl.Quit(); 
    Marshal.ReleaseComObject(WB); 
    Marshal.ReleaseComObject(xl); 
} 
+0

일관된 예제를 가져 주셔서 감사합니다! 이제 COM 라이브러리를 사용할 때의 문제에 대해 잘 알고 있습니다. 그리고 마샬링 :) – pwwolff