龙空技术网

String类解析 (三)方法解析

Java刚好有空 187

前言:

如今各位老铁们对“java获取字符串倒数第二个字符怎么写”大体比较关切,大家都需要知道一些“java获取字符串倒数第二个字符怎么写”的相关内容。那么小编在网上搜集了一些对于“java获取字符串倒数第二个字符怎么写””的相关资讯,希望我们能喜欢,我们快快来学习一下吧!

本次不考虑已被标记为过时的方法

//String 的长度就是字符数组的长度public int length() { return value.length;}//字符数组长度为0时表示空字符串public boolean isEmpty() { return value.length == 0;}//返回String对象index位置上的元素public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index];}//返回String对象index位置上的元素的ASCII码public int codePointAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointAtImpl(value, index, value.length);}//返回String对象index位置上前一个元素的ASCII码public int codePointBefore(int index) { int i = index - 1; if ((i < 0) || (i >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointBeforeImpl(value, index, 0);}//返回字符数 非 Unicode 时和length没有区别 //例如 String s = "/uD835/uDDA0";(即 'A' ) //此时 length()=2 ,codePointCount()= 1public int codePointCount(int beginIndex, int endIndex) { if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) { throw new IndexOutOfBoundsException(); } return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);}//相对unicode字符集 从index位置算起 偏移codePointOffset个位置,//返回偏移后的位置,//eg String s = "hello"; s.offsetByCodePoints(2,3)=5 public int offsetByCodePoints(int index, int codePointOffset) { if (index < 0 || index > value.length) { throw new IndexOutOfBoundsException(); } return Character.offsetByCodePointsImpl(value, 0, value.length, index, codePointOffset);}

getChars

//dst 复制到那个数组//dstBegin 从dst的dstBegin位置开始复制//eg char c[] = new char[10];// "abcdef".getChars(c,2);//结果 c:空空abcdef空空//此方法只能同包访问,不对外void getChars(char dst[], int dstBegin) { System.arraycopy(value, 0, dst, dstBegin, value.length);}//和上面方法类似//srcBegin 本字符串的开始位置//srcEnd 本字符串的结束位置public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}

getBytes

//按照字符集名称获取byte数组public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException(); return StringCoding.encode(charsetName, value, 0, value.length);}//按照字符集类获取byte数组public byte[] getBytes(Charset charset) { if (charset == null) throw new NullPointerException(); return StringCoding.encode(charset, value, 0, value.length);}//按照当前平台默认字符集获取byte数组public byte[] getBytes() { return StringCoding.encode(value, 0, value.length);}

equals 比较类方法

//为重写Object类方法 故 参数没有设置为String类型的public boolean equals(Object anObject) { if (this == anObject) {  return true; //比较引用 引用相同 则表示同一对象  } if (anObject instanceof String) { //判断是否为 String类型 String anotherString = (String)anObject; //转换为String类型 int n = value.length; if (n == anotherString.value.length) { //先判断字符串长度是否相等 char v1[] = value; char v2[] = anotherString.value; int i = 0; //循环的游标 while (n--!= 0) { //每次循环长度-1 直到为0 if (v1[i] != v2[i]) //同位置一一比较 return false; //发现不同返回false 循环结束 i++; } return true;  } } return false;}//为比较StringBuffer提供public boolean contentEquals(StringBuffer sb) { return contentEquals((CharSequence)sb);}//主要用于比较StringBuffer与StringBuilder String CharSequence//String StringBuffer StringBuilder 都实现的CharSequence接口public boolean contentEquals(CharSequence cs) { // Argument is a StringBuffer, StringBuilder if (cs instanceof AbstractStringBuilder) {  //AbstractStringBuilder 为 StringBuffer 及 StringBuilder的父类 if (cs instanceof StringBuffer) { //如果属于StringBuffer  synchronized(cs) { //锁定比较对象 进入同步块 调用非同步比较方法 return nonSyncContentEquals((AbstractStringBuilder)cs); } } else { //StringBuilder 直接调用非同步比较方法 return nonSyncContentEquals((AbstractStringBuilder)cs); } } // Argument is a String if (cs instanceof String) { //如果是String类型调用equals return equals(cs); } // Argument is a generic CharSequence  //和equals类似 先比较长度  // 但由于CharSequence 为接口 当中并没有定义属性  //所以使用length方法及charAt方法获取长度及坐标字符 char v1[] = value; int n = v1.length; if (n != cs.length()) { return false; } for (int i = 0; i < n; i++) { if (v1[i] != cs.charAt(i)) { return false; } } return true;}//和equals类似 不说明了 只是缺少一些判断private boolean nonSyncContentEquals(AbstractStringBuilder sb) { char v1[] = value; char v2[] = sb.getValue(); int n = v1.length; if (n != sb.length()) { return false; } for (int i = 0; i < n; i++) { if (v1[i] != v2[i]) { return false; } } return true;}//不区分大小写比较public boolean equalsIgnoreCase(String anotherString) { //三目运算  return (this == anotherString) ? //true 判断引用 : (anotherString != null)  && (anotherString.value.length == value.length) 判断长度 && regionMatches(true, 0, anotherString, 0, value.length);  //调用 regionMatches 方法 进行比较}//该方法作用类似于equals方法 与之不同的是可以比较部分字符串//toffset 当前字符串起始位置//other 要比较的字符串//ooffset 要比较字符串起始位置//len 比较长度public boolean regionMatches(int toffset, String other, int ooffset, int len) { char ta[] = value; int to = toffset; char pa[] = other.value; int po = ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. // 对数据进行校验  if ((ooffset < 0) || (toffset < 0) || (toffset > (long)value.length - len) || (ooffset > (long)other.value.length - len)) { return false; } while (len-- > 0) { if (ta[to++] != pa[po++]) { return false; } } return true;}//和上面一个方法差不多 多了一个是否忽略大小写//有些不理解为什么上面不直接设置false 然后调用此方法//猜测可能是转换大小写比较耗时间吧(如果有知道的小伙伴可以私信或者留言联系我)public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { char ta[] = value; int to = toffset; char pa[] = other.value; int po = ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > (long)value.length - len) || (ooffset > (long)other.value.length - len)) { return false; } while (len-- > 0) { char c1 = ta[to++]; char c2 = pa[po++]; if (c1 == c2) { continue; } if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. //如果字符不匹配但大小写可以忽略, //尝试将两个字符都转换成大写。 //如果结果匹配,则继续进行比较扫描 char u1 = Character.toUpperCase(c1); char u2 = Character.toUpperCase(c2); if (u1 == u2) { continue; } // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. //不幸的是,转换成大写无法正常工作 //对于格鲁吉亚字母,它有奇怪的大小写转换规则 //所以我们需要在离开之前做最后一次检查 if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { continue; } } return false; } return true;}

compareTo类方法

//因为String实现了Comparable接口,需要重写compareTo方法进行//返回的int类型,正数为大,负数为小,是基于字符的ASSIC码比较的//该方法是从高位到低位比较的,也可以简单的理解为安装首字母排序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;} //私有内部类,单例模式 public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();//不区分大小写的比较 //使用了恶汉单例模式 没有线程安全问题private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; //和之前方法类似 比较时先比较大写然后比较小写 public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } /** Replaces the de-serialized object. */ private Object readResolve() {  return CASE_INSENSITIVE_ORDER;  }}public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str);}

为什么String已经有一个compareTo方法了,还需要一个静态内部类再实现compare,首先两者的功能并不一样,compareTo 实现的仅仅是比较,而内部类实现的是忽略大小比较。这时我们会立刻想到方法的重载。 可是别忘了 compareTo 是从 Comparable 接口中重写的方法,排序比较时他只能识别compareTo (String str),所以我们只能使用Comparetor接口,使用时必须创建个其他类去实现Comparetor接口,并重写compare方法。所以出现了String当中的静态内部类,其实他就是一个比较器,而且使用了恶汉单例模式 没有线程安全问题,并且 compareToIgnoreCase 方法的实质也是调用的compare方法,使得代码得到了复用

startWith、endWith

//prefix 表示前缀 toffset 表示偏移l量 即从第几位开始public boolean startsWith(String prefix, int toffset) { char ta[] = value; //获得当前对象的值 int to = toffset; //获得需要判断的起始位置,偏移量 char pa[] = prefix.value; //获得前缀字符串的值 int po = 0; int pc = prefix.value.length; // Note: toffset might be near -1>>>1. if ((toffset < 0) || (toffset > value.length - pc)) { //偏移量不能小于0且能截取pc个长度 return false; //不能则返回false } //从开始处逐个比较每一位置上的值 while (--pc >= 0) { //循环pc次,既prefix的长度 if (ta[to++] != pa[po++]) { //每次比较当前对象的字符串的字符是否跟prefix一样 return false; //一样则pc--,to++,po++,有一个不同则返回false } } return true; //没有不一样则返回true,当前对象是以prefix在toffset位置做为开头 }public boolean startsWith(String prefix) { return startsWith(prefix, 0);} public boolean endsWith(String suffix) { //suffix是需要判断是否为尾部的字符串。 //倒数suffix.length 处查看是否以 suffix 字符串开始 //value.length - suffix.value.length是suffix在当前对象的起始位置 return startsWith(suffix, value.length - suffix.value.length); }

hashCode

public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h;}

为什么使用31而不选用其他数字呢 Effective Java 中是这样说的

之所以选择31,是因为它是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。在存储数据计算hash地址的时候,我们希望尽量减少有同样的hash地址,所谓“冲突”。当比较字符串是否相同时,会首先比较hashCode,如果相同再调用equals方法表具体内容,如果使用相同hash地址的数据过多,那么会增加调用equals方法次数,从而降低了查询效率!在hashMap中增加了“碰撞”的概率,使得map中某一条链表过长,会降低map的查询效率

indexOf

public int indexOf(int ch) { return indexOf(ch, 0);}//从 fromIndex 位置开始查找第一次出现 ch 的位置 //存在返回目标位置,不存在返回-1public int indexOf(int ch, int fromIndex) { //获取字符串长度 为什么使用final  //个人猜测 防止字符串引用改变 导致下次获取到的长度不同 因为一旦发生改变容易 出现数组下标异常 //但是自己十分不自信 有懂的朋友麻烦私信告诉我 final int max = value.length; //如果偏移量小于0,则代表偏移量为0,校正偏移量 if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= max) { // Note: fromIndex might be near -1>>>1. return -1; } //在Character类中 16进制数0x010000 为10进制中2的16次方即两个字节 //但是因为int可以存放四个字节的数据 所以才有了下面的比较 过滤掉一些编码 // public static final int MIN_SUPPLEMENTARY_CODE_POINT = 0x010000; if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { //在这里处理大多数情况 (包括BPM 与 无效代码点) // handle most cases here (ch is a BMP code point or a // negative value (invalid code point)) final char[] value = this.value; for (int i = fromIndex; i < max; i++) { if (value[i] == ch) { return i; } } return -1; } else { return indexOfSupplementary(ch, fromIndex); }}private int indexOfSupplementary(int ch, int fromIndex) { //确定指定的代码点是否是一个有效的Unicode代码点值 if (Character.isValidCodePoint(ch)) { final char[] value = this.value; //获取字符的高位 final char hi = Character.highSurrogate(ch); //获取字符的低位 简言之就是将 四个字节 拆开为两个两字节 final char lo = Character.lowSurrogate(ch); final int max = value.length - 1; for (int i = fromIndex; i < max; i++) { if (value[i] == hi && value[i + 1] == lo) { return i; } } } return -1;}//方法逐级调用 都是返回要检测的字符位置//默认都是将字符串转为 char[] 处理public int indexOf(String str) { return indexOf(str, 0);}public int indexOf(String str, int fromIndex) { return indexOf(value, 0, value.length, str.value, 0, str.value.length, fromIndex);}//这是一个不对外公开的静态函数//source就是原始字符串,sourceOffset就是原始字符串的偏移量,起始位置。// sourceCount就是原始字符串的长度,target就是要查找的字符串。// fromIndex就是从原始字符串的第fromIndex开始遍历static int indexOf(char[] source, int sourceOffset, int sourceCount, String target, int fromIndex) { return indexOf(source, sourceOffset, sourceCount, target.value, 0, target.value.length, fromIndex);}//targetOffset 查找字符串开始w位置 和 targetCount 表示查找字符串长度static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { if (fromIndex >= sourceCount) { //如果查找的起点大于当前对象的大小 //如果目标字符串的长度为0,则代表目标字符串为"",""在任何字符串都会出现 //配合fromIndex >= sourceCount,所以校正第一次出现在最尾部,仅仅是校正作用 return (targetCount == 0 ? sourceCount : -1);  } if (fromIndex < 0) { //也是校正,如果起始点小于0,则返回0 fromIndex = 0; } if (targetCount == 0) { //如果目标字符串长度为0,代表为"",则第一次出现在遍历起始点fromIndex 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; //原字符结束位置 //逐个比较目标字符串和次字符串 //k 为目标字符串 要检索的位置 下一个位置 for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++); //查找完整个字符串 if (j == end) { /* Found whole string. */ return i - sourceOffset; } } } return -1; }//是否含有CharSequence这个子类元素,通常用于StrngBuffer,StringBuilderpublic boolean contains(CharSequence s) { return indexOf(s.toString()) > -1;}

lastIndexOf 与 indexOf类似 就不做详细讲解了

substring

// beginIndex 开始位置 endIndex 结束位置// 本质是通过构造器实现的// 每次使用后都会 new 新的String对象publicString substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);}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);}

replace

//替换,将字符串中的oldChar字符全部替换成newChar//从replace的算法中,我们可以发现,它不是从头开始遍历替换的,//而是首先找到第一个要替换的字符,重新创建一个新的char[],//一边复制原字符一边替换原字符串//因为一开始为引用传递 直接修改会破坏String的不可变性public String replace(char oldChar, char newChar) { if (oldChar != newChar) { //如果旧字符不等于新字符的情况下 int len = value.length; //获得字符串长度 int i = -1; //flag char[] val = value; /* avoid getfield opcode 避免getfield操作码*/ // 此位置为引用传递 如果修改 val 原字符串也会修改 while (++i < len) { //循环len次 if (val[i] == oldChar) { //找到第一个旧字符,打断循环 break; } } if (i < len) { //如果第一个旧字符的位置小于len char buf[] = new char[len]; // 新new一个字符数组,len个长度 for (int j = 0; j < i; j++) { buf[j] = val[j]; // 把旧字符的前面的字符都复制到新字符数组上 } while (i < len) { //从i位置开始遍历 char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; //发生旧字符就替换,不想关的则直接复制 i++; } return new String(buf, true); //通过新字符数组buf重构一个新String对象 } } return this; //如果old = new ,直接返回自己 }//Pattern类和Matcher类 为Java正则表达式的处理 我没有了解过 不做说明了//替换第一个旧字符String replaceFirst(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceFirst(replacement); }//当不是正规表达式时,与replace效果一样,都是全体换。如果字符串的正则表达式,则规矩表达式全体替换public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement);}//可以用旧字符串去替换新字符串public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString()));}public boolean matches(String regex) { return Pattern.matches(regex, this); //实际使用的是Pattern.matches()方法}

join

/** * join方法是JDK1.8加入的新函数,静态方法 * 这个方法就是跟split有些对立的函数,不过join是静态方法 * delimiter就是分割符,后面就是要追加的可变参数,比如str1,str2,str3 *  * 例子:String.join(",",new String("a"),new String("b"),new String("c")) * output: "a,b,c" */  public static String join(CharSequence delimiter, CharSequence... elements) { Objects.requireNonNull(delimiter); //就是检测是否为Null,是null,抛异常 Objects.requireNonNull(elements); //不是就返回自己,即nothing happen // Number of elements not likely worth Arrays.stream overhead. StringJoiner joiner = new StringJoiner(delimiter); //嗯,有兴趣自己看StringJoiner类源码啦 for (CharSequence cs: elements) { joiner.add(cs); //既用分割符delimiter将所有可变参数的字符串分割,合并成一个字符串 } return joiner.toString(); }  /** * 功能是一样的,不过传入的参数不同 * 这里第二个参数一般就是装着CharSequence子类的集合 * 比如String.join(",",lists)  * list可以是一个Collection接口实现类,所含元素的基类必须是CharSequence类型 * 比如String,StringBuilder,StringBuffer等 */  public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements) { Objects.requireNonNull(delimiter); Objects.requireNonNull(elements); StringJoiner joiner = new StringJoiner(delimiter); for (CharSequence cs: elements) { joiner.add(cs); } return joiner.toString(); }

trim

/** * 去除字符串首尾部分的空值,如,' ' or " ",非"" * 原理是通过substring去实现的,首尾各一个指针 * 头指针发现空值就++,尾指针发现空值就-- * ' '的Int值为32,其实不仅仅是去空的作用,应该是整数值小于等于32的去除掉 */publicString trim() { int len = value.length; //代表尾指针,实际是尾指针+1的大小 int st = 0; //代表头指针 char[] val = value; /* avoid getfield opcode */ //st<len,且字符的整数值小于32则代表有空值,st++ //去掉头部空格 while ((st < len) && (val[st] <= ' ')) {  st++; } //len - 1才是真正的尾指针,如果尾部元素的整数值<=32,则代表有空值,len-- //去掉尾部空格 while ((st < len) && (val[len - 1] <= ' ')) { len--; } //截取st到len的字符串(不包括len位置) return ((st > 0) || (len < value.length)) ? substring(st, len) : this;}

toString

 //就是返回自己 public String toString() { return this; }

toCharArray

 /** * 就是将String转换为字符数组并返回 */public char[] toCharArray() { // Cannot use Arrays.copyOf because of class initialization order issues //不能使用 Arrays.copyOf 因为类初始化顺序的问题 char result[] = new char[value.length]; //定义一个要返回的空数组,长度为字符串长度 System.arraycopy(value, 0, result, 0, value.length); //拷贝 return result; //返回}

toLowerCase()、toUpperCase

这两个函数真真的我没想到 我一开始以为是循环对字母范围进行判断 然后加减32 没想到很复杂 说实话有点没看懂 等有机会研究研究

valueOf 与 copyValueOf

就直引用了

我觉得你最大的疑问应该在char数组上吧?比如

char[] test = new char[]{‘a’,‘b’,‘c’};

String a = String.valueOf(test);

String b = String.copyValueOf(test);

有什么区别?答案:没有任何区别!

首先你得知道,String的底层是由char[]实现的:通过一个char[]类型的value属性!早期的String构造器的实现呢,不会拷贝数组的,直接将参数的char[]数组作为String的value属性。然后 test[0] = ‘A’;

将导致字符串的变化。为了避免这个问题,提供了copyValueOf方法,每次都拷贝成新的字符数组来构造新的String对象。但是现在的String对象,在构造器中就通过拷贝新数组实现了,所以这两个方面在本质上已经没区别了。

请问String类的CopyValueOf 和ValueOf有什么不同吗?

标签: #java获取字符串倒数第二个字符怎么写