ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。或称为 线程本地变量
这个玩意有什么用处?先解释一下,在并发编程的时候,一个单例模式的类的属性,如果不做任何处理(是否加锁,或者用原子类)其实是线程不安全的,各个线程都在操作同一个属性,比如CoreServlet,Servlet是单例模式,所以如果在Servlet中增加一个属性,那么就会有多线程访问这个属性就会诱发的安全性问题。
这样显然是不行的,并且我们也知道volatile这个关键字只能保证线程的可见性,不能保证线程安全的。如果加锁,效率有会有一定程度的降低。
那么我们需要满足这样一个条件:属性是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,DAO我们在实际项目中都会是单例模式的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面,代码如下所示:
|
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); |
Spring的事务主要是ThreadLocal和AOP去做实现的,我这里提一下,大家知道每个线程自己的Connection conn是靠ThreadLocal保存的就好了。


当ThreadLocal Ref出栈后,由于ThreadLocalMap中Entry对ThreadLocal只是弱引用,所以ThreadLocal对象会被回收,Entry的key会变成null,然后在每次get/set/remove ThreadLocalMap中的值的时候,会自动清理key为null的value,这样value也能被回收了。
注意:如果ThreadLocal Ref一直没有出栈(例如上面的connectionHolder,通常我们需要保证ThreadLocal为单例且全局可访问,所以设为static),具有跟Thread相同的生命周期,那么这里的虚引用便形同虚设了,所以使用完后记得调用ThreadLocal.remove将其对应的value清除。
另外,由于ThreadLocalMap中只对ThreadLocal是弱引用,对value是强引用,如果ThreadLocal因为没有其他强引用而被回收,之后也没有调用过get/set,那么就会产生内存泄露,
在使用线程池时,线程会被复用,那么里面保存的ThreadLocalMap同样也会被复用,会造成线程之间的资源没有被隔离,所以在线程归还回线程池时要记得调用remove方法。
上面提到ThreadLocalMap是自己实现的类似HashMap的功能,当出现Hash冲突(通过两个key对象的hash值计算得到同一个数组下标)时,它没有采用链表模式,而是采用的线性探测的方法,既当发生冲突后,就线性查找数组中空闲的位置。
当数组较大时,这个性能会很差,所以建议尽量控制ThreadLocal的数量。
ThreadLocal在案例中一般以static形式存在的。

此方法为ThreadLocal保存的数据类型指定的一个初始化值,在ThreadLocal中默认返回null。但可以重写initialValue()方法进行数据初始化。
如果使用的是Java8提供的Supplier函数接口更加简化:




get()用于返回当前线程ThreadLocal中数据备份,当前线程的数据都存在一个ThreadLocalMap的数据结构中。


initialValue() : 初始化ThreadLocal中的value属性值。
set():获取当前线程,根据当前线程从ThreadLocals中获取ThreadLocalMap数据结构,
如果ThreadLocalmap的数据结构没创建,则创建ThreadLocalMap,key为当前ThreadLocal实例,存入数据为当前value。ThreadLocal会创建一个默认长度为16Entry节点,并将k-v放入i位置(i位置计算方式和hashmap相似,当前线程的hashCode&(entry默认长度-1)),并设置阈值(默认为0)为Entry默认长度的2/3。
如果ThreadLocalMap存在。就会遍历整个Map中的Entry节点,如果entry中的key和本线程ThreadLocal相同,将数据(value)直接覆盖,并返回。如果ThreadLoca为null,驱除ThreadLocal为null的Entry,并放入Value,这也是内存泄漏的重点地区。
get()
get()方法比较简单。就是根据Thread获取ThreadLocalMap。通过ThreadLocal来获得数据value。注意的是:如果ThreadLocalMap没有创建,直接进入创建过程。初始化ThreadLocalMap。并直接调用和set方法一样的方法。
|
package com.hy.threadlocal01;
public class ThreadLocalDemo0 { public static ThreadLocal<Integer> tl0 = new ThreadLocal<Integer>();
public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + ":" + tl0.get()); // main:null
tl0.set(1000);
System.out.println(Thread.currentThread().getName() + ":" + tl0.get()); //main:1000 } }
|

|
public static void main(String[] args) { Emp fbb = new Emp(1, "fbb", "fbb", 40); fbb.run();
Emp lbb = new Emp(2, "lbb", "lbb", 50) { @Override public void run() { super.run(); // 调用父类的run方法 } }; lbb.run();
//new了一个类的对象,这个类是一个匿名类,但是我知道这个类继承/实现了Emp类 Emp zjb = new Emp(3, "zjm", "zjm", 18) { @Override // 重写父类 run方法 public void run() { System.out.println(super.getEname() + "," + super.getAge() + ",run..."); } };
zjb.run();
//和下面这案例,不能说完全相同,只能说一模一样 //new了一个匿名类该匿名类实现了Runnable接口 Thread t1 = new Thread(new Runnable() { @Override public void run() {
} });
//lambda表达式写法 Thread t2 = new Thread(()-> {
}); } |
|
package com.hy.threadlocal01;
public class ThreadLocalDemo00 { public static ThreadLocal<Integer> tl00 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":"+tl00.get()); } } |

|
package com.hy.threadlocal01;
public class ThreadLocalDemo001 { public static ThreadLocal<Integer> tl001 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 100; }; };
public static void main(String[] args) { tl001.set(200); System.out.println(Thread.currentThread().getName()+":"+tl001.get()); } } |

|
package com.hy.threadlocal01;
public class ThreadLocalDemo01 { public static ThreadLocal<Integer> tl01 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { System.out.println("=======begin"); return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + ": ->get -> init:" + tl01.get()); tl01.set(200); // main线程改成200; System.out.println(Thread.currentThread().getName() + ": ->set -> get:" + tl01.get()); tl01.remove(); System.out.println(Thread.currentThread().getName() + ": -> remove -> get->init:" + tl01.get()); tl01.get(); System.out.println(Thread.currentThread().getName() + ": -> get:" + tl01.get()); } } |

|
package com.hy.threadlocal01;
public class ThreadLocalDemo011 { public static ThreadLocal<Integer> tl01 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { System.out.println("=======begin"); return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":"+tl01.get()); tl01.set(200); //main线程改成200; System.out.println(Thread.currentThread().getName()+":"+tl01.get());
System.out.println("***********************"); new Thread() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+tl01.get()); }; }.start(); } } |

|
package com.hy.threadlocal01;
public class ThreadLocalDemo0111 { public static ThreadLocal<Object> tl01 = new ThreadLocal<Object>() { @Override protected Object initialValue() { return new Object(); }; };
public static void main(String[] args) { final Object o1 = tl01.get(); System.out.println(Thread.currentThread().getName() + ":" + o1);
new Thread() { @Override public void run() { Object o2 = tl01.get(); System.out.println(Thread.currentThread().getName() + ":" + o2);
System.out.println(o1 == o2); }; }.start(); } }
|




案例2:
|
public class ThreadLocalTest05 { public static String dateToStr(int millisSeconds) { Date date = new Date(millisSeconds); SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); return simpleDateFormat.format(date); }
private static final ExecutorService executorService = Executors.newFixedThreadPool(100);
public static void main(String[] args) { for (int i = 0; i < 3000; i++) { int j = i; executorService.execute(() -> { String date = dateToStr(j * 1000); // 从结果中可以看出是线程安全的,时间没有重复的。 System.out.println(date); }); } executorService.shutdown(); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; // java8的写法,装逼神器 // public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = // ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); }
|
|
package com.hy.threadlocal02;
public class ThreadLocalDemo02 { private static ThreadLocal<Integer> tl02 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } };
private static void add() { for (int i = 0; i < 5; i++) { // 从当前线程的ThreadLocal中获取默认值 Integer n = tl02.get(); n += 1; // 往当前线程的ThreadLocal中设置值 tl02.set(n); System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n); } }
public static void main(String[] args) {
for (int i = 0; i < 3; i++) { new Thread(new Runnable() { @Override public void run() { add(); } }).start(); } } } |
保证每个线程都能遍历完成,并且数据正确,其他线程不会影响当前线程的数据。

通常用于保存线程不安全的工具类,典型的需要使用的类就是 SimpleDateFormat。
在这种场景下,每个 Thread 内都有自己的实例副本,且该副本只能由当前 Thread 访问到并使用,相当于每个线程内部的本地变量,这也是 ThreadLocal 命名的含义。因为每个线程独享副本,而不是公用的,所以不存在多线程间共享的问题。
我们来做一个比喻,比如饭店要做一道菜,但是有 5 个厨师一起做,这样的话就很乱了,因为如果一个厨师已经放过盐了,假如其他厨师都不知道,于是就都各自放了一次盐,导致最后的菜很咸。这就好比多线程的情况,线程不安全。我们用了 ThreadLocal 之后,相当于每个厨师只负责自己的一道菜,一共有 5 道菜,这样的话就非常清晰明了了,不会出现问题。
使用ThreadLocal的好处, 无非就是, 同一个线程无需通过方法参数传递变量, 因为变量是线程持有的, 所以想用就可以直接用。
一个request请求进入tomcat容器, 进入controller, 再进入service, 再进入dao, 可能还会向自定义线程池发一个异步任务
以 userId 为例:
以上所有方法, 如果都加上 String userId 作为参数有多丑陋不用我说大家也能想到, 即使你都加上了, 那么以后又多了一个字段你咋办? 再全改一遍吗 ?
TransactionSynchronizationManager
spring的事务是可以嵌套的, 可能是10个service方法属于一个事务, 如果没有这个机制那么所有方法签名都要加上 Connection connection 作为参数
RequestContextHolder
在任何地方都可以得到 request 请求的参数, 但是这个容易滥用, 导致不同层的代码耦合在一起, 如果你在 service 方法中用了他, 那么你的 service 方法就无法很方便的单元测试, 因为你耦合了 http 请求的一些东西, 这本身应该是 controller 关注的
比如异步调用发短信服务, 短信服务想知道user_id是谁, 那么加方法参数依然是丑陋的
好在 jdk 给我们解决了一部分也就是, 如果用的是InheritableThreadLocal 那么在new Thread()的时候会复制这些变量到新线程, 但是如果你用的线程池就搞不定了
因为线程池中的线程初期是 new Thread 可以将变量带过去, 后期就不会 new Thread了, 而是从 pool 中直接拿一个 thread, 也就触发不了这一步了, 因此需要用到阿里开源的一个框架 transmittable-thread-local 来改造线程池来支持tl的变量传递。
=====================================================
每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。
例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。
在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦
|
之前我们项目上线后发现部分用户的日期居然不对了,排查下来是SimpleDataFormat的锅,当时我们使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了。 其实要解决这个问题很简单,让每个线程都new 一个自己的 SimpleDataFormat就好了,但是1000个线程难道new1000个SimpleDataFormat? 所以当时我们使用了线程池加上ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程有一个SimpleDataFormat的副本,从而解决了线程安全的问题,也提高了性能。 |
|
package com.hy.db;
import java.sql.Connection; import java.sql.DriverManager;
public class DBManager { private static final String URL = "jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8"; private static final String USER = "root"; private static final String PWD = "root";
public static Connection getConn() throws Exception { Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(URL, USER, PWD);
return conn; }
public static void main(String[] args) throws Exception { System.out.println(DBManager.getConn()); } } |
|
package com.hy.filter;
import java.io.IOException;
import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter;
@WebFilter("*.do") public class TransactionManagerFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override public void destroy() {
} } |
try{
conn.setAutoCommit(false); //开启事务
chain.doFilter(req,resp);// 放行();
conn.commit(); //提交事务
}catch(Exception ex){
conn.rollback(); //回滚事务
}
|
package com.hy.utils;
public class TransactionManager { // 开启事务 public static void beginTrans() { }
// 提交事务 public static void commit() { }
// 回滚事务 public static void rollback() { } } |
现在问题的焦点来到了,如何在TranscationManager中获取Connection对象,当然可以在方法中传递Connection对象,但是这是面向对象的方式。
|
package com.hy.utils;
import java.sql.Connection;
import com.hy.db.DBManager;
public class TranscationManager { private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
// 开启事务 public void beginTrans() throws Exception { // 获取Connection对象 Connection conn = threadLocal.get();
if (conn == null) { // 重新获取connecton对象 conn = DBManager.getConn(); // 将Connection对象放在ThreadLocal操作的map中。 threadLocal.set(conn); }
// 设置不自动提交 conn.setAutoCommit(false); }
// 提交事务 public void commit() throws Exception { // 获取Connection对象 Connection conn = threadLocal.get();
if (conn == null) { // 重新获取connecton对象 conn = DBManager.getConn(); // 将Connection对象放在ThreadLocal操作的map中。 threadLocal.set(conn); }
conn.commit(); }
// 回滚事务 public void rollback() throws Exception { // 获取Connection对象 Connection conn = threadLocal.get();
if (conn == null) { // 重新获取connecton对象 conn = DBManager.getConn(); // 将Connection对象放在ThreadLocal操作的map中。 threadLocal.set(conn); } conn.rollback(); } } |
大家会发现,在这三个方法中,黄色代码部分都是一样的。这个代码的目的就是获取Connection对象。所以要想办法将这几句代码放入到DBManager当中。
|
package com.hy.db;
import java.sql.Connection; import java.sql.DriverManager;
public class DBManager { private static final String URL = "jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8"; private static final String USER = "root"; private static final String PWD = "root";
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private static Connection createConn() throws Exception { Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(URL, USER, PWD);
return conn; }
public static Connection getConn() throws Exception { Connection conn = threadLocal.get();
if(conn == null) { conn = createConn();
threadLocal.set(conn); }
return threadLocal.get(); }
public static void closeConn() throws SQLException { Connection conn = threadLocal.get();
if(conn == null) { return; }
if(!conn.isClosed()) { conn.close(); threadLocal.set(null); } } public static void main(String[] args) throws Exception { System.out.println(DBManager.getConn()); } } |
|
package com.hy.utils;
import com.hy.db.DBManager;
public class TranscationManager { // 开启事务 public void beginTrans() throws Exception { DBManager.getConn().setAutoCommit(false); }
// 提交事务 public void commit() throws Exception { DBManager.getConn().commit(); }
// 回滚事务 public void rollback() throws Exception { DBManager.getConn().rollback(); } } |
|
package com.hy.utils;
import com.hy.db.DBManager;
public class TransactionManager { // 开启事务 public static void beginTrans() throws Exception { DBManager.getConn().setAutoCommit(false); }
// 提交事务 public static void commit() throws Exception { DBManager.getConn().commit(); DBManager.closeConn(); }
// 回滚事务 public static void rollback() throws Exception { DBManager.getConn().rollback(); DBManager.closeConn(); } } |




是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的属性。
我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。
在默认情况下,每个线程对象都有两个属性,但是这两个属性量都为null
只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。
除此之外,和我所想的不同的是,每个线程的本地变量的值 不是存放在ThreadLocal对象中,而是放在调用的线程对象的threadLocals属性里面(前面也说过,threadLocals是Thread类的属性)。也就是说,
ThreadLocal类 其实相当于一个 管家一样(所谓的工具人),只是用来 存值/取值 的,但是 存的值/取的值都来自于 当前线程对象里 threadLocals属性,而这个属性是一个类似于Map的结构。
我们通过调用ThreadLocal的set方法将value值 添加到调用线程的threadLocals中,
通过调用ThreadLocal的get方法,它能够从它的当前线程的threadLocals中取出该值。
如果调用线程一直不终止,那么这个值(本地变量的值)将会一直存放在当前线程对象的threadLocals中。
当不使用本地变量的时候(也就是那个值时),需要只调用工具人ThreadLocal的 remove方法将其从当前线程对象的threadLocals中删除即可。
下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的

每个线程内部有一个名为threadLocals的属性,该属性的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

|
public void set(T value) { //(1)获取当前线程(调用者线程) Thread t = Thread.currentThread(); //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map ThreadLocalMap map = getMap(t); //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值 if (map != null) map.set(this, value); //(4)如果map为null,说明首次添加,需要首先创建出对应的map else createMap(t, value); } |
在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示
1 void createMap(Thread t, T firstValue) {
2 t.threadLocals = new ThreadLocalMap(this, firstValue);
3 }
createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。
|
public T get() { //(1)获取当前线程 Thread t = Thread.currentThread(); //(2)获取当前线程的threadLocals变量 ThreadLocalMap map = getMap(t); //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量 return setInitialValue(); }
private T setInitialValue() { //protected T initialValue() {return null;} T value = initialValue(); //获取当前线程 Thread t = Thread.currentThread(); //以当前线程作为key值,去查找对应的线程变量,找到对应的map ThreadLocalMap map = getMap(t); //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值 if (map != null) map.set(this, value); //如果map为null,说明首次添加,需要首先创建出对应的map else createMap(t, value); return value; } |
remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量
|
public void remove() { //获取当前线程绑定的threadLocals ThreadLocalMap m = getMap(Thread.currentThread()); //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量 if (m != null) m.remove(this); } |
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or