1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.volley.toolbox;
18 
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.List;
23 
24 /**
25  * ByteArrayPool is a source and repository of <code>byte[]</code> objects. Its purpose is to supply
26  * those buffers to consumers who need to use them for a short period of time and then dispose of
27  * them. Simply creating and disposing such buffers in the conventional manner can considerable heap
28  * churn and garbage collection delays on Android, which lacks good management of short-lived heap
29  * objects. It may be advantageous to trade off some memory in the form of a permanently allocated
30  * pool of buffers in order to gain heap performance improvements; that is what this class does.
31  *
32  * <p>A good candidate user for this class is something like an I/O system that uses large temporary
33  * <code>byte[]</code> buffers to copy data around. In these use cases, often the consumer wants the
34  * buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks off
35  * of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into account
36  * and also to maximize the odds of being able to reuse a recycled buffer, this class is free to
37  * return buffers larger than the requested size. The caller needs to be able to gracefully deal
38  * with getting buffers any size over the minimum.
39  *
40  * <p>If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this
41  * class will allocate a new buffer and return it.
42  *
43  * <p>This class has no special ownership of buffers it creates; the caller is free to take a buffer
44  * it receives from this pool, use it permanently, and never return it to the pool; additionally, it
45  * is not harmful to return to this pool a buffer that was allocated elsewhere, provided there are
46  * no other lingering references to it.
47  *
48  * <p>This class ensures that the total size of the buffers in its recycling pool never exceeds a
49  * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit,
50  * least-recently-used buffers are disposed.
51  */
52 public class ByteArrayPool {
53     /** The buffer pool, arranged both by last use and by buffer size */
54     private final List<byte[]> mBuffersByLastUse = new ArrayList<>();
55 
56     private final List<byte[]> mBuffersBySize = new ArrayList<>(64);
57 
58     /** The total size of the buffers in the pool */
59     private int mCurrentSize = 0;
60 
61     /**
62      * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay
63      * under this limit.
64      */
65     private final int mSizeLimit;
66 
67     /** Compares buffers by size */
68     protected static final Comparator<byte[]> BUF_COMPARATOR =
69             new Comparator<byte[]>() {
70                 @Override
71                 public int compare(byte[] lhs, byte[] rhs) {
72                     return lhs.length - rhs.length;
73                 }
74             };
75 
76     /** @param sizeLimit the maximum size of the pool, in bytes */
ByteArrayPool(int sizeLimit)77     public ByteArrayPool(int sizeLimit) {
78         mSizeLimit = sizeLimit;
79     }
80 
81     /**
82      * Returns a buffer from the pool if one is available in the requested size, or allocates a new
83      * one if a pooled one is not available.
84      *
85      * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be
86      *     larger.
87      * @return a byte[] buffer is always returned.
88      */
getBuf(int len)89     public synchronized byte[] getBuf(int len) {
90         for (int i = 0; i < mBuffersBySize.size(); i++) {
91             byte[] buf = mBuffersBySize.get(i);
92             if (buf.length >= len) {
93                 mCurrentSize -= buf.length;
94                 mBuffersBySize.remove(i);
95                 mBuffersByLastUse.remove(buf);
96                 return buf;
97             }
98         }
99         return new byte[len];
100     }
101 
102     /**
103      * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted
104      * size.
105      *
106      * @param buf the buffer to return to the pool.
107      */
returnBuf(byte[] buf)108     public synchronized void returnBuf(byte[] buf) {
109         if (buf == null || buf.length > mSizeLimit) {
110             return;
111         }
112         mBuffersByLastUse.add(buf);
113         int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
114         if (pos < 0) {
115             pos = -pos - 1;
116         }
117         mBuffersBySize.add(pos, buf);
118         mCurrentSize += buf.length;
119         trim();
120     }
121 
122     /** Removes buffers from the pool until it is under its size limit. */
trim()123     private synchronized void trim() {
124         while (mCurrentSize > mSizeLimit) {
125             byte[] buf = mBuffersByLastUse.remove(0);
126             mBuffersBySize.remove(buf);
127             mCurrentSize -= buf.length;
128         }
129     }
130 }
131