Android 에서 SparseArray 성능 최적화 사용 방법

이전 글 에서 가로 2 급 메뉴 를 연 구 했 는데 그 중에서 SparseArray 를 사용 하여 HashMap 의 사용 을 대체 한 것 을 발견 했다.그래서 자신 이 관련 자 료 를 찾 아 성능 을 테스트 했다.우선 SparseArray 의 원 리 를 말씀 드 리 겠 습 니 다.
  SparseArray(희소 배열).그 는 Android 내부 특유 의 api 입 니 다.표준 jdk 는 이러한 종류 가 없습니다.Android 내부 에 서 는 HashMap형식 을 대체 하여 SparseArray 를 사용 하면 메모리 공간 을 더욱 절약 할 수 있 습 니 다.SparseArray 도 key 와 value 로 데 이 터 를 저장 합 니 다.사용 할 때 value 형식 만 지정 하면 됩 니 다.또한 key 는 대상 형식 으로 봉인 할 필요 가 없습니다.
  건물 주 는 친 측 에 따 르 면 SparseArray 가 데 이 터 를 저장 하 는 데 사용 하 는 메모리 공간 이 확실히 HashMap 보다 작 습 니 다.잠시 후에 테스트 데 이 터 를 내 보 내 분석 하고 있 습 니 다.우 리 는 우선 양자 의 구조 적 특성 을 살 펴 보 자.
  HashMap 은 배열 과 링크 의 결합 체 로 링크 해시 라 고 불 린 다.

  SparseArray 는 단순 배열 의 결합 입 니 다.희소 배열 이 라 고 불 리 며 데 이 터 를 저장 할 때 추가 비용 이 들 지 않 습 니 다.구 조 는 다음 과 같 습 니 다.

  이것 이 바로 두 사람의 구조 입 니 다.우 리 는 두 사람 이 도대체 어떤 차이 가 있 는 지 볼 필요 가 있 습 니 다.
  우선 삽입:
  HashMap 의 정렬 삽입:

 HashMap<Integer, String>map = new HashMap<Integer, String>();
 long start_map = System.currentTimeMillis();
 for(int i=0;i<MAX;i++){
   map.put(i, String.valueOf(i));
 }
 long map_memory = Runtime.getRuntime().totalMemory();
 long end_map = System.currentTimeMillis()-start_map;
 System.out.println("<---Map     --->"+end_map+"<---Map     --->"+map_memory);

 실행 후 결과:

 <---Map     --->914
 <---Map     --->28598272
SparseArray 의 정렬 삽입:

 SparseArray<String>sparse = new SparseArray<String>();
 long start_sparse = System.currentTimeMillis();
 for(int i=0;i<MAX;i++){
    sparse.put(i, String.valueOf(i));
 }
 long sparse_memory = Runtime.getRuntime().totalMemory();
 long end_sparse = System.currentTimeMillis()-start_sparse;
 System.out.println("<---Sparse     --->"+end_sparse+"<---Sparse     --->"+sparse_memory);

//      :
<---Sparse     --->611
<---Sparse     --->23281664

   100000 개의 데 이 터 량 의 정렬 이 삽입 되 었 을 때 SparseArray 의 효율 이 HashMap 보다 높 은 것 을 볼 수 있 습 니 다.또한 사용 하 는 메모리 도 HashMap 보다 작 습 니 다.여기 의 정렬 은 i 의 값 이 작은 것 에서 큰 것 으로 증가 하 는 것 을 나타 내 는 것 입 니 다.서열 은 i 의 값 에 달 려 있 습 니 다.for 순환 내부 에서 어떻게 실행 하 느 냐 가 아 닙 니 다.
  실행 후의 결 과 를 통 해 우 리 는 SparseArray 가 정상 적 인 삽입 을 할 때 효율 이 HashMap 보다 훨씬 빠 르 고 일부 메모리 도 절약 한 다 는 것 을 알 수 있다.인터넷 에 서 는 이들 의 효율 문제 에 대해 많은 사람들 이 SparseArray 가 HashMap 의 삽입 과 검색 보다 효율 이 빠르다 고 오해 하고,또 다른 사람들 은 Hash 검색 이 당연히 SparseArray 의 2 분 검색 보다 훨씬 빠르다 고 생각한다.
  사실 저 는 Android 에서를 저장 할 때 SparseArray 를 사용 하 는 것 을 추천 합 니 다.본질 적 인 목적 은 효율 적 인 이유 가 아니 라 메모리 때 문 이 라 고 생각 합 니 다.삽입 할 때 SparseArray 가 HashMap 보다 빠 른 것 을 확실히 보 았 습 니 다.그러나 이것 은 정상 적 인 삽입 일 뿐 입 니 다.역순 으로 삽입 되 는 상황 을 살 펴 보 겠 습 니 다.
  HashMap 역순 삽입:

 System.out.println("<-------------    100000       Map     --------------->");
 HashMap<Integer, String>map_2 = new HashMap<Integer, String>();
 long start_map_2 = System.currentTimeMillis();
 for(int i=MAX-1;i>=0;i--){
   map_2.put(MAX-i-1, String.valueOf(MAX-i-1));
 }
 long map_memory_2 = Runtime.getRuntime().totalMemory();
 long end_map_2 = System.currentTimeMillis()-start_map_2;
 System.out.println("<---Map     --->"+end_map_2+"<---Map     --->"+map_memory_2);
 
 //      :
 <-------------    100000 Map     --------------->
 <---Map     --->836<---Map     --->28598272
  SparseArray 역순 삽입:

System.out.println("<-------------    100000       SparseArray     --------------->");
SparseArray<String>sparse_2 = new SparseArray<String>();
long start_sparse_2 = System.currentTimeMillis();
for(int i=MAX-1;i>=0;i--){
  sparse_2.put(i, String.valueOf(MAX-i-1));
}
long sparse_memory_2 = Runtime.getRuntime().totalMemory();
long end_sparse_2 = System.currentTimeMillis()-start_sparse_2;
System.out.println("<---Sparse     --->"+end_sparse_2+"<---Sparse     --->"+sparse_memory_2);
//      
<-------------    100000 SparseArray     --------------->
<---Sparse     --->20222<---Sparse     --->23281664

 위의 운행 결 과 를 통 해 우 리 는 SparseArray 와 HashMap 이 어떻게 삽입 되 든 데이터 양 과 동시에 전 자 는 후자 보다 일부 메모 리 를 절약 해 야 하지만 효율 은?역순 으로 삽입 할 때 SparseArray 의 삽입 시간 과 HashMap 의 삽입 시간 은 수량 급 이 아 닌 것 을 볼 수 있 습 니 다.SparseArray 는 삽입 할 때마다 2 분 으로 같은 값 이 삽입 되 었 는 지 확인 해 야 하기 때문에 이러한 역순 의 상황 은 SparseArray 의 효율 이 가장 떨 어 질 때 입 니 다.
 SparseArray 의 삽입 소스 코드 를 간단하게 보 겠 습 니 다.

 public void put(int key, E value) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key); //    .

    if (i >= 0) { //      i      ,          key ,    value      ..
      mValues[i] = value;
    } else { //           ,            .
      i = ~i; //     i    .
      //i   mSize      . mKey mValue          .        .             .              .           .
      if (i < mSize && mValues[i] == DELETED) {
        mKeys[i] = key;
        mValues[i] = value;
        return;
      }
      //         ,  mKey        ,      gc()  .
      if (mGarbage && mSize >= mKeys.length) {
        gc();
        
        // Search again because indices may have changed.
        i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
      }
      //                 ,     key value        .
      if (mSize >= mKeys.length) {
        int n = ArrayUtils.idealIntArraySize(mSize + 1);
        //       key value  .    mSize
        int[] nkeys = new int[n];
        Object[] nvalues = new Object[n];

        // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
        //          copy  .    mKey   mValue               .            .
        System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
        System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
        //     ..              ..              .
        mKeys = nkeys;
        mValues = nvalues;
      }
      //  i      mSize  .     mKey     .
      if (mSize - i != 0) {
        // Log.e("SparseArray", "move " + (mSize - i));
        System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
        System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
      }
      //              .
      mKeys[i] = key;
      mValues[i] = value;
      mSize++;
    }
  } 

  이것 이 바로 SparseArray 삽입 함수 의 소스 코드 입 니 다.매번 삽입 방식 은 2 점 으로 찾 아야 합 니 다.따라서 거꾸로 삽입 할 때 상황 이 매우 나 빠 집 니 다.효율 적 으로 HashMap 에서 데이터 구 조 를 배 운 사람들 에 게 절대적 으로 졌 다 는 것 을 잘 알 고 있 습 니 다.Map 은 삽입 할 때 충돌 인자 에 대해 해당 하 는 결정 을 내 립 니 다.충돌 을 잘 처리 하 는 방식 이 있 습 니 다.모든 값 을 옮 겨 다 닐 필요 가 없습니다.따라서 역순 이 든 정상 적 인 삽입 의 효율 은 처리 충돌 방식 에 달 려 있 기 때문에 삽입 할 때 희생 하 는 시간 은 대체적으로 같 습 니 다.
  삽입 을 통 해 우 리 는 양자 의 차 이 를 알 수 있다.
  다시 한 번 찾 아 보 겠 습 니 다.

 System.out.println("<-------------    100000 Map  --------------->");
 HashMap<Integer, String>map = new HashMap<Integer, String>();
    
 for(int i=0;i<MAX;i++){
    map.put(i, String.valueOf(i));
 }
 long start_time =System.currentTimeMillis();
 for(int i=0;i<MAX;i+=100){
      map.get(i);
 }
 long end_time =System.currentTimeMillis()-start_time;
 System.out.println(end_time);
 
 //      
 <!---------     :175------------>
 SparseArray 찾기:

 System.out.println("<-------------    100000 SparseArray   --------------->");
 SparseArray<String>sparse = new SparseArray<String>();
 for(int i=0;i<10000;i++){
    sparse.put(i, String.valueOf(i));
 }
 long start_time =System.currentTimeMillis();
    
 for(int i=0;i<MAX;i+=10){
    sparse.get(i);
 }
 long end_time =System.currentTimeMillis()-start_time;
 System.out.println(end_time);
 //      
 <!-----------     :239---------------->
  저도 간단하게 검색 의 효율 을 테스트 했 습 니 다.한 데이터 나 몇 개의 데 이 터 를 조 회 했 습 니 다.이들 의 차 이 는 매우 작 습 니 다.데 이 터 량 이 100000 개 일 때 100000 개 를 찾 는 효율 은 Map 이 빠 릅 니 다.데 이 터 량 이 10000 일 때 차이 가 더욱 적 습 니 다.하지만 Map 의 검색 효율 은 한 수 이 겼 습 니 다.
  사실 제 가 보기 에는를 저장 할 때 SparseArray 를 사용 하여 HashMap 을 대체 하 는 주요 원인 은 메모리 때 문 입 니 다.저 장 된 데 이 터 는 크 든 작 든,Map 이 사용 하 는 메모 리 는 항상 SparseArray 보다 큽 니 다.데 이 터 량 이 100000 개 일 때 SparseArray 는 HashMap 보다 27%의 메모 리 를 절약 합 니 다.즉,효율 을 희생 하 는 대가 로 메모리 공간 을 절약 하 는 것 입 니 다.우 리 는 Android 가 메모리 에 대한 사용 이 매우 까다롭다 는 것 을 알 고 있 습 니 다.더미 에서 사용 할 수 있 는 가장 큰 메모 리 는 16M 에 불과 합 니 다.OOM 현상 이 발생 하기 쉽 기 때문에 Android 에서 메모리 사용 은 매우 까다 롭 습 니 다.그래서 공식 적 으로 는 SparseArray를 사용 하여 HashMap를 교체 하 는 것 을 추천 합 니 다.공식 적 으로 도 이러한 차이 점 이 50%를 넘 지 않 을 것 이 라 고 밝 혔 습 니 다.그래서 일부 효율 을 희생 하여 메모리 로 바 꾸 는 것 은 안 드 로 이 드 에서 도 좋 은 선택 이 라 고 할 수 있 습 니 다.

좋은 웹페이지 즐겨찾기