String类
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
String类 1.1. 什么是字符串? 例如 “abc ” “你好” 上网发帖,QQ聊天的数据 由于这些非常常见所以就进行了对象的封装.描述定义了一些功能以便于操作这些字符串。 字符串是一个特殊的对象 字符串一旦初始化就不可以被改变. String str = “abc”; str = “def”; 1.2. 创建String类的对象 1:" " 直接赋值法 String str=”hello gzitcast”; 2:new关键字法 String str2=new String(”hello gzitcast”); 1.3. 分析String s1 = "abc"; String s1 = "abc"; 解读该行代码: s1 是什么 ?学过面向对象就不难理解了。s1是一个类类型变量, “abc”是一个对象。也就是一个引用指向了一个实例。 字符串是一个特殊的对象,只要看到双引号的部分那么都是字符串对象。 1.3.1. 字符串的不可变 字符串最大特点:一旦被初始化就不可以被改变。下列代码会改变字符串内容吗? String s1 = "abc"; s1 = "hello"; System.out.println(s1); // hello 答案是不能的,第一条语句创建了一个内容为”abc”的String对象,并将其引用赋值给s1,第二条语句创建了内容为”hello” 的新String对象,并将其引用赋值给s1.赋值后的第一个String对象仍然存在,但是不能在访问它,因为s1变量指向了新的对象。 1.4. 字符串常量池 1.4.1. String的常量池 String的常量池存储在方法区的常量池区域。 String s1="abc"; String s2="abc"; System.out.println(s1==s2); //结果为true 分析:如果单纯从引用变量考虑,那么s1引用变量指向了一个对象,s2引用变量也指向了另外一个对象,两个对象的引用必然不相等,但是结果为true。原因在于字符串常量池的存在。当第一次创建String s1="abc"; 时,会去常量池中检查,有没有该字符串,发现没有,就将该字符串存入常量池,并将引用(内存地址)赋给s1变量。执行到String s2="abc";时, 给s2变量初始化时时,会去常量池检查”abc”是否存在,已经存在,直接将引用(内存地址)赋给s2。所以s1 变量和s2变量持有的引用是一样的。 1.4.2. 为什么出现字符串常量池 字符串在程序中是不可变的,同时又频繁的使用,java虚拟机为了提高效率并节约内存,提供了字符串常量池区域。 随着程序的运行,String字面值(String对象中保存的字符串值)占用大量的程序内存非常常见,程序中可能出现大量重复的String字面值,为了使java更高效的使用内存,JVM留出一块特殊的内存区域称为String常量池,当编译器遇到String字面值时,会检查池内是否已经存在相同的String字面值。如果找到,则将新的字面值的引用指向现有的String,而不创建任何新的String字面值对象(现有的String只是具有一个额外的引用)。 由于会有多个引用指向同一个String,假如其中任何一个引用能够修改了String的值,那么会引发一些问题。这就是为什么String对象为不可变的原因之一。如果有人重写了String类的功能,可能会导致String常量池出现问题,这就是String类被标识为final的主要原因之一,所以任何人无法重写String方法。 注意:创建字符串对象两种方式的区别 String s1="abc"; 这种情况,"abc"放入了常量池中,s1变量引用它。 String s2=new String("abc"); 因为使用了new关键字,所以java在堆内存中创建一个新的String对象,并且将s引用指向它,另外将字面值"abc"拷贝到了常量池中. 所以是2个对象 1.5. String 类API文档 查看API文档发现类String 是java.lang 包中的。java.lang.String父类是java.lang.Object。该类是final的,说明不能有子类。方法不能被重写。 疑问String s2 = new String(“abc”); 为什么能够这么写呢? 查看String类的构造方法,原来原来是String 类的构造方法里边有对应的接收字符串的构造方法 1:String s=new String(); String s1=""; 是一回事 Sring类的equals方法 String s1 = "abc"; String s2 = new String("abc"); System.out.println(s1 == s2); //flase System.out.println(s1.equals(s2)); //true 1:s1==s2 结果为flase ,s1.equals(s2)结果为true 1:==比较的是内存地址,两个不同的对象,地址不同。 2:String重写了Object类的equals方法(比较内存地址),用于比较内容是否相 等 2:s1和s2有什么区别 1:s1在内存中有一个对象(常量池) 2:s2在内存中有两个对象(堆内存常量池) 1.6. String类常用方法 1.6.1. 获取 1:获取字符串的字符数,也就是字符串长度 int length(); 2:根据指定位置获取位置上的某个字符 char charAt(int index); 3:根据字符获取该字符在字符串中的位置 int indexOf(int ch); 传入的是ASCII码,返回的是ch在字符串中第一次出现的位置 int indexOf(int ch,int fromIndex); 从fromIndex指定位置开始,获取ch在字符串中出现的位置 注意:如果没有找到字符,返回-1 4:反向索引一个字符出现的位置 int lastIndexOf(int ch); 如果字符串特长,查找的字符在后可以提高效率返回的角标和正向查找的 角标不变 5:查找子字符串的位置 int indexOf(String str); 返回的是str在字符串中第一次出现的位置 int indexOf(String str,int fromIndex); 从fromIndex指定位置开 始获取str在字符中出现的位置 6:将传递进的字符串添加到调用者的尾部 String concat(String s) String str="hello"; str=str.concat("java"); hellojava 1.6.2. 判断 1:字符串中是否包含某一个子串 boolean contains(String str); 字符串中是否包含某一个子串 indexOf(String str); 可以获取子字符串第一次出现的位置,返回-1表示str在字符串中不存在 例如:if(str.indexOf("java")!=-1) 2:字符串中是否有内容 boolean isEmpty() 原理是判断字符串长度是否为0 3:字符串是否以指定内容开头 boolean startWith(String str); 4:字符串是否以指定内容结尾 boolean endsWith(String str); 5:判断字符串内容是否相同 boolean equals(String str); 重写了Object类中的equals方法 6:判断内容是否相同,不区分大小写 boolean equalsIgnoreCase(String str); 1.6.3. 转换 1:将字符数组转为字符串 1:构造函数 String(char[] ch); String(char[],int offset, int count); 1:offset 字符数组开始 2:count 字符个数 2:静态方法 static String copyValueOf(char[] ch); static String copyValueOf(char[] data, int offset, int count) 返回指定字符数组中一部分为String。 2:将字符串转成字符数组 char[] toCharArray(); 3:将字节数组转成字符串 构造函数: String(byte[] bytes); String(byte[] bytes,int offset,int count); 4:将字符串变为字节数组 byte[] getBytes(); 5:将基本数据类型转换成字符串 static String valueOf(int x); static String valueOf(double d); 注意:3+" " 和String.valueOf(3); 是一样的,后者更专业 1.6.4. 替换 String replace(char oldchar,char newchar); 如果要替换的字符不存在,返回的还是原串 1.6.5. 切割 String [] split(String regx); 1.6.6. 子串 String subString(int begin); 从指定位置开始到结尾,如果角标不存在会出现字符串角标越界异常 String subString(int begin,int end); 包含头,不包含尾 1.6.7. 转换,去除空格,比较 1:将字符串转换成大写或者小写 String toUpperCase(); 转大写由于字符串是不可变的,返回的是一个新创建的字符串,原本的还留在原地 String toLowerCase(); 转小写 2:将字符串两端空格去掉 String trim(); 3:对两个字符串进行自然比较 int compareTo(String str); 按照的是字典顺序,找不同字符的最小下标,直接比较两个字符的字典顺序,如 果找不到,两个字符串对象长度相减。 "a".compareTo("b"); -1 "aa".compareTo("bb"); -1 "aa".compareTo("ab"); -1 "aa".compareTo("abcd"); -1 该方法是Comparable接口中定义的,String类实现了该接口并重写了该方 法。按照的是字典顺序进行比较字符串。 自定义类也可以实现该接口,重写compareTo方法,实现自己的比较功能。就可以排序。例如书类,按照价格排序。 注意:比较分为三种情况 情况1:长度相同 “a”.compareTo(“a”); 0 “a”.compareTo(“b”); a的字典顺序在b之前,97-98=-1 “abc”.compareTo(“cde”); 找到最小的字符不同的索引位置即0, ”abc”.charAt(0)-”cde”.charAt(0) = ‘a’-’c’= -2 情况2:长度不同 “abc”.compareTo(“abcd”); “abc”.length()-”abcd”.length()=3-4=-1 情况3:长度不同且有最小的不同字符的索引 “abc”.compareTo(“edfr”); ”abc”.charAt(0)-”edfr”.charAt(0) = ‘a’-’e’= -4 compareTo public int compareTo(String anotherString) 按字典顺序比较两个字符串。该比较基于字符串中各个字符的 Unicode 值。将此 String 对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按字典顺序此 String 对象在参数字符串之前,则比较结果为一个负整数。如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。如果这两个字符串相等,则结果为 0; compareTo 只有在方法 equals(Object) 返回 true 时才返回 0。 如果参数字符串等于此字符串,则返回 0 值;如果按字典顺序此字符串小于字符串参数,则返回一个小于 0 的值;如果按字典顺序此字符串大于字符串参数,则返回一个大于 0 的值。 1.6.8. 练习 1、获取上传文件的名称 例如“E:\1224\day10\video\01Objcet.avi” 2、将字符串反转 3、一个字符在整串中出现的次数 4、对字符串中字符进行自然顺序排序 “fedca” ‘abcd’ 5、实现String的trim功能(子串原理) 6、获取一个字符串在另一个字符串中出现的次数。 2. StringBuilder类 2.1. 为什么使用该类 class Demo15 { public static void main(String[] args) { String str = "gz"; String str2 = str + "itcast" + "good" + "study"; System.out.println(str.toString()); } } 问题:字符串的+号是如何工作的?因为String类是对象,对象的操作是通过方法来完成的。 可以假设:String类有一个append方法,会产生一个新的String对象,用来包含"itcast"和str相连后的字符串,然后该对象与"good"相连,生成一个新的String对象,最后新对象与"study"相连生成另外一个新的String对象,就是gzitcastgoodstudy 字符串。那么之前的"gz" "itcast" "good" "study" 对象呢?就会产生一大堆垃圾需要回收。 那么上述代码是如何工作的?可以使用JDK自带的工具javap来反编译以上代码: Javap –c Demo15 -c 生成JVM字节码 只需要关注:编译自自动使用了java.lang.StringBuilder类,虽然在源代码中我们没有使用但是java编译器自动使用了它。为什么要用这个类?因为StringBuilder高效。 上述代码中编译器创建了一个StringBuilder对象,每+一次调用一次StringBuilder的append方法,一共四次。最后调用toString() 方法生成字符串。 分析如下代码。 class Demo15 { public static void main(String[] args) { String a = ""; for (int i = 0; i < 1000; i++) { System.out.println(a); a += i; } System.out.println("a = " + a); } } 继续使用javap –c Demo15 反编译代码 注意查看goto跳到了第5行,因为是一个循环,这里虽然也使用了StringBuilder但是该StringBuilder是在循环内部创建的。就是说每循环一次就创建了一个StringBuilder对象。 方案2: class Demo15 { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); } System.out.println(sb.toString()); } } 使用javap –c Demo15 反编译 反编译后发现,该循环的StringBuilder 对象是在循环外创建的,只在循环中执行append() 方法。显然使用StringBuilder要比String高效很多。 总结: 由于String对象是不可变的,如果要对String对象要做大量处理,那么将要导致String池中产生大量被丢弃的String对象。 2.2. StringBuilder是什么 字符串缓冲区,是容器并且长度是可变的,可以直接操作多个数据类型,最终通过toString方法变成字符串 验证String 的不可变 public static void main(String[] args) { //验证String的不可变 String str="abc"; str.concat("def"); //将指定字符串连接到此字符串的结尾。 System.out.println(str); //是输出"abcdef"吗? } 结果是abc 为什么,因为String是不可变的,调用了connect方法创建了一个新的”abcdef” String对象但是没有赋给变量,就是没有任何引用指向它,那么该”abcdef”就被丢弃。所以输出的还是”abc” class Demo15 { public static void main(String[] args) { // 验证String的不可变 String str = "abc"; str = str.concat("def"); // 将指定字符串连接到此字符串的结尾。 System.out.println(str); // 是输出"abcdef"吗? } } 结果是一个新的”abcdef”,但是旧的"abc"被丢弃在了String常量池中了。从而浪费了内存。 所以由于String的不可变,导致的问题,那么可以使用StringBuilder。 验证StringBuilder的可变 class Demo15 { public static void main(String[] args) { StringBuilder sb = new StringBuilder("abc"); sb.append("def"); System.out.println(sb.toString());// abcdef } } 因为StringBuilder是一个容器,初始化添加了abc 然后又把def添加进了容器中。所以操作的始终是StringBuilder这个容器。本例中的容器就是sb,该sb持有的对象引用只new了一次,所以不会有多余的对象。 简单查看StringBuilder容器的特点 class Demo15 { public static void main(String[] args) { StringBuilder sb = new StringBuilder r(); StringBuilder sb2 = sb.append("hello"); // 此对象的一个引用。返回的还是sb引用指向的那个容器 System.out.println(sb); System.out.println(sb2); System.out.println(sb == sb2); // true // 可以简化为sb.append("java").append("good").append("study"); // 连续的往容器中添加元素。 } } 2.3. 常见操作 2.3.1. 存储 StringBuilder append() 将指定数组作为参数添加到已有数据结尾处 StringBuilder insert(int index,数据) 将数据插入到指定index位置 2.3.2. 删除 StringBuilder delete(int start, int end); 删除缓冲区中包含start不包含end的子字符串 StringBuilder deleteCharAt(int index); 删除指定位置字符 1:清空缓冲区 delete(0,sb.length); //”abcd” sb.d(0,4) 2.3.3. 获取 char charAt(int index) int indexOf(String str); int lastIndexOf(String str); int length(); String SubString(int strat,int end); 2.3.4. 修改 StringBuilder replace(int start,int end, String str); void setCharAt(int index,char ch); 2.3.5. 反转 StringBuilder reverse(); 2.3.6. 将缓冲区中指定数据存到指定字符数组中 getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) srcBegin 缓冲区开始位置 srcEnd 缓冲区结束位置(包含头不包含尾巴) dst 保存字符数组 dstBegin 字符数组保存的开始位置 3. StringBuiffer 1:jdk5.0之前出现,线程同步,5.0之后出现了StringBuilder 2:多线程使用StringBuffer,单线程使用StringBuilder 4. System System 类包含一些有用的类字段和方法。它不能被实例化 字段: Field: err in out 方法: currentTimeMillis() 返回以毫秒为单位的当前时间。当前时间与1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位测量)。 exit(int status) 终止当前正在运行的 Java 虚拟机。status - 退出状态。0为正常退出 getProperties() 确定当前的系统属性。获取系统封装的所有的属性 返回的是Properties, Properties类是Hashtable的子类 使用Properties的list方法将系统属性输出到指定的输出流 setProperty(String key, String value) 设置指定键指示的系统属性。 getProperty(String key) 获取指定的属性 getenv(String name) 获取指定的环境变量值。 import java.util.Properties; class Demo8 { public static void main(String[] args) { Properties ps = System.getProperties();// 获取系统属性 // 输出系统属性 ps.list(System.out); // 获取操作系统名称 String os_name = System.getProperty("os.name"); System.err.println(os_name); // Windows XP // 检测操作系统的是否支持该软件 if ("Windows XP".equals(os_name)) { System.out.println("继续安装"); } else { System.out.println("系统不兼容....."); } // 获取path环境变量值 System.out.println(System.getenv("path")); System.out.println(System.getenv("os")); } } 5. Runtime Runtime类主要描述的是应用程序运行的环境。每个 Java 应用程序在运行时都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。 Runtime类特点: 无构造函数、无属性、是单例的、使用自身的static Runtime getRuntime() 返回自身实例 Runtime类方法: exit() 退出虚拟机 gc() 调用垃圾回收器程序,但是调用该方法的时候不会马上就运行GC。 Process exec(String command) 启动一个字符串命令的进程 应用: class Demo9 { public static void main(String[] args) throws Exception { // 获取应用运行环境的对象 Runtime run = Runtime.getRuntime(); Process notepad = run.exec("notepad Demo8.java" ); Process qq = run.exec("C:\\Program Files\\Tencent\\QQ\\Bin\\QQ.exe"); Thread.sleep(1000*10); // destroy() 杀掉子进程。 notepad.destroy(); } } 6. Date Date类封装的是系统的当前时间 Date date = new Date(); System.out.println(date); //Thu Sep 13 19:27:32 CST 2012 格式不是我们想要的,想要输出如下格式: 2012年09月13日 星期四 下午 07时27分32秒 就需要使用SimpleDateFormat 注意:Date在java.util.Date中,SimpleDateFormat在java.text.SimpleDateFormat中使用时注意导入包。 import java.util.Date; import java.text.SimpleDateFormat; class Demo10 { public static void main(String[] args) { Date date = new Date(); System.out.println(date); // Thu Sep 13 19:27:32 CST 2012 // 创建格式化对象对日期对象进行格式化 SimpleDateFormat sm = new SimpleDateFormat("yyyy年MM月dd日 E a hh时mm分ss秒"); System.out.println(sm.format(new Date())); } } Calendar : 该类是一个日历的类,封装了年月日时分秒时区 Calendar calendar = Calendar.getInstance(); // 获取年,月,日,时,分,秒 int year = calendar.get(Calendar.YEAR); // month是从0开始的 int month = calendar.get(Calendar.MONTH) + 1; int day = calendar.get(Calendar.DAY_OF_MONTH); int dayofweek = calendar.get(Calendar.DAY_OF_WEEK); int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); 例如可以查看3000年1月1日是星期几。 set(int year, int month, int date) month是从0开始的 1 星期天 2 星期一 3 星期二 4 星期三 5 星期四 6 星期五 7 星期六 Calendar calendar = Calendar.getInstance(); calendar.set(3000,0,1); int dayofweek = calendar.get(Calendar.DAY_OF_WEEK); // 1 星期天 2 一 3 二 4 三 5 四 6 五 7 六 7. Math Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。 特点: 功能都是静态的,可以直接调用 abs(double a) 返回指定数据的绝对值 ceil(double a) 向上取整 大于指定数据的最小整数 floor(double a) 向下取整 小于指定数据的最大整数 round(float a) 四舍五入 pow(double a, double b) a的b次幂 random() 返回0<=&>1的随机数 nextInt(int n) 返回从0(包含)到n(不包含)之间的随机整数 例如: (int)(Math.random()*10) 返回0到9之间随机整数 50+(Int)(Math.random()*50) 返回50-99之间的随机整数 a+Math.random()*b 返回一个在a到a+b之间单不包括a+b的随机数。 求出1~100之间的int类型的随机数 import java.util.Random; class Demo11 { public static void main(String[] args) { System.out.println(Math.PI); System.out.println(Math.ceil(12.3)); // 13.0 System.out.println(Math.ceil(12.5)); // 13.0 System.out.println(Math.ceil(-12.5));// -12.0 System.out.println(Math.floor(-15.1));// -16.0 System.out.println(Math.floor(15.1)); // 15.0 System.out.println(Math.round(15.1)); // 15 System.out.println(Math.round(15.5)); // 16 System.out.println(Math.random()); // 求1~10之间的随机整数 System.out.println((int) (Math.floor((Math.random() * 10 + 1)))); Random ran = new Random(); // 求1~10之间的随机整数 System.out.println(ran.nextInt(10) + 1); // 1 <= x < 11 } class Demo11 { public static void main(String[] args) { // 生成一个校验码 Random ran = new Random(); char chs[] = new char[] { 'a', 'b', 'c', 'F', 'H', '3', '6', '中', '过', '你', '好', '@' }; // 0~11 StringBuilder rel = new StringBuilder(""); for (int i = 0; i < 4; i++) { rel.append(chs[ran.nextInt(chs.length)]); } System.out.println("校验码是 : " + rel.toString()); } }