предисловие
Операции копирования массивов часто встречаются в программировании на Java.Как правило, существуют следующие четыре способа копирования массивов.
-
for
Traverse перебирает исходный массив и присваивает каждый элемент целевому массиву. -
clone
метод, исходный вызов массиваclone
Новый клонированный объект назначается целевому массиву, более подробные сведения о клонах см. в предыдущей статье "Клонирование объектов с точки зрения JDK". -
System.arraycopy
, реализация копирования массива, предоставляемая JVM. -
Arrays.copyof
, который на самом деле вызываетSystem.arraycopy
.
для обхода
В этом случае в слое Java записывается цикл for для обхода каждого элемента массива и его копирования.Если он не оптимизирован компилятором, он соответствует байт-коду операции обхода массива, а механизм выполнения зацикливается на получить массив в соответствии с этими байт-кодами, затем выполнить операцию копирования для каждого элемента .
Использование массива
Он очень прост в использовании, например, копирование массива следующим образом.
int size = 10000;
int[] src = new int[size];
int[] des = new int[size];
System.arraycopy(src, 0, des, 0, size);
метод копирования массива
Этот метод используется для выполнения операции копирования из указанного исходного массива.Вы можете указать начальную позицию и скопировать элементы указанной длины в указанный целевой массив. Метод является собственным методом, объявленным следующим образом:
@HotSpotIntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
О @HotSpotIntrinsicCandidate
Эта аннотация является стандартной аннотацией HotSpot VM. Метод, отмеченный ею, указывает на то, что это встроенный метод HotSpot VM. HotSpot VM внесет в него некоторые улучшения для повышения производительности выполнения, например, возможно, ручную сборку или ручную сборку. написан компилятор промежуточного языка для замены реализации этого метода. Хотя он объявлен как нативный метод, он отличается от других нативных методов в JDK: нативный метод будет реализован внутри JVM, а остальные будут реализованы в библиотеке JDK. С точки зрения вызова накладные расходы также сохраняются, поскольку внутренняя реализация JVM вызывается напрямую вместо обычного поиска JNI.
собственный метод копирования массива
Класс Java System имеет статический блок, который выполняется при загрузке класса, который выполняет соответствующиеregisterNatives
Родной метод.
public final class System {
private static native void registerNatives();
static {
registerNatives();
}
}
в соответствующемSystem.c
серединаJava_java_lang_System_registerNatives
Метод выглядит следующим образом, вы можете видеть, что есть три нативных метода, привязанных к встроенным методам JVM, один из которыхarraycopy
, его соответствующая функция(void *)&JVM_ArrayCopy
.
JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
#define OBJ "Ljava/lang/Object;"
static JNINativeMethod methods[] = {
{"currentTimeMillis", "()J", (void *)&JVM_CurrentTimeMillis},
{"nanoTime", "()J", (void *)&JVM_NanoTime},
{"arraycopy", "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},
};
Затем через вышеизложенноеarraycopy
метод связан со следующимJVM_ArrayCopy
предыдущая логика в основном используется для проверки того, пусты ли исходный массив и целевой массив, и если они пусты, генерируется нулевой указатель; затем преобразуйте объект исходного массива и объект целевого массива вarrayOop
, то есть описание объекта массива,assert
используется для определения того, являются ли они объектами; последнийs->klass()->copy_array
Это настоящая операция копирования массива.
JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored, jobject src, jint src_pos,
jobject dst, jint dst_pos, jint length))
JVMWrapper("JVM_ArrayCopy");
// Check if we have null pointers
if (src == NULL || dst == NULL) {
THROW(vmSymbols::java_lang_NullPointerException());
}
arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));
arrayOop d = arrayOop(JNIHandles::resolve_non_null(dst));
assert(s->is_oop(), "JVM_ArrayCopy: src not an oop");
assert(d->is_oop(), "JVM_ArrayCopy: dst not an oop");
// Do copy
s->klass()->copy_array(s, src_pos, d, dst_pos, length, thread);
JVM_END
Основные и распространенные типы
По упомянутому вышеs->klass()->copy_array
Чтобы завершить операцию копирования, процесс обработки фактически имеет различную обработку в соответствии с различными типами Java.Массивы можно разделить на базовые типы и общие типы в соответствии с типами элементов в них.Соответствующие JVM:TypeArrayKlass
иObjArrayKlass
.
TypeArrayKlass
Здесь некоторый исходный код проверки удаляется, оставляя основной код.Поскольку он включает перемещение указателей в памяти, чтобы повысить эффективность операции присваивания, начальная и конечная позиции преобразуются вchar*
,log2_element_size
заключается в вычислении длины типа элемента массиваlog
Значение, позиция может быть быстро рассчитана по операции смещения позже. иarray_header_in_bytes
Вычисление смещения первого элемента.
void TypeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) {
....
int l2es = log2_element_size();
int ihs = array_header_in_bytes() / wordSize;
char* src = (char*) ((oop*)s + ihs) + ((size_t)src_pos << l2es);
char* dst = (char*) ((oop*)d + ihs) + ((size_t)dst_pos << l2es);
Copy::conjoint_memory_atomic(src, dst, (size_t)length << l2es);
}
затем кCopy::conjoint_memory_atomic
функция, основная логика этой функции заключается в том, чтобы определить, к какому базовому типу принадлежит элемент, а затем вызвать соответствующую функцию. Поскольку уже есть начальный и конечный указатели, операции с быстрой памятью могут выполняться в зависимости от типа. Вот пример интегрального типа, который вызоветCopy::conjoint_jints_atomic
функция.
void Copy::conjoint_memory_atomic(void* from, void* to, size_t size) {
address src = (address) from;
address dst = (address) to;
uintptr_t bits = (uintptr_t) src | (uintptr_t) dst | (uintptr_t) size;
if (bits % sizeof(jlong) == 0) {
Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size / sizeof(jlong));
} else if (bits % sizeof(jint) == 0) {
Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size / sizeof(jint));
} else if (bits % sizeof(jshort) == 0) {
Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size / sizeof(jshort));
} else {
// Not aligned, so no need to be atomic.
Copy::conjoint_jbytes((void*) src, (void*) dst, size);
}
}
conjoint_jints_atomic
Функция в основном называетсяpd_conjoint_jints_atomic
функция, имеющая свою реализацию в разных операционных системах, см. здесьwindows_x86
Реализация,
static void conjoint_jints_atomic(jint* from, jint* to, size_t count) {
assert_params_ok(from, to, LogBytesPerInt);
pd_conjoint_jints_atomic(from, to, count);
}
Основная логика состоит в том, чтобы разделить репликацию на два случая: прямая репликация и обратная репликация. А присваивание осуществляется обходом массива по указателю, здесь выполняется копирование значения, которое некоторые называют так называемой «глубокой копией».
static void pd_conjoint_jints_atomic(jint* from, jint* to, size_t count) {
if (from > to) {
while (count-- > 0) {
// Copy forwards
*to++ = *from++;
}
} else {
from += count - 1;
to += count - 1;
while (count-- > 0) {
// Copy backwards
*to-- = *from--;
}
}
}
заlong
,short
,byte
и другие типы также обрабатываются аналогично, но в некоторых операционных системахcpu
Архитектура будет реализована на ассемблере.
ObjArrayKlass
Рассмотрим операцию копирования, когда в качестве элементов массива используются объекты обычного типа, здесь некоторые исходные коды проверки удаляются, оставляя основной код.UseCompressedOops
Идентификатор указывает на сжатие указателя объекта Java в JVM, в основном указывая, используется ли 32-разрядный или 64-разрядный указатель объекта. Игнорируйте его здесь, смотрите прямо на несжатый случай, то есть он вызоветdo_copy<oop>
функция.
void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d,
int dst_pos, int length, TRAPS) {
...
if (UseCompressedOops) {
narrowOop* const src = objArrayOop(s)->obj_at_addr<narrowOop>(src_pos);
narrowOop* const dst = objArrayOop(d)->obj_at_addr<narrowOop>(dst_pos);
do_copy<narrowOop>(s, src, d, dst, length, CHECK);
} else {
oop* const src = objArrayOop(s)->obj_at_addr<oop>(src_pos);
oop* const dst = objArrayOop(d)->obj_at_addr<oop>(dst_pos);
do_copy<oop> (s, src, d, dst, length, CHECK);
}
}
Этот код длиннее, такой же, я убираю часть кода, оставляю небольшой кусок кода, который может объяснить проблему. Здесь будут проводиться здесьs==d
Решение заключается в том, что исходный массив и целевой массив могут быть равны, и если они не равны, необходимо оценить, совпадает ли тип элемента исходного массива с типом элемента целевого массива. суждение о подклассе. Алгоритм основного назначения в двух приведенных выше случаях таков:Copy::conjoint_oops_atomic
.
template <class T> void ObjArrayKlass::do_copy(arrayOop s, T* src,
arrayOop d, T* dst, int length, TRAPS) {
BarrierSet* bs = Universe::heap()->barrier_set();
if (s == d) {
bs->write_ref_array_pre(dst, length);
Copy::conjoint_oops_atomic(src, dst, length);
} else {
Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass();
Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass();
if (stype == bound || stype->is_subtype_of(bound)) {
bs->write_ref_array_pre(dst, length);
Copy::conjoint_oops_atomic(src, dst, length);
} else {
...
}
}
bs->write_ref_array((HeapWord*)dst, length);
}
Эта функция также связана с операционной системой и архитектурой процессора, см. здесьwindows_x86
Реализация очень проста, и она также напрямую назначается путем обхода указателя.oop
это объектный класс уровня JVM, и этот класс не был переписанoperator=
Оператор по умолчанию копирует адрес, поэтому они по-прежнему указывают на одну и ту же память, которая также отражена в слое Java. Это так называемая «неглубокая копия».
static void conjoint_oops_atomic(oop* from, oop* to, size_t count) {
pd_conjoint_oops_atomic(from, to, count);
}
static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) {
if (from > to) {
while (count-- > 0) {
*to++ = *from++;
}
} else {
from += count - 1;
to += count - 1;
while (count-- > 0) {
// Copy backwards
*to-- = *from--;
}
}
}
Суммировать
System.arraycopy
Это внутренний метод внутри JVM, который создает копию массива Java с помощью ручной сборки или других методов оптимизации, что более эффективно, чем выполнение цикла for или клонирование непосредственно в Java. Чем больше массив, тем он очевиднее.
------------- Рекомендуем прочитать ------------
Резюме моей статьи за 2017 год — машинное обучение
Краткое изложение моих статей за 2017 год — Java и промежуточное ПО
Резюме моих статей 2017 года — глубокое обучение
Краткое изложение моих статей за 2017 год — исходный код JDK
Резюме моей статьи за 2017 год — обработка естественного языка
Резюме моих статей 2017 года — Java Concurrency
------------------рекламное время----------------
Меню официальной учетной записи было разделено на «распределенное», «машинное обучение», «глубокое обучение», «НЛП», «глубина Java», «ядро параллелизма Java», «исходный код JDK», «ядро Tomcat», и т.д. Там может быть один стиль, чтобы удовлетворить ваш аппетит.
Моя новая книга «Анализ дизайна ядра Tomcat» продана на Jingdong, и нуждающиеся друзья могут ее купить. Спасибо друзья.
Зачем писать «Анализ проектирования ядра Tomcat»
Добро пожаловать, чтобы следовать: