Java字符串String 集合的迭代器
Java字符串String
我们知道Java的字符窜是Immutable(不可变)的,一旦创建就不能更改其内容了;平常我们对字符串的操作是最多的,其实对字符串的操作,返回的字符串都是新建的字符串对象,原来并没有被改动,这跟C#是一模一样的;
既然字符串是不可变量,当我们对字符串进行各种操作时的效率肯定是有影响的,比如我们平时最常用的 + 运算符:
public class ConcatString{
public static void main(String[] args) {
var name = "marson";
var s = "abc" + name + "shine" + 47+ "nancy" + "summer zhu";
print(s);
}
}
这段代码我相信在我们日常开发中很容易遇见,它这里还没开始相加,就开辟了6段字符串对象,然后+起来又形成新的String对象,所以可以想象,当我们遇到大量(长度未可知且预知高于一定值的)字符串拼接时,会产生多少新的对象,对内存,性能造成不小的影响。
所以这时候就有了StringBuilder
StringBuilder
StringBuilder的目的就是为了解决String的不变量的问题的,StringBuilder在内部维护初始容量为16(可动态扩展)的对象,它是一个变量,所以它append字符串时返回的对象是同一个。所以存在大量的字符串拼接时,StringBuilder是可以明显优于String;
在JAVA SE5前,StringBuffer充当StringBuilder的角色,但是StringBuffer是线程安全的,细扣源码就会发现,里面含有大量的关键字synchronized,所以性能开销也比较大。
下面是StringBuilder的demo
public void UsingStringBuilder(){
var sb = new StringBuilder();
sb.append("abc").append("marson").append("shine")
.append("summer").append("zhu");
System.out.println(sb);
}
StringBuilder隐藏的陷阱
下面我们来学习《Java编程思想》一书中提到的一种StringBuilder场景,贴下代码:
public class InfiniteRecursion {
@Override
public String toString() {
return "InfiniteRecursion address: " + this + "\n";
}
public static void main(String[] args) {
var v = new ArrayList<InfiniteRecursion>();
for (int i = 0; i < 10; i++) {
v.add(new InfiniteRecursion());
}
System.out.println(v);
}
}
这种情况稍微不注意,就会犯下上面这段代码一样的错误——StackOverflowError
这是由于无限递归导致的堆栈内存溢出的错误,因为InfinitialRecursion类复写了toString,并且返回一个字符串+拼接操作符。尽管拼接的对象是this对象,但是由于是字符串的拼接,所以jvm会自动转型为String类型,从而再次调用toString,最后导致错误出现。
关于字符串池——intern
Java关于字符串对象,其实有一个装载字符串的容器——字符串池(pool of strings),新建的String对象,只要池中不存在,那么就可以存进去,并生成唯一个引用,当我们新建一个内容一样的字符串内容,我们可以直接引用池中的字符串对象,进而减小新建字符串带来的开销提高应用程序性能,而String的实例方法intern就是这个作用:
public class StringIntern {
public static void main(String[] args) {
var s = "MarsonShine";
var ss = new String("MarsonShine");
var sss = ss.intern();
System.out.println("s == ss: " + (s == ss));// false
System.out.println("s == sss: "+(s == sss));// true
System.out.println("ss == sss: "+(ss == sss));// false
}
}
String VS StringBuilder
最后我们来比较一下String与StringBuilder拼接字符串的性能对比来结束我们这个话题
public class StringVsStringBuilder {
private static final String INIT_STRING = "abcdefghijklmn1234567890";
public static void main(String[] args) {
var sw = new Stopwatch();
sw.start();
var str = "";
for (int i = 0; i < 100000; i++) {
str += INIT_STRING;
}
sw.end();
System.out.println("String + 运行时间:" + sw.ElapsedMilliseconds() + " ms");
sw.restart();
var sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append(INIT_STRING);
}
sw.end();
System.out.println("StringBuilder append 运行时间:" + sw.ElapsedMilliseconds() + " ms");
}
}
这个类里面分别用String,StringBuilder对定长的字符串对象INIT_STRING多次拼接
测试结果肯定也如大家所料,后者时间要远远小于前者的。但是当拼接的字符串比较少时,其差别就微乎其微了,理论上在少量字符串的拼接过程中,StringBuilder的性能是要逊色于String的,但是在我电脑上经过大量的测试,发现StringBuilder的性能始终要强与String的,我都有些怀疑是不是我Stopwatch辅助类写错了 - -;
最后我来把这个段代码附上吧
package performance;
public class Stopwatch {
private long startTime;
private long endTime;
public void start(){
startTime = System.currentTimeMillis();
}
public void end(){
endTime = System.currentTimeMillis();
}
public void restart(){
startTime = System.currentTimeMillis();
}
public long ElapsedMilliseconds(){
return endTime - startTime;
}
}
后记
因为我想弄清楚Java中的+操作符实际上是怎么调用的,运行的过程是怎么样的,是不是跟C#一样调用的是concat方法?
后来我通过反编译java代码发现string的+操作符在JVM变成了动态指令调用:
invokedynamic 指令去调用java.lang.invoke.makeConcatWithConstants方法,然后根据MethodHandler以及MethodType信息生成CallSite信息去执行具体的函数,但就CallSite调用那个过程我没搞清楚,调试断点也摸不清楚(Idiea玩不转 - -)
有了解的同学希望告诉我下^_^
集合的迭代器
任何集合都有迭代器。
任何集合类,都必须能以某种方式存取元素,否则这个集合容器就没有任何意义。
迭代器,也是一种模式(也叫迭代器模式)。在java中它是一个对象,其目的是遍历并选中其中的每个元素,而使用者(客户端)无需知道里面的具体细节。迭代器要足够的“轻量”——创建迭代器的代价小。所以看迭代器的源代码就会发现,里面会有很多要求:
- iterator方法返回一个Iterator,Iterator返回序列的头元素。
- next方法获取下一个元素
- hasNext检查还有元素
- remove删除迭代器新返回的元素
下面是迭代器的基本使用
public class UsingIterator {
public static void main(String[] args) {
List<String> names = Arrays.asList("marson", "shine", "summer", "zhu");
Iterator<String> it = names.iterator();
while(it.hasNext()){
String s = it.next();
print(s);
}
for (String s : names){
print(s);
}
System.out.println();
it = names.iterator();
for (int i = 0; i < 4; i++) {
it.next();
}
print(names);
}
}
ListIterator
ListIterator是一个更强大的Iterator子类型,能用于各种List类访问,前面说过Iterator支持单向取数据,ListIterator可以双向移动,所以能指出迭代器当前位置的前一个和后一个索引,可以用set方法替换它访问过的最后一个元素。我们可以通过调用listIterator方法产生一个指向List开始处的ListIterator,并且还可以用过重载方法listIterator(n)来创建一个指定列表索引为n的元素的ListIterator。
public class ListIteration {
public static void main(String[] args) {
var names = Arrays.asList("marson", "shine", "summer", "zhu");
var it = names.listIterator();
while (it.hasNext()) {
print(it.next() + ", " + it.nextIndex() + ", " + it.previousIndex() + "; ");
}
while (it.hasPrevious()) {
print(it.previous() + " ");
}
print(names);
it = names.listIterator(3);
while (it.hasNext()) {
it.next();
it.set("alias");
}
print(names);
}
}
输出结果为:
marson, 1, 0;
shine, 2, 1;
summer, 3, 2;
zhu, 4, 3;
zhu
summer
shine
marson
[marson, shine, summer, zhu][marson, shine, summer, alias]
Iterator模式
前面说了,迭代器又叫迭代器模式,顾名思义,只要符合这种模式都能叫迭代器模式,自然也能像前面一样使用迭代器
那么Iterator模式具体是个什么样子的模式呢?
我们通过Collection的源码发现其中的样子(为什么要看Collection而不是其他的List?因为Collection是所有容器的基类啊)
通过Collection代码我们发现它继承了一个叫Iterable<T>接口,注解说的很清楚——实现这个接口就说明这个对象是可迭代的;并且其成员函数也很清晰,只有三个方法
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action); //省略部分代码
default Spliterator<T> spliterator(); //省略部分代码
}
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
...
}
Iterator这个泛型接口才是我们真正实现迭代的核心,通过这些信息我们尝试来写一个迭代器
public class CustomIterator implements Iterable<String> {
protected String[] names = ("marson shine summer zhu").split(" ");
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public String next() {
return names[index++];
}
public void remove() {
}
};
}
public static void main(String[] agrs) {
for (var s : new CustomIterator()) {
print(s + " ");
}
}
}
到这里,自定义的迭代器就写完了,实际上我们只需要继承一个Iterable接口然后实现这个接口就行了,更深入的话,其实还可以自己写一个listIterator实现双向的操作数据
Java字符串String 集合的迭代器的更多相关文章
- Java字符串String
Java字符串String 我们知道Java的字符窜是Immutable(不可变)的,一旦创建就不能更改其内容了:平常我们对字符串的操作是最多的,其实对字符串的操作,返回的字符串都是新建的字符串对象, ...
- java 字符串和集合互相转换
今天在写项目的时候遇到一个问题,就是要把得到的一个集合转换成字符串,发现 import org.apache.commons.lang.StringUtils; 有这么一个简单的方法:String s ...
- java 字符串String
在 Java 中,字符串被作为 String 类型的对象处理. String 类位于 java.lang 包中.默认情况下,该包被自动导入所有的程序. 创建 String 对象的方法: 只要是双引号标 ...
- Java 字符串 String
什么是Java中的字符串 在 Java 中,字符串被作为 String 类型的对象处理. String 类位于 java.lang 包中.默认情况下,该包被自动导入所有的程序. 创建 String 对 ...
- java 字符串(String)常用技巧及自建方法模块汇总
1.String类常用方法汇总 (1)删除字符串的头尾空白符 public String trim() (2)从指定位置截取字符串 public String substring(int beginI ...
- Java字符串String类操作方法详细整理
关于String类的基本操作,可分为以下几类: 1.基本操作方法 2.字符串比较 3.字符串与其他数据类型之间的转换 4.字符与字符串的查找 5.字符串的截取与拆分 6.字符串的替换与修改 我觉得在整 ...
- [Java学习] Java字符串(String)
从表面上看,字符串就是双引号之间的数据,例如“微学苑”.“http://www.weixueyuan.net”等.在Java中,可以使用下面的方法定义字符串: String stringName = ...
- Java字符串(String)
从表面上看,字符串就是双引号之间的数据,例如“微学苑”.“http://www.weixueyuan.net”等.在Java中,可以使用下面的方法定义字符串: String stringName ...
- 老司机也晕车--java字符串String晕车之旅
首先声明,有晕车经历的司机请自备药物,String也可能让你怀疑人生! 第一道 开胃菜 请听题!第一道题: String hello="hello world!"; String ...
随机推荐
- 一个线程中lock用法的经典实例
/* 该实例是一个线程中lock用法的经典实例,使得到的balance不会为负数 同时初始化十个线程,启动十个,但由于加锁,能够启动调用WithDraw方法的可能只能是其中几个 作者:http://h ...
- JS(异步与单线程)
JS(异步与单线程) 题目1.同步和异步的区别是什么,试举例(例子见知识点) 区别: 1.同步会阻塞代码执行,而异步不会 2.alert 是同步,setTimeout 是异步 题目2.关于 setTi ...
- luogu2219 [HAOI2007]修筑绿化带
和「理想的正方形」比较相似,需要先掌握那道题. 花坛外头每一边必须套上绿化带 #include <iostream> #include <cstdio> using names ...
- [转]python 多线程threading简单分析
多线程和多进程是什么自行google补脑 对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的 ...
- Decorator(装饰器模式)
装饰器模式允许我们根据运行时不同的情景动态地为某个对象调用前后添加不同的行为动作. <?php class HtmlTemplate { // any parent class methods ...
- [adb 命令学习篇] adb 命令总结
https://testerhome.com/topics/2565 Android 常用 adb 命令总结 针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb ...
- POJ 3099 Go Go Gorelians
http://poj.org/problem?id=3099 树的重心:找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心 求树的重心 如何在点中构造符合条件的树 得到树后 从 ...
- elasticsearch起步
elasticsearch教程 elastic入门教程 阮一峰的elasticsearch教程 elasticsearch官网 kibana用户手册 elasticsearch安装步骤 参考:http ...
- 由String作为方法参数,引起的值传递,引用传递,及StringBuffer
原文引用: http://www.cnblogs.com/zuoxiaolong/p/lang1.html http://www.cnblogs.com/clara/archive/2011/09/1 ...
- AC日记——L国的战斗之间谍 洛谷 P1916
题目背景 L国即将与I国发动战争!! 题目描述 俗话说的好:“知己知彼,百战不殆”.L国的指挥官想派出间谍前往I国,于是,选人工作就落到了你身上. 你现在有N个人选,每个人都有这样一些数据:A(能得到 ...