Java引用类型(String)

目录

String解析

final的作用

String是否有长度限制

StringBuffer解析

StringBuilder解析

关键字、操作类相关


引用数据类型非常多大致包括:类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型。String类型就是引用类型。

String解析

JVM运行时会分配一块空间给String,字符串的分配和其他对象分配一样,需要消耗高昂的时间和空间,JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化,使用字符串常量池,创建字符串常量时,JVM先检查字符串常量池中有没有,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。String类是由final关键字修饰的,字符串具有不可变性,常量池中不会存在两个相同的字符串。

      public class App {public static void main(String[] args) {String a = "111";a = "222";System.out.println(a);}}

引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。所以上面String a = “111”,表达的是变量a里保存了“111”这个对象的引用地址,变量是可以变的,不能变的是“111”。a="222",先去JVM常量池中查找,如果常量池中存在,就直接把对象的引用地址赋给a,如果不存在就重新创建一个对象,然后把对象的引用地址赋给a。

final的作用

当用final修饰一个类时,表明这个类不能被继承。final修饰的类中的成员变量可以根据需要设为final(类中所有成员方法都会被隐式地指定为final方法)。final修饰的方法表示此方法已经是“最后的、最终的”含义,即此方法不能被重写,但可以重载。重写的前提是子类可以从父类中继承此方法,如果父类中final修饰方法同时访问控制权限为private,会导致子类中不能直接继承到此方法,此时可以在子类中定义相同的方法名和参数(类的private方法会隐式地被指定为final方法)。当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化。如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。另外final修饰一个成员变量必须要初始化。有两种初始化方式(在声明的时候给其赋值,在其类的所有构造方法中为其赋值)

    public class FinalDemo {private final String name;//1public FinalDemo(String name) {//2this.name = name;//3}public FinalDemo() {//4}}

1处不会通过编译,要先给name初始化值才可以通过编译。

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);str.getChars(buf, len);//看到了吗?返回的居然是新的String对象return new String(buf, true);}void getChars(char dst[], int dstBegin) {System.arraycopy(value, 0, dst, dstBegin, value.length);}    public String replace(char oldChar, char newChar) {//如果两个是一样的,那就必要替换了,所以返回thisif (oldChar != newChar) {int len = value.length;int i = -1;//把当前的char数组复制给val,然后下面基于val来操作char[] val = value; while (++i < len) {if (val[i] == oldChar) {break;}}if (i < len) {//创建一个新的char数组char buf[] = new char[len];for (int j = 0; j < i; j++) {buf[j] = val[j];}while (i < len) {char c = val[i];buf[i] = (c == oldChar) ? newChar : c;i++;}//创建一个新的String对象return new String(buf, true);}}return this;}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);}//正常返回的都是新new出来的String对象return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);}public String trim() {int len = value.length;int st = 0;char[] val = value;    /* avoid getfield opcode */while ((st < len) && (val[st] <= ' ')) {st++;}while ((st < len) && (val[len - 1] <= ' ')) {len--;}//如果是该字符串中包含了空格,调用substring方法,否则就是啥都没干原本返回//就是如果字符串里有空格,那么还是新生一个String对象返回return ((st > 0) || (len < value.length)) ? substring(st, len) : this;}

无论是concat、replace、substring还是trim方法的操作都不是在原有的字符串上进行的而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,任何变化性的操作都会生成新的对象。

    public class App {public static void main(String[] args) {String a = "111";String a1 = "111";String b = new String("111");//对象地址是同一个 //==比的是变量的值(值:指向内容的引用地址),equals比的是变量的内容System.out.println(a==a1);//对象内容是一样的System.out.println(a.equals(a1));//对象地址不一样System.out.println(a==b);//对象内容是一样的System.out.println(a.equals(b));}}

结果解析 

输出结果:true true false true 
第一个输出true,a和a1两个变量保存的引用地址是同一个。
第二个输出true,a和a1引用地址中内容是一样的。
String a = "111"在JVM申请内存存放"111"对应的对象,当String a1="111"的时候,先去JVM里寻找是否存在"111",如果存在直接把对象的引用地址给a1。此时的a和a1都保存着同一个引用地址。String b = new String("111")创建一个对象然后把对象引用地址赋给变量b,先去JVM里找 "111",找到了直接存放引用地址。找不到创建一个对象然后把引用地址给String的有参构造方法里。所以第三个中输出false,因为a和b所保存的对象引用是不一样的。
最后一个输出true。那是因为两个变量所保存的引用地址中的内容都是“111”。

String是否有长度限制

在Java中String是有长度限制的,在JVM编译中有规范。String长度限制的场景:将某固定文件转码成Base64的形式用字符串存储,运行时需要的时候在转回来,文件比较大。String a = "ssssssss..."构造的10万个字符的字符串,编译之后虚拟机提示报错,提示字符串长度过长。字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数且String类中返回字符串长度的方法length()返回值也是int ,通过int类型对应的包装类Integer源码中可以看到其长度最大限制为2^31 -1,说明数组的长度是0~2^31-1,那么大小就是(2^31-1 = 2147483647 = 2GB)。 但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义,对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535, 但是JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错。

StringBuffer解析

StringBuffer是可变的字符序列,当一个StringBuffer被创建以后,通过StringBuffer提供append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。StringBuffer的直接父类是AbstractStringBuilder,实现了Serializable即StringBuffer的对象可以串行化,在父类中AbstractStringBuilder有属性char[] value,不是final,该value数组存放字符串内容,存放在堆中,StringBuffer是一个final 类,不能被继承,因为StringBuffer字符内容是存在char[] value所以在变化(增加/删除) 时,不用每次都更换地址(不用创建新对象)效率高于String。

/*** @Author * StringBuffer**/
public class StringBuffer01 {public static void main(String[] args) {//创建一个大小为16的char[],用于存放字符内容StringBuffer stringBuffer01 = new StringBuffer();//2.通过构造器指定char[]的大小StringBuffer stringBuffer02 = new StringBuffer(100);//通过给一个String 创建 StringBuffer,char[] 大小就是str.length + 16StringBuffer stringBuffer03 = new StringBuffer("hello");String str = null;StringBuffer sb = new StringBuffer();sb.append(str);System.out.println(sb.length());//4}
}

String str = null是成立的,但是在StringBuffer的源码中append()方法:

@Overridepublic synchronized StringBuffer append(Object obj) {toStringCache = null;super.append(String.valueOf(obj));return this;}

从源码中看出StringBuffer中的append方法调用了父类的append方法,进父类AbstractStringBuilder查看父类的append方法,源码如下:

    public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;}

str为空时,调用appendNull()方法,追进appendNull()方法

  private AbstractStringBuilder appendNull() {int c = count;ensureCapacityInternal(c + 4);final char[] value = this.value;value[c++] = 'n';value[c++] = 'u';value[c++] = 'l';value[c++] = 'l';count = c;return this;}

把空对象转化为字符数组’null’,故最后输出的结果应该为4。

StringBuilder解析

StringBuilder和StringBuffer相似,两个类的构造器和方法也基本相同。不同的是StringBuffer是线程安全的,StringBuilder没有实现线程安全功能,所以性能略高。StringBuffer类中的方法都添加了synchronized关键字,给方法添加了一个锁,用来保证线程安全。Java9改进了字符串(包括String、StringBuffer、StringBuilder)的实现。在Java9以前字符串采用char[]数组来保存字符,字符串的每个字符占2字节,Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符,字符串的每个字符只占1字节,所以Java9的字符串更加节省空间。

StringBuilder源码

    @Override@HotSpotIntrinsicCandidatepublic StringBuilder append(String str) {super.append(str);return this;}

是什么导致了StringBuilder的线程不安全,进入父类AbstractStringBuilder

    public AbstractStringBuilder append(String str) {if (str == null) {return appendNull();}int len = str.length();ensureCapacityInternal(count + len);putStringAt(count, str);count += len;return this;}

问题在这两行

ensureCapacityInternal(count + len);
putStringAt(count, str); 

ensureCapacityInternal() 是一个扩容方法

    private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeint oldCapacity = value.length >> coder;if (minimumCapacity - oldCapacity > 0) {value = Arrays.copyOf(value,newCapacity(minimumCapacity) << coder);}}

coder是字符的编码格式,编码默认是Latin1,对应的coder是0。还有一种编码UTF16,对应的coder为1。用count(已经使用的长度)+len(要拼接的长度)得到需要的最小长度minimumCapacity。如果这个长度比原来的容量大,则触发扩容,把原数组复制到一个容量为(minimumCapacity*2+2)的新数组。在并发情况下,可能有多个线程拿到相同count,导致扩容不充分引起数组下标越界异常。一般要三个以上线程同时拿到count且必须是在程序开始时,数组不大的时候才可能出现这个异常。再来看putStringAt():

    private final void putStringAt(int index, String str) {if (getCoder() != str.coder()) {inflate();}str.getBytes(value, index, coder);}

在多线程下,可能有多个线程拿到相同count,在执行getBytes时,这几个线程添加的位置是相同的,可能会发生数据覆盖的情况。StringBuilder线程安全测试示例:

    @Testpublic void stringDemo02() throws InterruptedException {CountDownLatch count=new CountDownLatch(10000);StringBuilder stringBuilder=new StringBuilder();for (int i = 0; i <100 ; i++) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j <100 ; j++) {stringBuilder.append("q");count.countDown();}}});t.start();}count.await();System.out.println(stringBuilder.length());}
9912

关键字、操作类相关

final关键字:final修饰的类叫最终类,该类不能被继承。final 修饰的方法不能被重写。final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

操作字符串的类有:String、StringBuffer、StringBuilder。String和StringBuffer、StringBuilder的区别在于String声明的是不可变的对象,每次操作都会生成新的String对象,然后将指针指向新的 String对象,而StringBuffer、StringBuilder可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String。 StringBuffer和StringBuilder最大的区别在于StringBuffer 是线程安全的而StringBuilder是非线程安全的,StringBuilder的性能高于StringBuffer,在单线程环境下推荐使用StringBuilder,多线程环境下推荐使用StringBuffer。String str="i"与String str=new String("i")区别,String str="i"的方式,Java 虚拟机会将其分配到常量池中,String str=new String("i") 则会被分到堆内存中。

字符串反转

              使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。// StringBuffer reverseStringBuffer stringBuffer = new StringBuffer();stringBuffer. append("abcdefg");System. out. println(stringBuffer. reverse()); // gfedcba// StringBuilder reverseStringBuilder stringBuilder = new StringBuilder();stringBuilder. append("abcdefg");System. out. println(stringBuilder. reverse()); // gfedcba

 String类的常用方法:

              indexOf():返回指定字符的索引。charAt():返回指定索引处的字符。replace():字符串替换。trim():去除字符串两端空白。split():分割字符串,返回一个分割后的字符串数组。getBytes():返回字符串的 byte 类型数组。length():返回字符串长度。toLowerCase():将字符串转成小写字母。toUpperCase():将字符串转成大写字符。substring():截取字符串。equals():字符串比较。

普通类和抽象类区别

普通类不能包含抽象方法,抽象类可以包含抽象方法,抽象类不能直接实例化,普通类可以直接实例化。抽象类不能使用final修饰,定义抽象类就是让其他类继承的,如果定义为final该类就不能被继承,这样彼此就会产生矛盾,所以final不能修饰抽象类。

接口和抽象类区别

抽象类的子类使用extends来继承,接口必须使用implements来实现接口。抽象类可以有构造函数,接口不能有。类可以实现很多个接口,但是只能继承一个抽象类。接口中的方法默认使用 public修饰,抽象类中的方法可以是任意访问修饰符。

泛型

泛型就是可以适应不同的类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。保证了类型的安全性:泛型约束了变量的类型,保证了类型的安全性。例如List和ArrayList。List集合只能加入int类型的变量,ArrayList可以Add任何常用类型,编译的时候不会提示错误。泛型能够省去类型强制转换。提高方法、算法的重用性。

            //泛型类public class GenericClass<T> {private T value;public GenericClass(T value) {this.value = value;}public T getValue() {return value;}public void setValue(T value) {this.value = value;}}//泛型接口public interface GenericInterface<T> {void show(T value);}//泛型方法public class GenericFun {public void show(String value) { }public void show(Integer value) { }}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/199718.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang基础-面向过程篇

文章目录 基本语法变量常量函数import导包匿名导包 指针defer静态数组动态数组(slice)定义方式slice追加元素slice截取 map定义方式map使用方式 基本语法 go语言输出hello world的语法如下 package mainimport ("fmt""time" )func main() {fmt.Println(&…

Django 入门学习总结2 创建一个投票系统

通过学习&#xff0c;我们可以实现一个简单的投票系统。这个投票系统有两部分组成。 公共部分&#xff0c;公众可以查看和进行投票。管理员可以进行增加、删除、修改投票信息。 这里投票系统Python语言版本为3.10.13&#xff0c;Django Web框架版本为4.2.7。 投票系统的实现…

维基百科文章爬虫和聚类【二】:KMeans

维基百科是丰富的信息和知识来源。它可以方便地构建为带有类别和其他文章链接的文章&#xff0c;还形成了相关文档的网络。我的 NLP 项目下载、处理和应用维基百科文章上的机器学习算法。 一、说明 在我的上一篇文章中&#xff0c;展示了该项目的轮廓&#xff0c;并奠定了其基础…

LOJ #10134. 「一本通 4.4 练习 1」Dis

分析 根据数据范围分析一下复杂度&#xff0c;Floyd和dj算法都必爆。 发现题目说的是树&#xff0c;还是边还是双向的&#xff08;树本身就是无向的&#xff0c;连通无回路的无向图叫做无向树&#xff0c;简称树。如果题目说了树&#xff0c;那么默认边就是双向的&#xff09…

(二)汇编语句组成

一个完整的 RISC-V 汇编程序有多条 语句&#xff08;statement&#xff09; 组成。 一条典型的 RISC-V 汇编 语句 由 3 部分组成&#xff1a; 1.标签 List item label&#xff08;标签&#xff09;: 标签是标识程序位置的记号。通常定义一个名称然后加上":"后缀。…

PP-PicoDet算法训练行人检测模型

PP-PicoDet算法训练行人检测模型 1&#xff0c;效果图2&#xff0c;PP-PicoDet介绍3&#xff0c;使用飞浆框架训练模型1&#xff0c;准备好图片和对应的标注文件2&#xff0c;划分训练集和验证集3&#xff0c;vi label_list.txt4&#xff0c;目录结构5&#xff0c;修改配置文件…

【Windows 常用工具系列 11 -- 福昕PDF搜索高亮过的文本】

文章目录 福昕 PDF 搜索高亮过的文本 福昕 PDF 搜索高亮过的文本 在 pdf 文档阅读过程中&#xff0c;我们需要经常高亮一些文本&#xff0c;以方便下次阅读时找到重点。我这边使用的是 福昕PDF 阅读器&#xff0c;下面就介绍下如何在福昕阅读器中搜索已经高亮过的文本。

场景交互与场景漫游-交运算与对象选取(8-1)

交运算与对象选取 在面对大规模的场景管理时&#xff0c;场景图形的交运算和图形对象的拾取变成了一项基本工作。OSG作为一个场景管理系统&#xff0c;自然也实现了场景图形的交运算&#xff0c;交运算主要封装在osgUtil 工具中在OSG中&#xff0c;osgUtil是一个非常强有力的工…

python实战—核心基础4(超市购物小票随机抽奖程序) lv1

目录 一、核心代码解释 二、代码 三、运行截图 一、核心代码解释 1、random() 函数 描述 random() 方法返回随机生成的一个实数&#xff0c;它在[0,1)范围内。 语法 以下是 random() 方法的语法: import randomrandom.random() 注意&#xff1a;random()是不能直接访问…

OpenLDAP配置web管理界面PhpLDAPAdmin服务-centos9stream

之前已经发了一篇关于centos9下面配置openldap多主高可用集群的内容&#xff0c;不会配置ldap集群的请参考&#xff1a;服务器集群配置LDAP统一认证高可用集群&#xff08;配置tsl安全链接&#xff09;-centos9stream-openldap2.6.2-CSDN博客 这里跟着前篇文章详细说明如何配置…

在回调之间共享数据

可以在 App 中为 UI 组件编写回调函数&#xff0c;以指定用户与其交互时的行为方式。 在具有多个相互依赖的 UI 组件的 App 中&#xff0c;回调函数通常必须访问主 App 函数中定义的数据&#xff0c;或与其他回调函数共享数据。例如&#xff0c;如果创建一个具有列表框的 App&a…

.Net中Redis的基本使用

前言 Redis可以用来存储、缓存和消息传递。它具有高性能、持久化、高可用性、扩展性和灵活性等特点&#xff0c;尤其适用于处理高并发业务和大量数据量的系统&#xff0c;它支持多种数据结构&#xff0c;如字符串、哈希表、列表、集合、有序集合等。 Redis的使用 安装包Ser…

云计算赛项容器云2023搭建

部署容器云平台[5 分] 使 用 OpenStack 私 有 云 平 台 创 建 两 台 云 主 机 &#xff0c; 云 主 机 类 型 使 用 4vCPU/12G/100G 类型&#xff0c;分别作为 Kubernetes 集群的 Master 节点和 node 节点&#xff0c; 然后完成 Kubernetes 集群的部署&#xff0c;并完成 Istio …

【MySql】13- 实践篇(十一)

文章目录 1. 自增主键为什么不是连续的&#xff1f;1.1 自增值保存在哪儿&#xff1f;1.2 自增值修改机制1.2.1 自增值的修改时机1.2.2 自增值为什么不能回退? 1.3 自增锁的优化1.3.1 自增锁设计历史 2. Insert语句为何很多锁?2.1 insert … select 语句2.2 insert 循环写入2…

鸿蒙4.0真机调试踩坑

传言鸿蒙next版本将不再兼容Android&#xff0c;所以领导安排做下鸿蒙开发的调研工作。 鸿蒙开发指南其实已经非常的友好了。但是鸿蒙开发本身还是有些坑要踩&#xff0c;这篇文章主要讲了鸿蒙真机调试问题。 目前手上的真机为华为 nova6&#xff0c;处理器为麒麟990.鸿蒙系统…

AI绘画使用Stable Diffusion(SDXL)绘制三星堆风格的图片

一、前言 三星堆文化是一种古老的中国文化&#xff0c;它以其精湛的青铜铸造技术闻名&#xff0c;出土文物中最著名的包括青铜面具、青铜人像、金杖、玉器等。这些文物具有独特的艺术风格&#xff0c;显示了高度的工艺水平和复杂的社会结构。 青铜面具的巨大眼睛和突出的颧骨&a…

11.16~11.19绘制图表,导入EXCEL中数据,进行拟合

这个错误通常是由于传递给curve_fit函数的数据类型不正确引起的。根据你提供的代码和错误信息&#xff0c;有几个可能的原因&#xff1a; 数据类型错误&#xff1a;请确保ce_data、lg_data和product_data是NumPy数组或类似的可迭代对象&#xff0c;且其元素的数据类型为浮点数。…

C#,怎么修改(VS)Visual Studio 2022支持的C#版本

一些文字来自于 Microsoft . &#xff08;只需要读下面的红色文字即可&#xff01;&#xff09; 1 C# 语言版本控制 最新的 C# 编译器根据项目的一个或多个目标框架确定默认语言版本。 Visual Studio 不提供用于更改值的 UI&#xff0c;但可以通过编辑 .csproj 文件来更改值。…

基于SSM的学院网站设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

4.3 Windows驱动开发:监控进程与线程对象操作

在内核中&#xff0c;可以使用ObRegisterCallbacks这个内核回调函数来实现监控进程和线程对象操作。通过注册一个OB_CALLBACK_REGISTRATION回调结构体&#xff0c;可以指定所需的回调函数和回调的监控类型。这个回调结构体包含了回调函数和监控的对象类型&#xff0c;还有一个A…