文章目录
- ArrayList 集合底层原理
- LinkedList 集合(底层双链表)
- Iterator 迭代器源码分析
- HashSet 底层原理
- HashMap 底层原理
- TreeMap 底层原理
ArrayList 集合底层原理
原理:
① 利用空参创建的集合,在内存中是默认长度为0的
② 添加第一个元素的时候,底层会创建一个长度为10的数组
③ 存满时,会扩容1.5倍
④ 如果一次添加多个元素,1.5倍还放不下,新创建的数组以实际为准
源码分析
- ArrayList 的空参构造
public ArrayList() {
// 这个数组是空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
elementDate 是java底层的数组
// 可以看到,elementData是一个数组对象
transient Object[] elementData;
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
size 默认是 0
private int size;
- 添加元素
e 代表添加的元素
size 默认是 0 代表集合长度和 元素应存入的位置
public boolean add(E e) {
// 调用方法,进入ensureCapacityInternal 传递长度为 1
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将当前元素传给size位置,并移动到下一个位置
elementData[size++] = e;
// 返回添加成功
return true;
}
- ensureCapacityInternal 这个方法记录修改次数,和显示容量
minCapacity 是最小容量,传递的值为 1
modCount++ 是记录修改次数
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 最小容量 - 集合长度 = 1 1 > 0 成立
if (minCapacity - elementData.length > 0)
// 调用grow 方法 把最小容量传递过去
grow(minCapacity);
}
- 扩容 grow
minCapacity 是最小容量,传递的值为 1
oldCapacity 是原来的容量 老容量 这里为0
newCapacity 是新的容量 这里是0
MAX_ARRAY_SIZE int 包装类Integer - 8 2147483647 - 8
private void grow(int minCapacity) {
// overflow-conscious code
// 老容量 = 集合的长度 初始为0 0
int oldCapacity = elementData.length;
// 新容量 = 老容量 + 老容量 右移一位 还是0 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量 - 最小容量 < 0 就把最小容量给新容量 实现扩容
if (newCapacity - minCapacity < 0)
// 容量扩容 至原来的1.5倍
newCapacity = minCapacity;
// 如果新容量 - 最大数 (2147483647 - 8) > 0 就直接扩大最大数
if (newCapacity - MAX_ARRAY_SIZE > 0)
//
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 拷贝数组到新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
- hugeCapacity 判断传递最大数
minCapacity 最小容量
MAX_ARRAY_SIZE 2147483647 - 8 )
Integer.MAX_VALUE 0x7fffffff 转换为10进制 2147483647
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
// 如果是负数,这里报错,应该是内存溢出
throw new OutOfMemoryError();
// 如果是大于 2147483647 - 8
// 就传递 2147483647 否则传递2147483647 - 8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
LinkedList 集合(底层双链表)
源码分析
创建一个LinkdList 集合
LinkedList<string> list = new Linkedlist<>();
内存会创建一个包含头节点和尾结点的空间
- 结点对象
private static class Node<E> {
// 记录我要存储的数据
E item;
// 记录下一个结点的地址值
Node<E> next;
// 记录上一个结点的地址值
Node<E> prev;
// 传递数值
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
- 三个成员变量
// 集合的长度 也表示结点的总个数
transient int size = 0;
// 头结点
transient Node<E> first;
// 尾结点
transient Node<E> last;
- add 添加方法
// e 表示元素
public boolean add(E e) {
// 调用linkLast 传递元素
linkLast(e);
return true;
}
- linkLast 创建结点
void linkLast(E e) {
// last 初始值是null 集合尾结点
final Node<E> l = last;
// 并创建一个结点 赋值给 newNode
// new Node<>(l, e, null); 上一个结点 元素 下一个结点 null e null
final Node<E> newNode = new Node<>(l, e, null);
// 把刚刚创建的结点newNode 赋值给 集合last尾结点 last
last = newNode; // 每次都会把新结点赋值
// 如果 l 等于空
if (l == null)
// 把 newNode 赋值给 集合头节点
first = newNode;
else
// 把 新结点newNode 赋值给 当前l.next 也就是上一个尾结点
l.next = newNode;
// 集合长度+1
size++;
modCount++; // 修改次数
}
Iterator 迭代器源码分析
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
- 首先创建一个迭代器 创建好之后 就会new Itr
public Iterator<E> iterator() {
return new Itr();
}
- 就会在内部类创建一个迭代器
private class Itr implements Iterator<E> {
// 表示光标 当前索引
int cursor; // index of next element to return
// 表示上一个索引,光标
int lastRet = -1; // index of last element returned; -1 if no such
// modCount 集合变化的次数 删除和添加都会自增
// expectedModCount 创建迭代器的时候就会初始次数
int expectedModCount = modCount;
// 空参
Itr() {}
// 判断下一个索引是否大于等于迭代器的最大元素
// 如果大于等于返回false
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
// 元素光标移动
public E next() {
// 当前集合中最新的变化次数跟一开始记录的次数是否相同,
// 如果相同,证明当前集合没有发生改变
// 如果不一样,证明在送代器遍历集合的过程中,
// 使用了集合中的方法添加/删除了元素
checkForComodification();
// 当前光标赋值给 i
int i = cursor;
// 当前光标是否大于等最大元素
if (i >= size)
// 成立,报错越界
throw new NoSuchElementException();
// 把当前底层的数组赋值给 elementData
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 光标移动下一个位置
cursor = i + 1;
// 上一个元素 等于 i 当前光标 并添加元素
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
// expectedModCount 初始修改次数 和 每一次相比较
if (modCount != expectedModCount)
// 并发异常报错
throw new ConcurrentModificationException();
}
}
HashSet 底层原理
Hashset集合底层原理
HashSet集合底层采取哈希表存储数据
哈希表是一种对于增删改查数据性能都较好的结构
哈希表组成
JDK8之前:数组+链表
JDK8开始:数组+链表+红黑树
- 创建一个默认长度为16,默认加载因素为0.75的数组,数组名table
HashSet<String> hm = new HashSet<>();
- 根据元素的哈希值跟数组的长度计算出应存入的位置
int index = (数组长度 - 1) & 哈希值;
- 判断当前位置是否为 null,如果为 null 直接存入
- 如果不为 null,表示有元素,则调用equals方法比较属性值
- 一样:不存 不一样:存入数组形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面 - 会依次和当前数组位置的所有元素依次比较
细节:当数组存了 16 * 0.75 = 12 个元素的时候,数组就会扩容2倍
当链表长度大于8而且数组长度大于等于64,链表就会自动转换红黑树
如果集合中存入的是自定义对象,必须重写hashCode和equals
HashMap 底层原理
常规说明:
这个C表示类名
下面的M表示方法,当方法名和类名相同时,代表构造方法,其他都是成员方法
后面的向上的箭头表示,重写的父类方法,后面是父类类名
后面的向右的箭头表示,继承过来的方法
像这种 f 开头的黄色的,代表是成员变量或者是常量
这个绿色的 i 表示接口来自Map
蓝色的是类,最小边都是内部类
在HashMap中,每一个元素都是一个Node节点
// 实现 Map 里面的 Entry对象(链表数组)
static class Node<K,V> implements Map.Entry<K,V> {
// 根据键计算出来的哈希值
final int hash;
// 键
final K key;
// 值
V value;
// 在链表中记录下一个节点的地址值
Node<K,V> next;
}
红黑树的每一个节点叫做 TreeNode
// TreeNode 继承与 LinkedHashMap 的 Entry对象
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
// 父节点的地址值
TreeNode<K,V> parent; // red-black tree links
// 左子节点
TreeNode<K,V> left;
// 右子节点
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
// 当前节点的颜色,false 黑色
boolean red;
介绍一些常量和变量:
表示是 Node元素的数组,存入的都是Node对象
transient Node<K,V>[] table;
默认的数组长度是 16,1左移4位
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
默认的加载因子是0.75,扩容时机数组乘以0.75,就是12,每次扩容是两倍
static final float DEFAULT_LOAD_FACTOR = 0.75f;
数组最大的长度,1左移30位 1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;
下面是HashMap的构造方法
无参构造
public HashMap() {
// 把默认加载因子赋值给成员变量,loadFactor, 这个时候没有创建数组
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
添加元素
返回给真正的添加数据 putVal
- 第一个参数获取哈希值
- 第二个是键
- 第三个是值
- 第四个表示当前的数据是否保留,判断是否覆盖
- 第五个没太大关系
// k 键 v 值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
过程:
- tab 局部变量,表示哈希表中的数组的地址值。
- p 临时的第三方变量,记录键值对对象的地址值
- n 表示数组的长度
- i 表示索引
Node<K,V>[] tab; Node<K,V> p; int n, i;
- tab = table 将数组的地址赋值给tab
- n = tab.length 将数组的长度赋值给n
- 然后判断数组是否等于 0 或者 数组等于 null
- 调用resize方法,把数组长度再次赋值给 n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
- i = (n - 1) & hash
- n - 1 是数组 和 键的哈希值进行计算,计算出应存入的位置,并赋值给 i
- p = tab[i] 获取元素中的数据赋值给 p
- 如果p 为 null else不执行
if ((p = tab[i = (n - 1) & hash]) == null)
// 把要添加的 哈希值,键,值,空 赋值给newNode
// 返回一个node对象,给当前索引
tab[i] = newNode(hash, key, value, null);
else {
// 键值对对象
Node<K,V> e; K k;
// p.hash == hash 数组中的哈希值 和 当前添加的哈希值 是否相等
// 如果不相等,返回false
if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 判断数组中的获取出来的键值对是否是红黑树的节点
else if (p instanceof TreeNode)
// 如果是 调用方法 putTreeVal,把当前节点按照红黑树的规则添加到树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果不是,就挂链表
for (int binCount = 0; ; ++binCount) {
// 判断当前 p 的下一个节点是否等于 null 并赋值给 e
if ((e = p.next) == null) {
// 如果等于空,直接把这个对象,赋值给p的下一个节点,形成链表
p.next = newNode(hash, key, value, null);
// 判断当前链表长度是否大于8,就会调用 treeifyBin
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 进入treeifyBin还会继续判断
//如果数组长度大于64,就转红黑树
treeifyBin(tab, hash);
// 跳出循环
break;
}
// 如果当前元素的哈希 和 p元素的下一个元素(就是链表下面的)
// 哈希值对比,不一样就false e.hash == hash
// 如果哈希值一样,就会调用equals方法进行比较 key.equals(k)
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
// 成立就跳出
break;
p = e;
}
}
// 如果e为null,表示当前不需要覆盖元素
// 否则覆盖
if (e != null) { // existing mapping for key
// 当前元素的value 赋值给 老的value
V oldValue = e.value;
// 取反就是true
if (!onlyIfAbsent || oldValue == null)
// 把新的值,赋值给老的元素value
e.value = value;
afterNodeAccess(e);
// 返回老的值
return oldValue;
}
- threshold 就是数组长度 * 0.75,哈希表的扩容时机
- ++size 默认是0,先+1,用 1 > 扩容时机吗 threshold默认是12
- return null; 表示本次添加数据,没有覆盖元素
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict); // 暂时
return null;
resize方法
- 如果当前是第一次添加数据,底层会创建一个长度为16 ,加载因子为0.75的数组
- 如果不是第一次添加数据,会看数组中元素是否达到扩容的条件
- 如果没有达到条件,底层不做任何事情
- 如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据转移到新的哈希表中
final Node<K,V>[] resize() {
// 把数组地址赋值给oldTab
Node<K,V>[] oldTab = table;
// 如果数组是null 就 返回0,否则,返回长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 判断oldCap 如果大于0
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 如果小于0,就把默认值 16 赋值给 newCap
newCap = DEFAULT_INITIAL_CAPACITY;
// 把加载因子 * 默认长度16 赋值给newThr (扩容的意思)
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// new 一个node 创建一个数组长度是 newCap 赋值给 newTab
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 赋值给 数组 table
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
newNode方法
- 就是创建了一个Node对象,进行赋值操作并返回
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
TreeMap 底层原理
先认识一下 每个节点就是 Entry
static final class Entry<K,V> implements Map.Entry<K,V> {
// 键
K key;
// 值
V value;
// 左子节点地址值
Entry<K,V> left;
// 右子节点地址值
Entry<K,V> right;
// 父节点的地址值
Entry<K,V> parent;
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}
public String toString() {
return key + "=" + value;
}
}
还有两个静态常量
red | false表示红色 |
black | true 表示黑色 |
private static final boolean RED = false;
private static final boolean BLACK = true;
还有静态对象
// 表示比较的规则
private final Comparator<? super K> comparator;
// 表示根节点
private transient Entry<K,V> root;
// 表示集合的长度,也表示红黑树中节点的个数
private transient int size = 0;
当我们利用空参构造创建对象时,表示没有传递规则
public TreeMap() {
// comparator 就是比较器
comparator = null;
}
当我们利用有参构造创建对象时,表示传递规则,规则就是我们传递的比较器对象
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
添加元素的流程
调用put方法
// 参数一,键,参数二,值
public V put(K key, V value) {
// 调用 put 重载方法
return put(key, value, true);
}
调用 put 重载方法
参数1 | 键 |
参数2 | 值 |
参数3 | 是否覆盖默认为 true |
private V put(K key, V value, boolean replaceOld) {
// 获取根节点(root)的地址值,赋值给 t
// 第一次为null
Entry<K,V> t = root;
// 判断 t 是否 为 null
// 如果是第一次添加,就会把当前元素作为根节点
// 如果不是第一次添加,就会不执行
if (t == null) {
// 方法底层会创建一个entry对象,把它当作根节点
addEntryToEmptyMap(key, value);
// 返回null,表示没用覆盖任何元素
return null;
}
// 表示两个元素的键比较之后的结果
int cmp;
// 当前要添加节点的父节点
Entry<K,V> parent;
// 表示当前比较规则
//如果我们采取的默认自然排序,此时comparator为null,cpr 也是null
//如果我们采取的比较器排序方式,此时comparator记录的就是比较器
Comparator<? super K> cpr = comparator;
// 判断是否有比较器对象
// 传递了比较器,就执行if,没有就是else
if (cpr != null) {
do {
// 把当前节点赋值给父节点
parent = t;
// 调用传入的比较器,比较一下
cmp = cpr.compare(key, t.key);
if (cmp < 0)
// 负数。继续调左边
t = t.left;
else if (cmp > 0)
// 正数调用右边
t = t.right;
else {
//为0覆盖
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
} else {
Objects.requireNonNull(key);
@SuppressWarnings("unchecked")
// 把键进行强转,强转成Comparable类型的
// 要求,键必须实现Comparable接口,如果没有实现这个接口
// 此时在强转的时候就会报错
Comparable<? super K> k = (Comparable<? super K>) key;
do {
// 把根节点赋值给当前节点,当作当前节点的父节点
parent = t;
// 调用比较器对象,比较这两个的大小关系
cmp = k.compareTo(t.key);
//如果比较结果为负数
if (cmp < 0)
// 那就继续到根节点的左边去找
t = t.left;
// 如果比较的为正数
else if (cmp > 0)
// 那就到根节点的右边去找
t = t.right;
else {
// 如果比较的结果为0,那会覆盖
// 将当前节点的值,赋值给oldValue
V oldValue = t.value;
// 如果replaceOld 或者oldValue 等于空
if (replaceOld || oldValue == null) {
// 就把当前值付给t.value
t.value = value;
}
// 返回之前的值
return oldValue;
}
// 判断当前t是不是null
} while (t != null);
}
// 把当前节点按照比较器规则添加
addEntry(key, value, parent, cmp < 0);
return null;
}
addEntryToEmptyMap 方法
private void addEntryToEmptyMap(K key, V value) {
// 创建比较器,把 key 和 key 去比较,第一次的时候,key肯定是一样的
compare(key, key); // type (and possibly null) check
// 创建 entry 对象,把元素赋值给根节点
root = new Entry<>(key, value, null);
//长度 为1
size = 1;
modCount++;
}
添加的位置
private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
//new一个entry对象赋值给e
Entry<K,V> e = new Entry<>(key, value, parent);
//如果添加到左边就把 e赋值给parent.left
if (addToLeft)
parent.left = e;
else
//如果添加到左边就把 e赋值给 parent.right
parent.right = e;
// 添加完节点要按照红黑树规则进行调整
fixAfterInsertion(e);
size++;
modCount++;
}
红黑规则进行调整
x.parent.color | 获取父节点的颜色 |
parentOf(parentOf(x) | 获取父节点的父节点 |
leftOf | 获取左节点 |
private void fixAfterInsertion(Entry<K,V> x) {
//因为红黑树的节点默认是红色的
x.color = RED;
//判断是不是空,判断是不是根节点,判断.color == RED
while (x != null && x != root && x.parent.color == RED) {
// 判断当前节点的父亲节点 == 左子节点的爷爷节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}