15

Currently I have an array of size N. I'm trying to copy every X amount of bytes from the array.

Example if the array is size 10 and I want arrays of size 3. I'd copy the first 3 elements then the next 3 and the last 1.

Currently I'm using the following algorithm:

int I = 0;
int sub = bytes.length;
int counter = 0;
for (I = 0; I < bytes.length; ++I) {
    if (I % 3 == 0 && I != 0) {
       NewArray[counter] = Arrays.copyOfRange(bytes, I - 3, I));
        sub -= 3;
        ++counter;
    }
}

NewArray[counter] = Arrays.copyOfRange(bytes, I - sub, I)); //Copy remainder.

Is there a more efficient or a more decent way of doing the what I want? This algorithm looks pretty bad =l

Any ideas how I can improve it or at least a hint?

Brandon
  • 22,723
  • 11
  • 93
  • 186

9 Answers9

13

What about this:

int x = 3;  // chunk size
int len = bytes.length;
int counter = 0;

for (int i = 0; i < len - x + 1; i += x)
    newArray[counter++] = Arrays.copyOfRange(bytes, i, i + x);

if (len % x != 0)
    newArray[counter] = Arrays.copyOfRange(bytes, len - len % x, len);
arshajii
  • 127,459
  • 24
  • 238
  • 287
  • Will produce a 0-length array for all cases where byte.length % 3 == 0, and will cause ArrayIndexOutOfBounds for the last iteration.... when i+x > bytes.length ..... – rolfl Oct 08 '13 at 01:17
  • Fixes case where len % 3 == 0, but you will still have invalid data in 2/3 of the cases where i+x > bytes.length (depending on bytes data type may be adding extra `(byte)0` values – rolfl Oct 08 '13 at 01:21
  • 2
    @rolfl Now it should be *really* fixed. I needed a `+ 1` in the loop condition. – arshajii Oct 08 '13 at 01:28
  • 1
    what is the meanning of ```newArray[counter++]``` ? – youHaveAlsoBeenABeginner Dec 05 '20 at 16:51
7

Here's a convenient method that converts a byte[] to an array of byte[]'s. So, the result is a byte[][].

public byte[][] splitBytes(final byte[] data, final int chunkSize)
{
  final int length = data.length;
  final byte[][] dest = new byte[(length + chunkSize - 1)/chunkSize][];
  int destIndex = 0;
  int stopIndex = 0;

  for (int startIndex = 0; startIndex + chunkSize <= length; startIndex += chunkSize)
  {
    stopIndex += chunkSize;
    dest[destIndex++] = Arrays.copyOfRange(data, startIndex, stopIndex);
  }

  if (stopIndex < length)
    dest[destIndex] = Arrays.copyOfRange(data, stopIndex, length);

  return dest;
}

Some advantages compared to the previous best answer:

  1. The for condition uses a <= which makes more sense than < ... + 1.
  2. Putting the stop-index in a temporary field reduces the number of calculations in the last if block.

(Unit tested)

bvdb
  • 22,839
  • 10
  • 110
  • 123
3

Few things to do here:

First, common conventions frown apon using capitals to start variable names, change the I and NewArray variables to 'i' and 'newArray' respectively.

Then, your code does not work because your first time through the loop, i-3 will lead to an IndexOutOfBounds exception.....

Finally, you do not show how you set the size of the newArray array.

int sublen = 3; // how many elements in each sub array.
int size = ((bytes.length - 1) / sublen) + 1; // how many newArray members we will need
byte[][] newArray = new byte[size][]; 
int to = byte.length;
int cursor = size - 1;
int from = cursor * sublen;
while (cursor >= 0) {
    newArray[cursor] = Arrays.copyOfRange(bytes, from, to);
    to = from;
    from -= sublen;
    cursor --;
}
rolfl
  • 17,539
  • 7
  • 42
  • 76
2

Here's my implementation for this, it will split your array in sub-arrays of up to a maximum size you decide on, and put the sub-arrays into a list of arrays. The last array will be smaller if the size of the array is not a multiple of the maximum size chosen.

import java.util.Arrays;
...

public static <T> List<T[]> splitArray(T[] items, int maxSubArraySize) {
  List<T[]> result = new ArrayList<T[]>();
  if (items ==null || items.length == 0) {
      return result;
  }

  int from = 0;
  int to = 0;
  int slicedItems = 0;
  while (slicedItems < items.length) {
      to = from + Math.min(maxSubArraySize, items.length - to);
      T[] slice = Arrays.copyOfRange(items, from, to);
      result.add(slice);
      slicedItems += slice.length;
      from = to;
  }
  return result;
}
Gubatron
  • 6,222
  • 5
  • 35
  • 37
2

Here is a function to split arrays, you can use below main method to test it.

private static List<Integer[]> splitArray(Integer[] originalArray, int chunkSize) {
List<Integer[]> listOfArrays = new ArrayList<Integer[]>();
int totalSize = originalArray.length;
if(totalSize < chunkSize ){
   chunkSize = totalSize;
}
int from = 0;
int to = chunkSize;

while(from < totalSize){
    Integer[] partArray = Arrays.copyOfRange(originalArray, from, to);
    listOfArrays.add(partArray);

    from+= chunkSize;
    to = from + chunkSize;
    if(to>totalSize){
        to = totalSize;
    }
}
return listOfArrays;
}

Testing method:

public static void main(String[] args) {
List<Integer> testingOriginalList = new ArrayList<Integer>();

for(int i=0;i<200;i++){
    testingOriginalList.add(i);
}

int batchSize = 51;
Integer[] originalArray = testingOriginalList.toArray(new Integer[]{});

List<Integer[]> listOfArrays = splitArray(originalArray, batchSize);


for(Integer[] array : listOfArrays){
    System.out.print(array.length + ", ");
    System.out.println(Arrays.toString(array));
}
}
Evan Hu
  • 977
  • 1
  • 13
  • 18
1

I know that this question is pretty old but hey, someone could search for another clean Java answer for this common question. It you are working with List (Java 7), there is a pretty simple and clean method to get a portion of a list : List.subList( fromIndex, toIndex )

It's straightforward to use. If I take the question example, it would be like :

int chunkSize = 3;
int counter = 0;
// bytes must be a List like an ArrayList
List<Byte> byteList = Arrays.asList(bytes);
int length = byteList.size(); 
for (int fromIndex = 0; fromIndex < length; fromIndex += chunkSize) {
   int toIndex = fromIndex + chunkSize;
   if(toIndex > length){
      toIndex = length;
   }
   NewArray[counter] = byteList.subList(fromIndex, toIndex);
   counter++;
}
// Now NewArray[] contain sub array and the last one is of the remaining length

To get ride of the 'counter', some could change the way NewArray is build for a List approach as well, with something like :

// NewArray must be a List<List<Byte>>
NewArray.addAll(byteList.subList(fromIndex, toIndex));

Hope this will help someone in the future !

CtrlX
  • 6,946
  • 1
  • 17
  • 21
0

You can use split with a special regular expression:

 System.out.println(Arrays.toString(
     "Thisismystringiwanttosplitintogroupswith4chareach".split("(?<=\\G.{4})")
 ));

Credit to earlier post by Alan Moore. Please visit and vote up.

Community
  • 1
  • 1
henderso
  • 1,035
  • 8
  • 13
  • 1
    If you're already using strings or the array is small, this would be okay. For a large byte array, converting to/from strings and regexp is a bit overkill. – Geobits Oct 08 '13 at 01:16
0

If actually you need quite big chunks, and don't want to modify their contents independently, consider reusing the same initial array by means of ByteBuffer.wrap() and then slice() repeatedly. This would prevent unnecessary copying and memory waste.

leventov
  • 14,760
  • 11
  • 69
  • 98
0
import java.util.Arrays;

public class Test {

    private void run() {
        try {

            byte[] cfsObjIds = "abcdefghij".getBytes();
            System.out.println(Arrays.toString(cfsObjIds));

            final int chunkSize = 4;
            System.out.println("Split by " + chunkSize + ":");
            int objQty = cfsObjIds.length;
            for (int i = 0; i < objQty; i += chunkSize) {
                int chunkUpperLimit = Math.min(objQty, i + chunkSize);
                byte[] cfsIdsChunk = Arrays.copyOfRange(cfsObjIds, i, chunkUpperLimit);

                System.out.println(Arrays.toString(cfsIdsChunk));
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        new Test().run();
    }
}
Splash
  • 106
  • 6