2020年注定是不平凡的一年
今天也是四月初了
昨天
是我一个多月以来的第一次出门
仿佛是看到夏天的气息了
加上最近的感情不顺
一时有很多的感慨
于是
于是今天开始写一些技术文章,当然写文章也不是一时兴起,想来花费一些时间来把自己的心得、学会的知识写出来分享给大家也是一种总结,同时,也可以让大家指正出我理解的不对的地方,帮助不会的同学掌握新知识。
附一张简单的生活照 废话不说了我们进入正题
Java之父詹姆斯·高斯林说: 字符串,是Java中最重要的类
。所以他的重要性你就毋庸置疑了!
关于字符串,有很多的面试题,先看看面试题!
(1)String是否可以被继承?
不可以,因为String类似final类.
(2) String s = new String(“小顾”);创建了几个String Object?二者之间有什么区别?
两个或一个
1、如果String常量池中,已经创建"小顾",则不会继续创建,此时只创建了一个对象new String(“小顾”),此时为一个Obeject对象;
2、如果String常量池中,没有创建"小顾",则会创建两个对象,一个对象的值是"小顾",一个对象new String(“小顾”),此时为二个Obeject对象;
(3) String s = ”Hello”; s = s+”world!”;
这两句代码执行后,原始的String对象中的内容变没?
没有,因为String被设计成为不可变类,不能修改,继承,代码执行后,s所指向的对象”hello”没有改变.不过s不指向s,而是指向了另外一个String.原来的对象还在内存中,只是不指向了.
(4) Java的String,StringBuffer,StringBuilder有什么区别?
1.可变与不可变
String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。
2.是否多线程安全
String中的对象是不可变的,也就可以理解为常量,显然线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
三者的继承结构图
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。
(5) 如何将字符串转化成int?
使用包装类Integer。其他基本数据类型都是类似
前提是该字符的数据满足该类型的要求!
String str = "123"; int s =Integer.parseInt(str); System.err.println(s);
(6) String可以在Switch语句中使用吗?
在Java 7 以后,switch语句可以用作String类型上。
从本质来讲,switch对字符串的支持,其实也是int类型值的匹配。它的实现原理如下:
通过对case后面的String对象调用hashCode()方法,得到一个int类型的Hash值,然后用这个Hash值来唯一标识着这个case。
那么当匹配的时候,首先调用这个字符串的hashCode()方法,获取一个Hash值(int类型),用这个Hash值来匹配所有的case,
如果没有匹配成功,说明不存在;如果匹配成功了,接着会调用字符串的equals()方法进行匹配。
由此看出,String变量不能是null;同时,switch的case子句中使用的字符串也不能为null。
(7) 比较两个字符串时使用“==”还是equals()方法?
当然是equals方法。
“==”测试的是两个对象的引用是否相同,而equals()比较的是两个字符串的值是否相等。简单来说,基本数据类型都可以使用==。而引用类型使用==比较不了。
(8) 如何将字符串转换成时间Date?
使用SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date =sdf.parse("2017-12-10");
String s = "2008-08-08 20:08:08";SimpleDateFormat s1= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date dat= s1.parse(s);
(9) 计算一个字符串某个字符的出现次数。
a. 使用charAt方法截取之后,循环判断.
b. 使用apache commons lang包中的StringUtils:
String str="Happy New Year!"; int count = 0; for(int i =0;i if(str.charAt(1) == str.charAt(i)) { count++; } } System.out.println(count);
int n = StringUtils.countMatches("ababababab", "a");System.out.println(n);
(10) 如何使一个字符串重复N次。
API提供了一个非常好的方法。String str = "ab";
String repeated = StringUtils.repeat(str,3);
//输出的结果是ababab,三次重复.
(11) 编写程序将由数字及字符组成的字符串中的数字截取出来被按顺序输出,例如:“ABC137GMNQQ2049PN5FFF”输出结果应该为01234579 ?
public static void main(String[] args) { String str = "ABC137GMNQQ2049PN5FFF"; StringBuilder stringBuilder = new StringBuilder() ; for(int i=0;i char c = str.charAt(i); if(c >= '0' && c<= '9') { stringBuilder. append(c) ; } } char[] ary = stringBuilder.toString().toCharArray() ; Arrays.sort(ary) ; System.out.println(ary); }
(12) String s = "abcdefghijklmnopqrstuvwxyz" ;编写-段程序,实现”mnop"输出?
public static void main(String[] args) { String s ="abcdefghijklmnopqrstuvwxyz"; int a = s.indexOf("mnop"); int n = "mnop".length(); String st =s.substring(a,a+n); System.out.println(st); }
深入理解Java中的String
先来认识一下字符串中的常用方法
(这些是作为java程序员必会的工作中会用、面试中会问,所以你懂)
public static void main(String[] args) { String str="Happy New Year!"; //indexOf 从指定位置处查找字符出现的位置 int a =str.indexOf("N"); //lastindexof 查找最后一次出现给定字符的位置 int b = str.lastIndexOf("a"); //length 返回字符串的长度 int c = str.length(); //charAt 获取字符串指定位置处的字符 char d = str.charAt(1); //trim 去除字符串两边的空白 String e = str.trim(); //toUpperCase toLowerCase //转换为全大写或转换为全小写 String f1 = str.toLowerCase(); String f2 = str.toUpperCase(); System.out.println(f1+":"+f2); //subString 截取指定位置的字符串(含头不含尾) String substring = str.substring(0, 2); System.out.println(substring); //boolean startwith 判读字符串是否以给定的字母开头 //boolean endwith 或者结尾 boolean start = str.startsWith("H"); System.out.println(start); boolean end = str.endsWith("r"); System.out.println(end); }
想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码:
public final class String implements java.io.Serializable, Comparable, CharSequence{ /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; ........}
从上面可以看出几点:
1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
2)上面列举出了String类中所有的成员属性,从上面可以看出String类其实是通过char数组来保存字符串的。
下面再继续看String类的一些方法实现:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value);}public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } char buf[] = new char[count + otherLen]; getChars(0, count, buf, 0); str.getChars(0, otherLen, buf, count); return new String(0, count + otherLen, buf);}public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = count; int i = -1; char[] val = value; /* avoid getfield opcode */ int off = offset; /* avoid getfield opcode */ while (++i < len) { if (val[off + i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0 ; j < i ; j++) { buf[j] = val[off+j]; } while (i < len) { char c = val[off + i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(0, len, buf); } } return this;}
从上面的三个方法可以看出,无论是sub操、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。
在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
二、字符串常量池
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
来看下面的程序:
String a = "chenssy";String b = "chenssy";
a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"对象,他们指向同一个对象。
String c = new String("chenssy");
new关键字一定会产生一个对象chenssy(注意这个chenssy和上面的chenssy不同),同时这个对象是存储在堆中。所以上面应该产生了两个对象:保存在栈中的c和保存堆中chenssy。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的chenssy应该是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的关系应该是:c--->chenssy--->池chenssy。整个关系如下:
通过上面的图我们可以非常清晰的认识他们之间的关系。所以我们修改内存中的值,他变化的是所有。
总结:虽然a、b、c、chenssy是不同的对象,但是从String的内部结构我们是可以理解上面的。String c = new String("chenssy");虽然c的内容是创建在堆中,但是他的内部value还是指向JVM常量池的chenssy的value,它构造chenssy时所用的参数依然是chenssy字符串常量。
最后认识一下Java对String的优化
String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一下String对象的优化过程。
1. 在 Java6 以及之前的版本中
String对象是对 char 数组进行了封装实现的对象,主要有四个成员变量:char 数组、偏移量 offset、字符数量 count、哈希值 hash。
String对象是通过 offset 和 count 两个属性来定位 char[] 数组,获取字符串。这么做可以高效、快速地共享数组对象,同时节省内存空间,但这种方式很有可能会导致内存泄漏。
2. 从 Java7 版本开始到 Java8 版本
从 Java7 版本开始,Java 对String类做了一些改变。String类中不再有 offset 和 count 两个变量了。这样的好处是String对象占用的内存稍微少了些,同时 String.substring 方法也不再共享 char[],从而解决了使用该方法可能导致的内存泄漏问题。
3. 从 Java9 版本开始
将 char[] 数组改为了 byte[] 数组,为什么需要这样做呢?我们知道 char 是两个字节,如果用来存一个字节的字符有点浪费,为了节约空间,Java 公司就改成了一个字节的byte来存储字符串。这样在存储一个字节的字符是就避免了浪费。
在 Java9 维护了一个新的属性 coder,它是编码格式的标识,在计算字符串长度或者调用 indexOf() 函数时,需要根据这个字段,判断如何计算字符串长度。coder 属性默认有 0 和 1 两个值, 0 代表Latin-1(单字节编码),1 代表 UTF-16 编码。如果 String判断字符串只包含了 Latin-1,则 coder 属性值为 0 ,反之则为 1。