System.out.println("h"+"i");
System.out.println('h'+'i');
hi
209
解决方案:
//1
System.out.println("" + 'h' + 'i');
//2
System.out.printf("%c%c", 'h', 'i');
System.out.println(String.format("%c%c", 'h','i'));
//3
StringBuffer sb = new StringBuffer();
sb.append('h');
sb.append('i');
System.out.println(sb);
private static Random rnd = new Random();
public static void main(String[] args) {
StringBuffer word = null;
switch(rnd.nextInt(2)) {
case 1: word = new StringBuffer('P');
case 2: word = new StringBuffer('G');
default: word = new StringBuffer('M');
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);
}
ain
为何?
Ranmdom API:Random(next):均等返回0-next-1,这里只有0,1,没有2,应该写为3没有break,那就一直执行到最后,也就是default,覆盖前面所有的赋值编译器选择接受int类型,通过拓宽原始类型转换把M转为77,换句话说,new StringBuffer (‘M’) 返回的是一个具有初始容量 77 的空的字符串缓冲区。该程序余下的部分将字符 a、i 和 n 添加到了这个空字符串缓冲区中,并打印出该字符串缓冲区那总是 ain 的内容。解决方案:
private static Random rnd = new Random();
public static void main(String[] args) {
System.out.println("PGM".charAt(rnd.nextInt(3)) + "ain");
}
2、Java小白踩坑录 - Random 揭秘
private static final Random rnd = new Random();
//第一种
static int random(int n) {
return n>=0 ? rnd.nextInt(n) : rnd.nextInt(Math.abs(n));
}
//第二种
static int random2(int n) {
return Math.abs(rnd.nextInt())%n;
}
public static void main(String[] args) {
int n = 1000;
int di = 0;
int low = 0;
for(int i = 0;i < 10_000_000;i++) {
if(random(n) < Math.abs(n/2)) {
di++;
}
if(random2(n) < Math.abs(n/2)) {
low++;
}
}
System.out.println(di);
System.out.println(low);
}
运行数据看结果就会发现:
random 的数据比较均匀;random2 的数据差不多 2/3 落到前半部分,后半部分只有 1/3nextInt() 这个方法看起来可能不错,但是存在三个缺点。
第一个缺点是: 如果 n 是比较小的 2 的乘方,经过一段相当短的周期之后,它产生的随机数将会重复。
第二个缺点是:如果 n 不是 2 的乘方,那么平均起来,有些数就会比其它的数出现的更频繁。特别是 n 比较大,这个缺点就非常明显。这就是为什么 2*(Integer.MAX_VALUE/3)
中有用2乘的原因。
第三个缺点是:在极少数情况下,会返回一个落在指定范围之外的数或者报错。示例如下:
public static void main(String[] args) {
int n=Integer.MIN_VALUE;
int di=0;
int low=0;
for(int i=0;i<10_000_000;i++) {
if(random(n)<Math.abs(n/2)) {
di++;
}
if(random2(n)<Math.abs(n/2)) {
low++;
}
}
System.out.println(di);
System.out.println(low);
}
报错原因 Math.abs() 碰到 Integer.MIN_VALUE 时会返回 Integer.MIN_VALUE。在 abs 的方法说明中有这么一段话:
Note that if the argument is equal to the value of {@link Integer#MIN_VALUE}, the most negative representable{@code int} value, the result is that same value, which is negative.
即注意:Math.abs() 方法中,如果输入值为 Integer.MIN_VALUE,那么会返回同样的结果,就是MIN_VALUE绝对值还是负数,还是它本身
另一方面,也可以看看 abs 的代码实现来理解
public static int abs(int a) { return (a < 0) ? -a : a; }
假设 a=Integer.MIN_VALUE 即 -2147483648(0x80000000),假设返回 int 的值 2147483648 会发生溢出,因为 int 的最大值为 2147483647(0x7fffffff),溢出后又变成了 0x80000000,即 Integer.MIN_VALUE
源码描述:
/**
* Returns the absolute value of an {@code int} value.
* If the argument is not negative, the argument is returned.
* If the argument is negative, the negation of the argument is returned.
*
* Note that if the argument is equal to the value of
* {@link Integer#MIN_VALUE}, the most negative representable
* {@code int} value, the result is that same value, which is
* negative.
*
* @param a the argument whose absolute value is to be determined
* @return the absolute value of the argument.
*/
public static int abs(int a) {
return (a < 0) ? -a : a;
}
Java 类库提供了一个带 seed 的方法来解决上面的问题,就是 Random.nextInt(n)。
总结
随机数的生成器涉及了很多算法的相关知识,幸运的是,我们并不需要自己来做这些工作,我们可以利用现成的成果为我们所用,如 Random.nextInt(n) 或者 java.security.SecureRandom,或者第三方的 API。注意:我们尽量使用类库,而不是自己去开发。
Linux 系统有 /dev/random,/dev/urandom 向用户提供真随机数。
3、Java小白踩坑录 - B计划之Java资源如何释放?public void openFile() throws IOException {
String line;
try (BufferedReader br = new BufferedReader(
new FileReader("C:\testing.txt"))) {
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
从 JDK7 开始,使用 try-with-resources 可以自动释放资源,即把资源放到 try() 内部, JVM 会调用 java.lang.AutoCloseable.close() 方法,自动关闭 try() 内部的资源。
4、Java小白踩坑录 - 反射到底有多慢?import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
public class ReflectionTest {
private static final int batchsize=1000000;
public static void main(String[] args) {
//非反射方式测试
long start=System.currentTimeMillis();
addWithoutReflect();
System.out.println(System.currentTimeMillis()-start);
//反射方式测试
start=System.currentTimeMillis();
addWithReflect();
System.out.println(System.currentTimeMillis()-start);
}
//非反射方式
static void addWithoutReflect() {
List<String> list=new ArrayList<>();
for(int i=0;i<batchsize;i++) {
list.add(""+i);
}
}
//反射方式
static void addWithReflect() {
List<String> list=new ArrayList<>();
Method method=null;
try {
method = list.getClass().getMethod("add", Object.class);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
for(int i=0;i<batchsize;i++) {
try {
method.invoke(list, ""+i);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
经过不同级别的测试,测试结果如下(本次测试,不做任何数据的修饰):单位毫秒
方式 \ 数据量 | 10 | 100 | 1000 | 1w | 10w | 100w | 1kw | 10kw |
---|---|---|---|---|---|---|---|---|
普通方式 | 0 | 1 | 1 | 4 | 24 | 96 | 6384 | 107714 |
反射方式 | 1 | 3 | 4 | 9 | 35 | 117 | 2004 | 71285 |
可以大致看出,在百万以下级别,反射方式的耗时是普通方式的 1~4 倍,千万级别以上的话,反而普通方式比较慢,原因未知,一般情况下,1w 次以下的反射对性能的影响微乎其微(0.05 秒内),可以忽略。
总结
如果只是以非常有限的形式使用反射机制,虽然也要付出少许代价,但是可以获得许多好处。使用反射可能会付出的代价:
丧失了编译时类型检查的好处,包含异常检查;执行反射访问所需要的代码非常笨拙和冗长;性能损失;对性能敏感的接口尽量不使用反射或者少使用反射。 5、Java小白踩坑录 - 数组 & Listimport java.util.ArrayList;
import java.util.List;
public class TestArray {
private static final int batch = 10_000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
insert2Array();
long end = System.currentTimeMillis();
System.out.println(end-start);
insert2List();
System.out.println(System.currentTimeMillis()-end);
}
static void insert2Array() {
int[] nums = new int[batch];
for(int i = 0;i < batch;i++) {
nums[i]=i;
}
}
static void insert2List() {
List<Integer> nums=new ArrayList<Integer>();
for(int i=0;i<batch;i++) {
nums.add(i);
}
}
}
数量级 batch 不同,耗时不同:
batch | 数组耗时 | Arraylist 耗时(单位毫秒) |
---|---|---|
1w | 0 | 1 |
10w | 1 | 4 |
100w | 3 | 18 |
1kw | 18 | 196 |
10kw | 153 | 21880 |
List<Integer> nums=new ArrayList<Integer>();
// ...
nums.add(i);
因 List 只能存放 Integer 对象型的变量,i 需要先转为 Integer,然后才能插入,就是这个动作影响了主要性能,我们可以验证一下这个问题。
这里还有一点是 list 这需要不断扩容也会耗时。
装箱,扩容
public class TestArray2 {
private static final int batch = 10_000;
public static void main(String[] args) {
long start=System.currentTimeMillis();
insert2Array();
long end=System.currentTimeMillis();
System.out.println(end-start);
insert2List();
System.out.println(System.currentTimeMillis()-end);
}
static void insert2Array() {
String[] nums = new String[batch];
for(int i=0;i<batch;i++) {
nums[i]=""+i;
}
}
static void insert2List() {
List<String> nums=new ArrayList<String>();
for(int i=0;i<batch;i++) {
nums.add(""+i);
}
}
}
总结
Java 语言设计的数据结构如 set、list、map 等只能存放引用对象,碰到数值型的问题时,就会发生拆箱、装箱的动作,特别是数据较大时,拆箱、装箱对性能的影响就比较大了,所以写程序时或者设计时需要根据场景选择合适的数据结构。
6、Java小白踩坑录 - Java类型的七十二变揭秘public class Multicast{
public static void main (String[] args){
System.out.println((int)(char)(byte) -1);
}
}
65535
public static void main(String[] args) {
int i = -1;
byte b = (byte) i; //1
char c = (char) b; //2
int r = (int) c; //3
System.out.println(r);
}
“因为 byte 是一个有符号类型,而 char 是一个无符号类型。在将一个整数类型转换成另一个宽度更宽的整数类型时,通常是可以保持其数值的,但是却不可能将一个负的 byte 数值表示成一个 char。因此,从 byte 到 char 的转换被认为不是一个拓宽原始类型的转换,而是一个拓宽并窄化原始类型的转换:byte 被转换成了 int,而这个 int 又被转换成了 char”。
1.-1 是 int 类型,它的二进制结构
0b1111_1111_1111__1111_1111_1111_1111_1111
int 转成 byte,截取低 8 位 0b1111_1111 值也为 -1
2.byte 转 char,需要先拓展到 int 型,然后转成 char
2.1 byte 转 int
0b1111_1111_1111__1111_1111_1111_1111_1111
2.2 int 转 char
0b1111_1111_1111__1111
值位 65535
3.char 转 int (补零)
0b0000_0000_0000__0000_1111_1111_1111_1111
其值位 65535
负数-5的表示法
现在想知道,-1在计算机中如何表示?在计算机中,负数以原码的补码形式表达。
10000....1->11111111..0+1->11111111..1
7、Java小白踩坑录 - Java 编程语言中很少被人了解的特性(标号语句)
public static void main(String[] args) {
System.out.print("baidu site :");
https://www.baidu.com;
System.out.println(" format");
}
“:” 是 statement label 翻译成标号语句
interface FileNotFound {
void run() throws FileNotFoundException;
}
interface CloneNotSupported {
void run() throws CloneNotSupportedException;
}
class TryCatchException implements FileNotFound, CloneNotSupported {
public static void main(String[] args) {
TryCatchException e = new TryCatchException();
e.run();
}
@Override
public void run() {
System.out.println("Hello world");
}
}
Hello world
8.Java小白踩坑录 - 难以琢磨的try-catch
public static void main(String[] args) {
try {
System.out.println("Hello world");
} catch(IOException e) {
System.out.println("抓到一个 IO 异常!");
}
}
就是 try 里没有能抛出 IOException 异常的语句,catch 该异常就通不过编译。
import java.io.IOException;
public class TryCatchException {
public static void main(String[] args) {
try {
System.out.println("Hello world");
throw new IOException();//或者子异常,如throw new FileNotFoundException();
} catch(IOException e) {
System.out.println("抓到一个IO 异常!");
}
}
}
public class TryCatchException {
public static void main(String[] args) {
try {
System.out.println("hello world!");
} catch(Exception e) {
System.out.println("捕获到异常");
}
}
}
hello world!
不管与其相对应的 try 子句的内容是什么,捕获 Exception 或者 Throwable 的 catch 语句是 ok 的,这点 JSL 并没有说清楚。
public interface FileNotFound {
void run() throws FileNotFoundException;
}
public interface CloneNotSupported {
void run() throws CloneNotSupportedException;
}
public class TryCatchException implements FileNotFound,CloneNotSupported {
public static void main(String[] args) {
TryCatchException e=new TryCatchException();
e.run();
}
@Override
public void run() {
System.out.println("Hello world");
}
}
一个方法可以抛出的受检查异常的集合时,它所适用所有类型声明要抛出的受检查异常的交集,而不是合集。
9.Java小白踩坑录 - 一分钱到底去哪里了?public static void main(String[] args) {
double total=2.00f;
double cost=1.10f;
System.out.println(total-cost);
}
0.8999999761581421
public static void main(String[] args) {
BigDecimal total=new BigDecimal(2.00);
BigDecimal cost=new BigDecimal(1.10);
System.out.println(total.subtract(cost));
}
0.899999999999999911182158029987476766109466552734375
将 double 值传入 BigDecimal 实例里,double 本身会损失精度,故结果也会损失精度;而使用BigDecimal 实例 total 和 cost 会解析字符串的值,不会损失精度。详细实现你可以看 BigDecimal 的源码”
public static void main(String[] args) {
BigDecimal total=new BigDecimal("2.00");
BigDecimal cost=new BigDecimal("1.10");
System.out.println(total.subtract(cost));
}
0.90
在我们的业务中可以使用 int 结算,毕竟总额和消费金额及余额最小单位为分,可以使用 int 计算出
public static void main(String[] args) {
int total=200;
int cost=110;
System.out.println((200-110)+" cents");
}
总结
在需要精确答案的地方,要避免使用 float 和 double;对于货币计算,要使用 int、long 或 BigDecimal,同时也要考虑业务的本身的使用场景,选择合适的数据类型。
10、Java小白踩坑录 - 反射的小秘密public static void main(String[] args) throws Exception {
Set<String> s = new HashSet<String>();
s.add("foo");
Iterator<String> it = s.iterator();
Method m = it.getClass().getMethod("hasNext");
System.out.println(m.invoke(it));
}
Exception in thread "main" java.lang.IllegalAccessException: Class com.javapuzzle.davidwang456.ReflectorTest can not access a member of class java.util.HashMap$HashIterator with modifiers "public final"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.javapuzzle.davidwang456.ReflectorTest.main(ReflectorTest.java:15)
11、Java小白踩坑录 - 猿类分级考试实录
一阶猿类
public class Counter1 {
private static int cnt=0;
public int increase() {
return ++cnt;
}
public int decrease() {
return --cnt;
}
}
旁白:实现了功能。
二阶猿类
public class Counter2 {
private static long cnt=0;
public long increase() {
return ++cnt;
}
public long decrease() {
return --cnt;
}
}
旁白:考虑了 int 的范围限制,long 的范围更广泛。
三阶猿类
public class Counter3 {
private static long cnt=0;
public synchronized long increase() {
return ++cnt;
}
public synchronized long decrease() {
return --cnt;
}
}
旁白:考虑了并发环境下的执行。
四阶猿类
public class Counter4 {
private static AtomicLong cnt=new AtomicLong(0);
public long increase() {
return cnt.getAndIncrement();
}
public long decrease() {
return cnt.getAndDecrement();
}
}
旁白:考虑了并发环境下的 CAS 性能更优。
五阶猿类
public class Counter5 {
private static LongAdder cnt=new LongAdder();
public long increase() {
cnt.increment();
return cnt.longValue();
}
public long decrease() {
cnt.decrement();
return cnt.longValue();
}
}
旁白:在单线程下,并发问题没有暴露,两者没有体现出差距;随着并发量加大,LongAdder 的 increment *** 作更加优秀,而 AtomicLong 的 get *** 作则更加优秀。鉴于在计数器场景下的特点—写多读少,所以写性能更高的 LongAdder 更加适合。
六阶猿类
public class Counter6 {
private static JdbcTemplateUtils jdbc=new JdbcTemplateUtils();//JdbcTemplateUtils封装了jdbc的调用
private static long cnt=0;
public long increase() {
cnt=jdbc.getCnt();
return jdbc.setCnt(++cnt);
}
public long decrease() {
cnt=jdbc.getCnt();
return jdbc.setCnt(--cnt);;
}
}
旁白:考虑了在集群环境下保证数据的唯一性和一致性。
七阶猿类
public class Counter7 {
private static RedisclusterUtils redis=new RedisclusterUtils();//RedisclusterUtils封装了Rediscluster的client功能
private static long cnt=0;
public long increase() {
return redis.incr(cnt);
}
public long decrease() {
return redis.decr(cnt);;
}
}
旁白:考虑了计数器集群下的并发性能问题,同样的实现可以使用 zk 或者 mongo 等内存数据库。
注:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。MongoDB 是一个基于分布式文件存储的数据库。
八阶猿类
public class Counter8 {
private static JdbcTempalteUtils jdbc=new JdbcTempalteUtils();
private static RedisclusterUtils redis=new RedisclusterUtils();
private static long cnt=0;
public long increase() {
if(redis.exsits(cnt)) {
return redis.incr(cnt);
}
cnt=jdbc.getCnt(key);
++cnt;
redis.set(key,cnt);
return cnt;
}
public long decrease() {
if(redis.exsits(cnt)) {
return redis.decr(cnt);
}
cnt=jdbc.getCnt(key);
--cnt;
redis.set(key,cnt);
return cnt;
}
}
旁白:考虑到 Redis 宕机或者不可用的情况下的处理,有备份方案。
九阶猿类
这个要免考的。
12.Java小白踩坑录 - Java有关null的几件小事Java 中的空 null
我们先看几段代码吧。
1. 例一:null 的对象性
public class NullTest {
public static void greet() {
System.out.println("Hello world!");
}
public static void main(String[] args) {
((NullTest) null).greet();
}
}
上面的程序看起来似乎应该抛出 NullPointerExceptioin 异常,因为其 main 方法是在常量 null 上调用 greet 方法,而你是不可以在 null 上调用方法的,对吗?
其实编译和运行都没有问题。运行结果为:
Hello world!
2. 例二:null 的初始化
public static void main(String[] args) {
String str=null;
Integer in=null;
Double dou=null;
String str1=(String)null;
Integer in1=(Integer)null;
Double dou1=(Double)null;
int in2=null;
int in3=(int)null;
}
发现 null 可以初始化引用类型,也可以转换为任意的引用类型。但不能给基本类型赋值,或者转换为基本类型。
3. 例三:null 的相等性
public static void main(String[] args) {
System.out.println(null==null);
System.out.println(null!=null);
System.out.println(Double.NaN==Double.NaN);
System.out.println(Double.NaN!=Double.NaN);
}
结果该是什么呢?
true
false
false
true
4. 例四:null 不是引用类型
public static void main(String[] args) {
Integer in=null;
if(in instanceof Integer) {
System.out.println("null is integer");
}else {
System.out.println("null is not integer");
}
}
结果是:
null is not integer
5. 例 5:不可传递
public static void main(String[] args) {
Integer i=null;
int k=i;
System.out.println(k);
}
报错:
Exception in thread "main" java.lang.NullPointerException
NullTest.main(NullTest.java:6)
6. 例 6:null 的数组
public static void main(String[] args) {
String[] arr1={"abc","123",null,"sky"};
boolean flag=false;
for (String s1 : arr1) {
if(s1.equals("sky")) {
flag=true;
break;
}
}
System.out.println(flag);
}
运行时报错
Exception in thread "main" java.lang.NullPointerException
at NullTest.main(NullTest.java:8)
修改成
public static void main(String[] args) {
String[] arr1={"abc","123",null,"sky"};
boolean flag=false;
for (String s1 : arr1) {
if("sky".equals(s1)) {//对比前后顺序
flag=true;
break;
}
}
System.out.println(flag);
}
就没有问题了。
追根到底
JSL 3.10.7 定义了 null
The null type has one value, the null reference, represented by the null literal null, which is formed from ASCII characters.
JSL 4.1 做了补充:
1.There is also a special null type, the type of the expression null (§3.10.7, §15.8.1), which has no name.
Because the null type has no name, it is impossible to declare a variable of the null type or to cast to the null type.
注:null 是一种特殊类型,它的表达式为 null,但没有名称。因为 null 类型没有名称,故不能声明一个 null 类型(如 private null a),也不能将一个类型转为 null 类型。
2.The null reference is the only possible value of an expression of null type.
注:使用 null 类型的唯一方式是使用 null 引用(如 private Integer a = null);
3.The null reference can always be assigned or cast to any reference type (§5.2, §5.3, §5.5).
注:空引用可以赋值给其他任意类型,如 String,Integer,Class 等等。
4.In practice, the programmer can ignore the null type and just pretend that null is merely a special literal that can be of any reference type.
注:其实,程序开发者可以忽略 null 类型,仅仅将它当作一种可以赋值给其他任意类型的特殊引用。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)