一、Collections工具类所含通用算法
-
排序、二分查找、洗牌、旋转等
-
Sort排序
-
旧版的有归并排序和新版的TimSort
-
基本都是执行了TimSort
-
排序过程中还会因为元素的个数是否大于32而选择分段排序和二分插入排序。
-
-
BinarySearch 二分查找
-
需要是有序集合,对链表结构和数组结构分两个函数执行,数组的耗时基本是O(1),但链表的耗时基本都是O(n)级别。
-
对数组结构的操作
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) { int low = 0; int high = list.size()-1; while (low <= high) { int mid = (low + high) >>> 1; Comparable<? super T> midVal = list.get(mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found }
-
对链表结构的查询
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) { int low = 0; int high = list.size()-1; ListIterator<? extends Comparable<? super T>> i = list.listIterator(); while (low <= high) { int mid = (low + high) >>> 1; Comparable<? super T> midVal = get(i, mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found } // 链表迭代查询 private static <T> T get(ListIterator<? extends T> i, int index) { T obj = null; int pos = i.nextIndex(); if (pos <= index) { do { obj = i.next(); } while (pos++ < index); } else { do { obj = i.previous(); } while (--pos > index); } return obj; }
-
-
shuffle 洗牌算法
-
主要作用就是对集合中的元素进行打乱,一般可以用在摇奖、摇号、洗牌等各个场景中。
-
主要逻辑就是随机位置后进行位置置换,具体逻辑就是顺延处理,因为要保持rnd的最大值是已被置换过的。
public static void shuffle(List<?> list, Random rnd) { int size = list.size(); // 当size小于5 且集合类属于 RandomAccess if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) { for (int i=size; i>1; i--) swap(list, i-1, rnd.nextInt(i)); } else { Object[] arr = list.toArray(); // Shuffle array for (int i=size; i>1; i--) swap(arr, i-1, rnd.nextInt(i)); // Dump array back into list // instead of using a raw type here, it's possible to capture // the wildcard but it will require a call to a supplementary // private method // 链表结构置换数据 ListIterator it = list.listIterator(); for (Object e : arr) { it.next(); it.set(e); } } } // 置换位置 public static void swap(List<?> list, int i, int j) { // instead of using a raw type here, it's possible to capture // the wildcard but it will require a call to a supplementary // private method final List l = list; l.set(i, l.set(j, l.get(i))); }
-
-
rotate 旋转算法
-
可以把ArrayList和LinkedList从指定的某个位置开始,进行正旋或逆旋的操作,把需要的数据前移或后移,类似把数组比作绳子,然后打结成圆。
-
逻辑
-
distance = distance % size;,获取旋转的位置。
-
for 循环和 dowhile,配合每次的旋转操作,比如这里第一次会把 0 位置元素替换到2 位置,接着在 dowhile 中会判断 i != cycleStart,满足条件继续把替换了 2位置的元素继续往下替换。直到一轮循环把所有元素都放置到正确位置。
-
最终 list 元素被循环替换完成,也就相当从某个位置开始,正序旋转 2 个位置的操作。
-
-
二、StringBuilder与String的对比
-
StringBuilder为什么比String快,为什么
-
虽然String的加号拼接会被jvm优化成StringBuilder但它是每次都会new一个新的StringBuilder来使用,所以在for循环里还是把new StringBuilder的操作提取到外面性能会更好。
-
-
String的内部value和String类都是final,所以每次+号拼接时都会new 一个新的类来进行拼接并且toString();
-
String.intern() 时直接把值推进了常量池,所以两个对象都做了intern()操作后,比对的是常量池里的值。
-
例
String str_1 = new String("ab"); String str_2 = new String("ab"); String str_3 = "ab"; // 不会重新创建对象,jvm优化成先去常量池查找 System.out.println(str_1 == str_2); System.out.println(str_1 == str_2.intern()); System.out.println(str_1.intern() == str_2.intern()); System.out.println(str_1 == str_3); System.out.println(str_1.intern() == str_3);
-
intern是一个本地方法,底层给通过JNI调用C++语言编写的功能。
-
-
StringBuilder源码分析
-
底层是char[] 结构,数组结构,操作基本和ArrayList一致,也是需要扩容,copy数组,只不过再toString的时候会将char[] 通过new String的方式创建出来。
-
-
StringBuffer源码分析
-
底层操作基本和StringBuilder一致,只不过是在append时增加了synchronized锁,所以它是线程安全的。
-
synchronized 不是重量级锁吗,JVM 对它有什么优化呢?
-
其实为了减少获得锁与释放锁带来的性能损耗,从而引入了偏向锁、轻量级锁、重量级锁来进行优化。图是引用
-
从无锁状态开始,当线程进入 synchronized 同步代码块,会检查对象头和栈帧内是否有当前线下 ID 编号,无则使用 CAS 替换。
-
解锁时,会使用 CAS 将 Displaced Mark Word 替换回到对象头,如果成功,则表示竞争没有发生,反之则表示当前锁存在竞争锁就会升级成重量级锁。
-
另外,大多数情况下锁是不发生竞争的,基本由一个线程持有。所以,为了避免获得锁与释放锁带来的性能损耗,所以引入锁升级,升级后不能降级
-
-
三、如果你来提问,你会怎么提问
-
StringBuffer和StringBuilder的区别在哪里?
-
StringBuffer是线程安全的,StringBuilder是非线程安全的,主要区别在append方法中,StringBuffer是增加了synchronized锁。
-
-
String.intern()的作用
-
它会将char数组推入常量池,这个函数的本质是一个本地方法,会通过JNI调用C++编写的功能。
-
-
使用StringBuilder然后toString后会创建几个对象
-
两个,在toString时会创建一个String对象。
-
-
使用StringBuilder 进行for循环拼接为什么比String + 号拼接快
-
在JVM层面,String的+号会被优化成StrinBuilder,但它是每拼接一次+号就会新new出来一个StringBuilder,创建对象的时间耗时,所以将StringBuilder提取到外层来会比直接加号拼接快很多。
-