Math类
Math类的常用方法
Math类都是静态的方法我们只需要直接使用即可
1 | package Learn; |
Math类都是静态的方法我们只需要直接使用即可
1 | package Learn; |
1 | public StringBuffer() { |
1 | package Learn; |
方法名 | 用法 |
---|---|
append | 增 |
delete | 删 |
replace | 改 |
indexOf | 查 |
insert | 插入 |
1 | package Learn; |
练习:
一步一步调试,追溯源码,可以看出最终两个的差别。
1 | package Learn; |
学会利用StringBuffer格式化输出
1 | package Learn; |
1 | package Learn; |
String对象用于保存字符串,也就是一组字符序列
字符串常量的对象是用双引号括起的字符序列
字符串通常使用Unicode字符编码,一个字符(不管时字母还是汉字)占用两个字节
字符串String拥有很多的构造器
String类时final类,不能被其他的类所继承。private final byte[] value;String类拥有value属性,并且为final性质,表示不能被做出修改。比如从TOM变成TOO。也就是一句话,当我们的str发生更改之后,其地址已经改变,原地址的内容不会发生改变。
1 | package Learn; |
查看上述的图可以发现,String继承了以上的接口,而继承Serializable表示该字符可以在网络上传输,而对于Comparable则表示可以用于比较。
方式一:直接赋值
1 | String str1="jack"; |
先从常量池查看是否有”jack“是数据空间,如果有,则直接指向;如果没有,则重新创建,然后指向。str1最终指向的是常量池的地址空间。
调用构造器:
1 | String str2=new String("tom"); |
先在堆中创建空间,里面维护了value属性,指向常量池tom空间。如果常量池没有tom,重写创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。但是由于value是String的私有方法,所以我们无法查看value的值
1 | package Learn; |
他们虽然值相等,但是他们的地址却不相等。
1 | public String intern() |
返回字符串对象的规范表示。
最初为空的字符串池由String
类String
。
当调用intern方法时,如果池已经包含与equals(Object)
方法确定的相当于此String
对象的字符串,则返回来自池的字符串。 否则,此String
对象将添加到池中,并返回对此String
对象的引用。
由此可见,对于任何两个字符串s
和t
, s.intern() == t.intern()
是true
当且仅当s.equals(t)
是true
。
所有文字字符串和字符串值常量表达式都被实体化。 字符串文字在The Java™ Language Specification的 3.10.5节中定义。
结果
一个字符串与该字符串具有相同的内容,但保证来自一个唯一的字符串池。
1 | package Learn; |
1 | package Learn; |
编译器会堆常量池的对象进行优化。下面的一段代码中,按照本意会生成三个对象,但是编译器会自动识别出其中的关系,从而仅仅创建一个adcdac对象。查看以下代码之后我们可以看出,在str3中是创建了一个新对象的,否则不会与4和5的对象不相同。在3执行的过程,首先创建了StringBuilder对象,利用append接受str1与str2,然后在将值给与str3,由于使用了StringBuilder,所以本次的str3与String str3=new String(“adcdac”);效果相同。所以常量的相加在,常量池中进行操作,如果是变量则是在堆中进行操作。
1 |
|
Java中当数组与字符串在方法中被更改时
1 |
|
方法名 | 用法 |
---|---|
equals | 区分大小写,判断是否相等 |
equalsIgnoreCase | 忽略大小写,判断是否相等 |
length | 获得长度 |
indexOf | 获得字符在字符串中第一次出现的索引,从0开始,找不到就返回-1 |
lastindexOf | 获得字符在字符串中最终出现的位置,索引从0开始 |
substring(a,b) | 截取指定范围的字符串(不包含位置b) |
trim | 去除前后空格 |
charAt | 获取某索引处的字符 |
1 | package Learn; |
方法名 | 用法 |
---|---|
toUpperCase | 变成大写 |
toLowerCase | 变成小写 |
concat | 获得长度 |
replace | 替换字符串中的字符 |
split | 分割字符串 |
compareTo | 比较字符串的大小 |
toCharArray | 转换成字符数组 |
format | 格式化字符串 |
1 | package Learn; |
对于喜欢使用表情包的同学,例如,其实表情包的表示使用的是两个代码单元,这是想要如果我们想要得到的第三个char字符不是最后一个,如果此时想要精确使用,就需要我们调用另外的方法。
1 | /** |
此时我们输出length为4,如果想要输出正确的结果,需要首先转换为数组
1 | /** |
额,怎么顺利遍历正在思考中。。。,在《Java技术核心卷》中建议不要使用charAt方法,
1 | /** |
上述中一个主线程和两个子线程,主线程执行优先执行完毕,但是子线程还没由结束,主线程会提结束,而子线程继续执行。
1 | package windowSale; |
三个窗口售卖,会发现我们的票数剩余0张还可以继续售卖。这是因为在我们的票数还有两张时,我们的1号窗口进来还没有进行减去操作,2号与3号都进来,导致2张票被买了三次。
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任意同一时刻,最多由一个线程访问,保证数据的完整性。
//需要被同步的代码},操作的必须是同一个对象
1 | package windowSale; |
public synchronized void m(String name){
}
1 | package windowSale; |
此时发现没有超票售出,但是售票的顺序却不正确,而且其实内部已经发生了超卖现象,但是我们只不过用代码块掩盖 了
1 | package windowSale; |
在我们运行一个进程的时候,会出现主线程和子线程,主线程并不会因为子线程的运行而发生阻塞。
1 | /** |
运行以上程序,可以发现主线程的for循环并没有因为子线程cat的运行而进行停滞,而是不断的交替运行。
使用我们cat.run方法也可以调用这个方法,但是为什么使用start方法??
如果我们直接调用Run方法,会发现控制他的线程是main方法,而不是我们的Thread-0线程,这样的就不会交替执行,而是必须将方法执行完毕才可以执行下一个方法。这样就是串行化执行,而不是多线程。
1 | public void run(){ |
他的底层调用了start0方法,但是start0方法是一个native方法,由jvm底层进行调用,而不是由我们程序员进行调用
在start方法调用start0方法之后,线程并不是立刻执行,而是进入了可执行的状态,而最终的运行是由cpu统一进行调度。
每一个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证任意时刻,只有一个线程可以访问该对象,但是会导致执行效率比较低,同步方法的锁可以是this,也可以是其他对象,同步方法的锁为当前类的本身。上面买票的问题可以是this本身,也可以object,但是必须同一个对象,不可以是new的多个对象。
如果在静态代码中,必须当前对象
1 | class AA implements Runnable{ |
多线程占用对方的资源,但是不肯相让,导致了死锁。线程一直得不到资源,而一直在相互等待。
1 | /** |
可以看到程序一直卡在这里等待对方释放资源
1 | import java.util.Scanner; |
1 | /** |
每隔一秒输出一个语句,20秒结束
1 | /** |
1 | /** |
其实我们的底层使用了静态代理模式,而之所以可以使用Thread方法传入cat,是因为构造函数中存在Thread(Runnable),而Cat继承了Runnable。
1 | Thread thread = new Thread(cat); |
模拟线程代理
1 | class ThreadProxy implements Runnable{ |
其实总源码上来看,这两个创建线程的机制没有本质的区别,他们的底层都是调用start0机制进行线程的创建,而且Thread其实是调用了Runnable接口的。
但是Runnable接口更加适合多个线程共享一个资源的情况(可以将两个线程同时调用cat对象),并且避免了单继承的限制。
注意当我们运行start时,线程并不是立即执行的,而是根据cpu的执行状况来进行判定。线程的sleep与wait是两个不同的方法,sleep会自动结束阻塞状态,而wait是进入锁池状态,需要进行唤醒。
1 | /** |
1 | package windowSale; |