2013-03-14 15 views
6

Być może mam zamiar o tym w niewłaściwy sposób, ale brakuje niektórych absolutnie put metod na ByteBuffer.Brakuje niektórych metod bezwzględnych na ByteBuffer

Jeśli spojrzysz na ByteBuffer zobaczysz, że większość metod put ma zarówno wariant bezwzględny, jak i względny.

wyjątkiem:

  • Zapis częściami byte tablicy w ByteBuffer.
  • Zapisywanie ByteBuffer do ByteBuffer.

.. i potrzebuję dokładnie tych.

Żeby było jasne ByteBuffer ma metody:

put(byte[] src, int offset, int length) 
put(ByteBuffer src) 

ale brakuje:

put(int index, byte[] src, int offset, int length) 
put(int index, ByteBuffer src) 

mam powody, dlaczego nie chce się przenieść pozycję wskaźnika bufora, stąd chcę używać tylko bezwzględne metody put.

Każdy pomysł, dlaczego te metody zostały pominięte?

Mogę oczywiście naśladować brakujące metody bez przesuwania wskaźnika pozycji bufora, ale będzie to dotyczyło pętli nad bajtami źródłowymi. Javadoc wyraźnie stwierdza, że ​​te metody są (potencjalnie) znacznie bardziej wydajne niż przenoszenie niż zapętlanie i przenoszenie bajtów jeden po drugim. Wierzę w Javadoc, ponieważ moje testy wskazują na to samo. Muszę wycisnąć jak najwięcej prędkości z mojej implementacji, więc jestem oczywiście skłonny skorzystać z każdej dużej metody, którą mogę dostać w swoje ręce ... gdyby tylko istniały.

Incedentially ByteBuffer Brakuje również metody absolutnej get dla przesunięcia tablicy bajtów częściowych. Ale tak naprawdę nie potrzebuję obecnie takiej metody. Ale znowu dziwne, że nie istnieje.

+0

Tylko kontynuacja. Znalazłem ten http://thesoftwarelife.blogspot.dk/2009/10/java-bytebuffer-annoyances.html, który wydaje się narzekać na mniej więcej to samo. – peterh

+1

Twierdzę, że metody absolutne są bardziej użyteczne i ważne niż te relatywne. Gdybym przeprojektował ten interfejs API, pominąłbym całą pozycję, odwrócił, przewinął, oznaczył, zresetował i tak dalej, i skupił się na zapewnieniu dostępu do pamięci, pozostawiając ją w zasadzie ciężką tablicą z C "union" cechy. – Boann

+1

@Boann - ja też powinienem. Dokładnie tak, jak o tym myślę. – peterh

Odpowiedz

4

Jednym ze sposobów uzyskania potrzebnych metod jest posiadanie drugiego ByteBuffera, który dzieli tę samą pamięć, aby można było zmienić jego położenie bez zmiany pozycji oryginału.

Niestety, metoda slice również nie przyjmuje parametru pozycji; zamiast tego używa aktualnej pozycji oryginalnego bufora. Więc nie można zrobić:

dstBuffer.slice(100).put(srcBuffer); 

Oto kilka pomysłów, w przypadkowej kolejności, z wyjątkiem, że to rozkaz Myślałem o nich:

  • czy pasuje z okazji używasz bufora, możesz przygotować kopię bufora przy pomocy slice() i zachować ją, gdy chcesz umieścić dane w pozycji niezależnej od pozycji oryginału.

  • Jeśli pozycja chcesz absolutnym oddane jest zawsze większa lub równa pozycja wskaźnika oryginalnego bufora, można zrobić:

    dstBuffer.slice().position(desiredPosition - dstBuffer.position()).put(srcBuffer); 
    

To nie będzie działać, aby umieścić na wcześniejsze położenie, niestety, ponieważ pozycja na wycinku nie może być ujemna. EDIT: Nieważne, zapomniałem o metodzie duplicate. Zobacz wspaniałą odpowiedź @ BorisBrodskiego.

  • Jeśli nie używasz bezpośrednie bufory bajt System.arraycopy jest łatwa i szybka:

    System.arraycopy(
        srcBuffer.array(), srcBuffer.arrayOffset() + srcBuffer.position(), 
        dstBuffer.array(), dstBuffer.arrayOffset() + desiredPosition, 
        srcBuffer.remaining() 
    ); 
    
  • Jeżeli równoczesny dostęp nie jest to konieczne, można tymczasowo zmienić pozycję bufora, gdy trzeba zrobić absolutny put i umieścić go później. Jeśli potrzebujesz jednoczesnego dostępu, ale twierdzenie wątek jest niska, można synchronizować wszystkie dostępu do bufora (być może oczywiste, ale włączone do kompletności):

    synchronize (lock) { 
        int originalPosition = dstBuffer.position(); 
        dstBuffer.position(desiredPosition); 
        dstBuffer.put(srcBuffer); 
        dstBuffer.position(originalPosition); 
    } 
    
  • jeśli żaden z innych pomysłów pracować dla Ciebie, możesz zhakuj bufor. Jest brudny, ale oto przykład:

    private static final sun.misc.Unsafe UNSAFE; 
    static { 
        Object result = null; 
        try { 
         Class<?> klass = Class.forName("sun.misc.Unsafe"); 
         for (Field field : klass.getDeclaredFields()) { 
          if (field.getType() == klass && 
           (field.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == 
            (Modifier.FINAL | Modifier.STATIC)) { 
           field.setAccessible(true); 
           result = field.get(null); 
           break; 
          } 
         } 
        } catch (Throwable t) {} 
        UNSAFE = result == null ? null : (sun.misc.Unsafe)result; 
    } 
    
    private static final Field ADDRESS_FIELD; 
    static { 
        Field f; 
        try { 
         f = Buffer.class.getDeclaredField("address"); 
         f.setAccessible(true); 
        } catch (NoSuchFieldException | SecurityException e) { 
         f = null; 
        } 
        ADDRESS_FIELD = f; 
    } 
    
    
    public static void absolutePut(ByteBuffer dstBuffer, int dstPosition, ByteBuffer srcBuffer) { 
        if (!srcBuffer.isDirect()) { 
         absolutePut(dstBuffer, dstPosition, 
          srcBuffer.array(), srcBuffer.arrayOffset() + srcBuffer.position(), 
          srcBuffer.remaining()); 
         return; 
        } 
    
        if (UNSAFE != null && ADDRESS_FIELD != null && dstBuffer.isDirect()) { 
         try { 
          long dstAddress = (long)ADDRESS_FIELD.get(dstBuffer) + dstPosition; 
          long srcAddress = (long)ADDRESS_FIELD.get(srcBuffer) + srcBuffer.position(); 
          UNSAFE.copyMemory(srcAddress, dstAddress, srcBuffer.remaining()); 
         } catch (IllegalAccessException e) { 
          throw new RuntimeException(e); 
         } 
        } else { 
         // fallback to basic loop 
         for (int i = srcBuffer.position(); i < srcBuffer.limit(); i++) { 
          dstBuffer.put(dstPosition + i, srcBuffer.get(i)); 
         } 
        } 
    } 
    
    public static void absolutePut(ByteBuffer dstBuffer, int dstPosition, byte[] src, int srcOffset, int length) { 
        if (UNSAFE != null && ADDRESS_FIELD != null && dstBuffer.isDirect()) { 
         try { 
          long dstAddress = (long)ADDRESS_FIELD.get(dstBuffer) + dstPosition; 
          UNSAFE.copyMemory(
           src, UNSAFE.arrayBaseOffset(byte[].class) + srcOffset, 
           null, dstAddress, 
           length); 
         } catch (IllegalAccessException e) { 
          throw new RuntimeException(e); 
         } 
        } else { 
         // fallback to System.arraycopy 
         System.arraycopy(
          src, srcOffset, 
          dstBuffer.array(), dstBuffer.arrayOffset() + dstPosition, 
          length); 
        } 
    } 
    

Dałem ten kod pewną minimalną testy z mieszanek buforów bezpośrednich i innych bezpośrednich i wydaje porządku. Jeśli techniki odbicia zawiodą (np. Ponieważ znajdujesz się w bezpiecznym środowisku testowym apletu lub implementacja Java nie jest kompatybilna), może on powrócić do zwykłych metod.

+0

To interesujące, że bardzo zależy Ci na współbieżności. Chociaż ByteBuffer nie jest przeznaczony do jednoczesnego używania i nie ma gwarancji, że którakolwiek z jego metod, nawet absolutna, jest bezpieczna dla wątków, nadal możemy rozsądnie przyjąć pewną współbieżność i jednocześnie używać bufora w niektórych określonych aplikacjach. (większość aplikacji korzysta z ByteBuffer nie równolegle) – ZhongYu

+0

@Boann. Dziękuję za to. Trochę ścigałem się po tej trasie, ale kończę na milionie Bajtów, które wszyscy muszą gdzieś zamieszkać. Ciągle mnie zastanawia, dlaczego tych metod brakuje. Być może jest coś, co przeoczyłem. I tak, niestety jestem w sytuacji, w której używam bezpośrednich buforów, w przeciwnym razie zgadzam się, że rozwiązanie "System.arraycopy()" byłoby dobre. – peterh

2
static void put(ByteBuffer buffer, int index, ByteBuffer src) 
{ 
    int p0 = buffer.position(); 
    buffer.position(index); 
    buffer.put(src); 
    buffer.position(p0); 
} 
+0

Niestety, nie rozwiązanie. Przesuwasz wskaźnik położenia celu (ponieważ robisz 'buffer.position (index)'), co oznacza, że ​​twoje rozwiązanie nie ma cech bezwzględnej metody put.(co oznacza, że ​​wskaźnik pozycji pozostaje niezmieniony) – peterh

+0

@ nolan6000: Umieszczasz wskaźnik położenia celu dokładnie z powrotem w miejscu, w którym się rozpoczął, co jest rzeczywiście doskonałym rozwiązaniem tego problemu. +1 dla tego legalnego rozwiązania. –

+0

@ nolan6000 uh? pozycja zostaje zmieniona z powrotem na jego wartość początkową. – ZhongYu

3

Moim zdaniem te metody zostały pominięte, ponieważ nie istnieją inne takie metody. Istnieje szereg metod bezwzględnych, w których biorą udział prymitywy, w tym "bajt", ale nie istnieją żadne metody absolutne pobierające prymitywy.

Rozwiązaniem mogłoby być:

((ByteBuffer)buffer.duplicate().position(index)).put(src, offset, length); 
((ByteBuffer)buffer.duplicate().position(index)).put(otherByteBuffer); 
+0

Chociaż ma niewielki nakład na tworzenie obiektów, jest to najlepsze rozwiązanie, jakie uważam. Jest krótki i słodki i spełnia swoją rolę. – Boann

+0

Zgadzam się. Wygląda słodko. Będę musiał sprawdzić, czy wpływ na wydajność tworzenia obiektu jest znikomy, czy nie. Jeśli używałbym tego rozwiązania, wykonywałbym powyższe dla każdej operacji zapisu, którą chcę wykonać. Wydaje się, że dużo narzut, ale czasami my (lub raczej ja sam) widzimy problemy z wydajnością tam, gdzie ich nie ma. :-). Dzięki za wskazówkę. – peterh

0

Oto co używam do absolutnego luzem get():

// Perform a bulk get() that doesn't modify the buffer 
public void get(ByteBuffer buf, int position, byte[] dest, int off, int len) { 
    if (buf.hasArray()) 
     System.arraycopy(buf.array(), buf.arrayOffset() + position, dest, off, len); 
    else if (len < 256) {  // 256 is a wild guess TODO: performance testing 
     while (len-- > 0) 
      dest[off++] = buf.get(position++); 
    } else 
     ((ByteBuffer)buf.duplicate().position(position)).get(dest, off, len); 
} 

A Podobne prace approache dla put().

Powiązane problemy