您的位置:首页 > 理论基础 > 数据结构算法

【数据结构】串(String、StringBuilder、StringBuffer)的JAVA代码实现

2018-03-24 20:20 405 查看
串即字符串,是由0或多个字符组成的有限序列,是数据元素为单个字符的特殊线性表。
串从数据结构上来说是一种特殊的线性表,其特殊性在于串中的数据元素是一个个的字符。但是,串的基本操作和线性表的基本操作相比却有很大的不同,线性表的操作主要是针对线性表的某个数据元素进行的,而串上的操作主要是针对串的整体或串的某一部分子串进行的。
本文主要介绍Java中的字符串类String、StringBuilder、StringBuffer。

串的抽象数据类型

数据元素:只能是字符类型;
数据结构:数据元素之间呈线性关系;
数据操作:对串的基本操作定义在IString中,代码如下:
public interface IString {
int length();//求串长度
IString concat(IString str);//串的连接
int compareTo(IString anotherString);//串的比较
IString substring(int start,
4000
int end);//求子串
int indexOf(IString str, int fromIndex);//串定位
IString append(String str);//串附加
IString delete(int start, int end);//串删除
IString insert(int offset, IString str);//串插入
}

Java中的字符串类String

先看一下String类的声明public final class String
implements java.io.Serializable, Comparable<String>, CharSequence

String类的存储结构

String类采用顺序存储结构,使用字符数组保存字符串,字符数组的长度与字符串中字符的个数相同。String类中的保存的字符串数组空间一旦创建,内容就无法改变了。String类源码中存储字符串内容的数组value为final的,代码如下:private final char value[];Java在创建字符串时,使用不同的创建方式,表示字符串的数组也会在不同的内存区域创建。这里,需要理解有关内存分配的三个术语:

栈:由Java虚拟机分配的用于保存线程执行的动作和数据引用的内存区域。栈是一个运行的单位,Java中一个线程就会有一个与之对应的栈。这里只会存放表示字符串的数组的引用。
堆:由Java虚拟机分配的用于存储对象的内存区域。通过显示调用构造函数的方式来创建字符串对象,此时表示字符串的数组会存放在这个区域。
常量池:在编译的阶段,在堆中分配出来的一块存储区域,用于存放基本类型常量和显示声明的字符串的对象。
总之:在Java中,字符串对象的引用是存储在栈中的;如果是编译期已经创建好,即指用双引号定义的字符串的存储在常量池中;如果是运行期出来的对象,则存储在堆中。对于通过equals方法比较相等的字符串,在常量池中是只有一份的,在堆中则是有多份。

String类的构造函数

在Java中创建String字符串,是通过调用String字符串类的构造函数来实现的。String常用的构造函数有下面三个: public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}创建一个新的字符串,使其表示字符数组参数中当前包含的字符序列。函数实现中的Arrays.copyOf(value, value.length)完成字符数组的复制,并使得复制的数组具有value.length的长度,实现新数组与参数数组内容相同。
在实际的开发中,很少有直接使用该构造函数创建字符串对象,而使用类似String str="abc"比较多,查看String类的源码文档,对其解释为:
String str="abc";
等价于
char data[]={'a','b','c'};
String str=new String(data);
相当于在编译时,自动调用String(char value[])构造函数在常量池中创建了常量字符串对象"abc"。但由于并没有显示调用构造函数,在堆中并没有存放,只有常量池中独有。 public String(String original) {
this.value = original.value;
this.hash = original.hash;
}初始化一个新创建的String对象,使其表示一个与参数相同字符序列。在String str=new String("abc")中,就是使用了该构造函数。相当于在编译时,Java虚拟机会先去常量池中查找是否有"abc"对象,如果没有则在常量池中创建一个,然后再堆中也创建一个"abc"对象,并将str指向它;如果常量池中有则直接去堆中创建。 public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}创建一个新的String,它包含取自字符数组参数一个子数组的字符。offset参数指定子数组第一个字符的索引,count表示长度。函数实现中的Arrays.copyOfRange(value, offset, offset+count)完成子字符数组的复制。

String类的基本操作

public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
System.arraycopy(str.value, 0, buf, len, str.value.length);
return new String(buf, true);
}
串连接函数concat(),Array.copyOf(value,len+otherLen)创建一个长度为两个字符串长度之和的新数组buf,并将value复制到新数组的开头,剩余空间用null字符填充。 public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}其实,Array.copyOf()的内部实现也是通过System.arraycopy(src,srcPos,dest,destPos,length)函数实现的。这个函数参数的意义就是从src的srcPos位置复制长度length到dest的destPos位置。 public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;

int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}串比较函数compareTo(),基于字符串中各个字符的Unicode值进行比较,通过Math.min来计算出字符串中较短的长度。 public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}求子串函数subString(),首先确认index的合法性,再通过调用构造函数String(char[],offset,count)来完成。 public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}

char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);

for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}

/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);

if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
串定位函数indexOf(),从指定的索引处开始返回指定参数字符串在此字符串中第一次出现处的索引,串定位也称为模式匹配。首先确认index的合法性,然后根据首字符来寻找地址接上后续字符的匹配。

Java中的字符串类StringBuilder和StringBuffer

由于String字符串是常量字符串,不方便进行插入和删除操作,在对字符串进行插入和删除操作时,通常使用StringBuilder和StringBuffer类。
StringBuilder和StringBuffer类都继承自AbstractStringBuilder类,有相同的属性和方法,可以向其中插入或删除字符,它们是可变字符串。两个类主要的区别是StringBuffer对方法加了同步锁或对调用的方法加了同步锁,是线程安全的,而StringBuilder是非线程安全的。下面以StringBuilder为例看可变字符串类。

StringBuilder类的存储结构

先看一下StringBuilder类的声明public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequenceStringBuilder和String类一样使用字符数组保存字符串,但数组的内容是可变的,保存在StringBuilder字符串的字符数组定义在AbstractStringBuilder中,代码如下: char[] value;

StringBuilder类的构造函数

在Java中创建StringBuilder字符串,是通过调用StringBuilder字符串类的构造函数实现的。StringBuilder常用的构造函数有三个: public StringBuilder() {
super(16);
}StringBuilder()调用父类侍卫构造函数创建一个不带任何字符的字符串生成器,其初始容量为16个字符。在使用的过程中,如果超过这个值,就会重新分配内存,创建一个更大的数组,并将原来的数组复制,再丢弃旧的数组。下面是扩大数组容量的方法: void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {            //溢出
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}我们可以看到,数组的最大容量是Integer.MAX_VALUE。 public StringBuilder(int capacity) {
super(capacity);
}StringBuilder(capacity)构造一个不带任何字符的字符串生成器,其初始容量由capacity参数指定。 public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}StringBuilder(str)构造一个不带字符串生成器,并初始化为指定字符串内容,其初始容量是16加上字符串参数的长度

StringBuilder类的基本操作

public StringBuilder append(String str) {
super.append(str);
return this;
}
串附加函数append(),将指定字符串str追加到此字符序列。如果str为null,则追加4个字符"null"
该方法调用了父类AbstractStringBuilder的append()方法,具体实现是: public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}在此方法中使用的getChars(srcBegin,srcEnd,dst[],destBegin)函数底层,也同样还是采用System.arraycopy(src,srcPos,dest,destPos,length)函数实现的。 public StringBuilder insert(int offset, String str) {
super.insert(offset, str);
return this;
}串插入函数insert(),按顺序将String参数中的字符插入此序列中的指定位置offset处,将此位置处原来的字符向后推,此序列将增加该参数的长度。如果str为null,则追加4个字符"null"。
该方法调用了父类AbstractStringBuilder的insert()方法,具体实现是:
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
System.arraycopy(value, offset, value, offset + len, count - offset);
str.getChars(value, offset);
count += len;
return this;
}本质上还是采用System.arraycopy(src,srcPos,dest,destPos,length)函数实现的,只不过首先要检验index的合法性。 public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}串删除函数delete(),删除此序列字符串中从指定的start处开始到索引end-1处的字符,如果不存在这种字符,则一直到序列尾部。如果start等于end,则不做更改。
该方法调用了父类AbstractStringBuilder的delete()方法,具体实现是:
public AbstractStringBuilder delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}本质上还是采用System.arraycopy(src,srcPos,dest,destPos,length)函数实现的,只不过首先要检验index的合法性。在检验end的合法性的时候,如果大于字符串长度count,会将end改成count,而不会报错
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: